前回、Flutterのゲームエンジン「Flame」
に入門してみたけど、GameLoopってどうしてるのか気になり、
いろいろ調べてみたときの備忘録(*´ω`*)
Tickerで実現してるっぽい
ソースを見るとすごくシンプル
import 'package:flutter/scheduler.dart'; class GameLoop { GameLoop(this.callback) { _ticker = Ticker(_tick); } void Function(double dt) callback; Duration _previous = Duration.zero; late final Ticker _ticker; /// This method is periodically invoked by the [_ticker]. void _tick(Duration timestamp) { final durationDelta = timestamp - _previous; final dt = durationDelta.inMicroseconds / Duration.microsecondsPerSecond; _previous = timestamp; callback(dt); } void start() { if (!_ticker.isActive) _ticker.start(); } void stop() { _ticker.stop(); _previous = Duration.zero; } void step(double stepTime) { if (!_ticker.isActive) callback(stepTime); } void dispose() { _ticker.dispose(); } }
FlutterのTickerを使っていて、
Tickerはアニメーションフレームごとに関数を呼び出してくれるやつ
GameLoopはRenderGameWidgetでつくられる
FlameはRenderObjectWidgetとRenderBoxを継承した、
RenderGameWidgetとGameRenderBoxで描画しているっぽい。
GameLoopはGameRenderBoxがattachされたときに生成&スタートしているよう。
/// A [RenderObjectWidget] that renders the [GameRenderBox]. /// /// This is the widget that is used by the [GameWidget] to ACTUALLY /// render the game. class RenderGameWidget extends LeafRenderObjectWidget { // ...略 @override RenderBox createRenderObject(BuildContext context) { return GameRenderBox(game, context, isRepaintBoundary: addRepaintBoundary); } // ...略 } class GameRenderBox extends RenderBox with WidgetsBindingObserver { // ...略 @override void attach(PipelineOwner owner) { super.attach(owner); _attachGame(owner); } void _attachGame(PipelineOwner owner) { game.attach(owner, this); final gameLoop = this.gameLoop = GameLoop(gameLoopCallback); if (!game.paused) { gameLoop.start(); } _bindLifecycleListener(); } // ...略 void gameLoopCallback(double dt) { assert(attached); if (!attached) { return; } game.update(dt); markNeedsPaint(); } // ...略 }
- flame/packages/flame/lib/src/game/game_render_box.dart at main · flame-engine/flame
- LeafRenderObjectWidget class - widgets library - Dart API
- RenderBox class - rendering library - Dart API
RenderGameWidgetは、GameWidgetで生成/組込され、
GameWidgetはStatefulWidgetを継承してる。
class GameWidget<T extends Game> extends StatefulWidget { // ...略 } class GameWidgetState<T extends Game> extends State<GameWidget<T>> { // ...略 @override Widget build(BuildContext context) { return _protectedBuild(() { Widget? internalGameWidget = RenderGameWidget( game: currentGame, addRepaintBoundary: widget.addRepaintBoundary, ); // ... 略 ); } }
GameLoopの一時停止/再開はGameで
GameLoopの操作はGameから行っている感じ
GameRenderBoxがattachされたとき(GameRenderBoxのattach()
)に、
GameRenderBoxがGameをattachする(game.attach()
)っぽい。
abstract mixin class Game { // ...略 /// Marks game as attached to any Flutter widget tree. /// /// Should not be called manually. void attach(PipelineOwner owner, GameRenderBox gameRenderBox) { // ...略 _gameRenderBox = gameRenderBox; // ...略 } // ...略 /// Pauses or resume the engine set paused(bool value) { _paused = value; final gameLoop = _gameRenderBox?.gameLoop; if (gameLoop != null) { if (value) { gameLoop.stop(); } else { gameLoop.start(); } } } /// Pauses the engine game loop execution. void pauseEngine() { _paused = true; _gameRenderBox?.gameLoop?.stop(); } /// Resumes the engine game loop execution. void resumeEngine() { _paused = false; _gameRenderBox?.gameLoop?.start(); } /// Steps the engine game loop by one frame. Works only if the engine is in /// paused state. By default step time is assumed to be 1/60th of a second. void stepEngine({double stepTime = 1 / 60}) { if (_paused) { _paused = false; _gameRenderBox?.gameLoop?.step(stepTime); _paused = true; } } // ...略 }
まとめ
Flameでは、GameがGameLoopをもつっぽい感じだと思ったけど、
- GameWidget extends StatefulWidget
- RenderGameWidget extends LeafRenderObjectWidget
- GameRenderBox extends RenderBox with WidgetsBindingObserver
- ここでGameLoopを生成
- &gameのupdate()を呼び出す
という感じっぽい。
Game/GameWidget/RenderGameWidget/GameRenderBox/GameLoopは、
役割ごとに分かれているけど、どれも1:1で、共通的に管理されているっぽい。
なるほど。。(*´ω`*)