序文
以前の管理ベースのbloc
グローバルな状態が形になりました。この記事では、このデータを使用して、Flutterのコントロールに基づいたゲームの表示パネルと、ゲームを再開するためのメニューロジックを作成します。
著者はこのシリーズの記事を次のコラムに含めており、興味のある学生は次の記事を読むことができます。
パネルディスプレイ
以前のパッケージを覚えていGameView
ますか?これがすべてのロジックであり、その上にさまざまなパネルを表示するためのGameWidget
レイヤーがあります。Stack
なお、入手できるようにするGameView
父Widget
MultiBlocProvider
ためです。子Widget
GameStatusBloc
class GameView extends StatelessWidget {
const GameView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(
child: GameWidget(
game: SpaceGame(gameStatusBloc: context.read<GameStatusBloc>()),
overlayBuilderMap: {
'menu_reset': (_, game) {
return ResetMenu(game: game as SpaceGame);
}
},
)),
SafeArea(
child: Stack(children: [
const Positioned(top: 4, right: 4, child: ScorePanel()),
Positioned(
bottom: 4,
right: 4,
left: 4,
child: Row(
children: const [
Expanded(child: BombPanel()),
Expanded(child: LivePanel()),
],
),
)],
))
],
);
}
}
健康パネル
ヘルスパネルとの会話だけが実際には通常のbloc
モードです。BlocBuilder
リッスンGameStatusState
することで、メソッドがトリガーbuilder
され、更新後にUIが更新されます。これがFlutter
元のレベルの知識ポイントです。
ここではビューの表示と非表示をOffstage
使用していることに注意してください。条件は、前の記事で説明したゲームの実行状態です。GameStatus
class LivePanel extends StatelessWidget {
const LivePanel({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<GameStatusBloc, GameStatusState>(
builder: (context, state) {
int live = state.lives;
return Offstage(
offstage: state.status != GameStatus.playing,
child: Wrap(
spacing: 2,
runSpacing: 5,
alignment: WrapAlignment.end,
children: List.generate(
live,
(index) => Container(
constraints:
const BoxConstraints(maxWidth: 35, maxHeight: 35),
child: const Image(
image: AssetImage('assets/images/player/life.png')),
)).toList(),
),
);
});
}
}
効果
パネル表示の効果を見てみましょう
- ヘルスパネルの右下隅に3つの小さな平面があり、監視は
GameStatusState.lives
です。 - ミサイルの小道具用とスコアリング用の2つがあります。これら2つは上記に類似しているため、ここでは説明しません。
- 导弹道具面板,在左下角,顺带一提之前没有提及这个功能,它是在游戏中通过道具获得,和子弹补给同理。监听的是
GameStatusState.bombSupplyNumber
。 - 计分面板,在右上角,击落一艘
敌机Component
就会有相应的计分。监听的是GameStatusState.score
。
- 导弹道具面板,在左下角,顺带一提之前没有提及这个功能,它是在游戏中通过道具获得,和子弹补给同理。监听的是
GameOver与Replay
GameStatusController
当战机Component
的生命值到0时,使GameStatusState.status == GameStatus.gameOver
。来看一下GameStatusBloc
的处理逻辑。
// class GameStatusBloc
on<PlayerLoss>((event, emit) {
if (state.lives > 1) {
emit(state.copyWith(lives: state.lives - 1));
} else {
emit(state.copyWith(lives: 0, status: GameStatus.gameOver));
}
});
在上文中,我们往Component树
中塞多了一个叫GameStatusController
的东西。答案在这里揭晓了,它是专门用于响应当游戏运行状态变化时界面变化的。
- 当
GameStatusState.status == GameStatus.gameOver
时,需要先暂停游戏的运行时(还记得Flame
有一个update
方法回调吗?他是依赖运行时响应的)。然后展示GameOver
菜单。 GameOver
菜单会展示分数和一个Replay
按钮。Replay
按钮点击后,会重新将GameStatusState.status == GameStatus.initial
,此时恢复游戏的运行时,与之前的游戏开始逻辑形成闭环。
class GameStatusController extends Component with HasGameRef<SpaceGame> {
@override
Future<void> onLoad() async {
add(FlameBlocListener<GameStatusBloc, GameStatusState>(
listenWhen: (pState, nState) {
return pState.status != nState.status;
}, onNewState: (state) {
if (state.status == GameStatus.initial) {
gameRef.resumeEngine();
gameRef.overlays.remove('menu_reset');
if (parent == null) return;
parent!.removeAll(parent!.children.where((element) {
return element is Enemy || element is Supply || element is Bullet;
}));
parent!.add(gameRef.player = Player(
initPosition:
Vector2((gameRef.size.x - 75) / 2, gameRef.size.y + 100),
size: Vector2(75, 100)));
} else if (state.status == GameStatus.gameOver) {
Future.delayed(const Duration(milliseconds: 600)).then((value) {
gameRef.pauseEngine();
gameRef.overlays.add('menu_reset');
});
}
}));
}
}
- 还是利用
FlameBlocListener
监听GameStatusState
的变化。 GameStatus.gameOver
时,通过gameRef.pauseEngine()
可暂停游戏的运行时。这里的gameRef.overlays.add('menu_reset')
会在视图最上层添加一个菜单。下面会讲到。GameStatus.initial
、ゲームのランタイムを再開し、今すぐメニューを削除します。ちなみに、ここで削除する部分があります。前のものが削除されたため、もう一度追加する必要があります。gameRef.resumeEngine()
Component
敌机Component、补给Component、子弹Component
战机Component
GameOverメニュー
GameWidget
プロパティを提供し、overlayBuilderMap
1つを渡すことができますkey-value
。valueはbuilder
このビューのメソッドです。
GameWidget(
game: SpaceGame(gameStatusBloc: context.read<GameStatusBloc>()),
overlayBuilderMap: {
'menu_reset': (_, game) {
return ResetMenu(game: game as SpaceGame);
}
},
)
上記のように表示と非表示が必要な場合は、add/remove
メソッドを呼び出します。
// 显示
gameRef.overlays.add('menu_reset');
// 隐藏
gameRef.overlays.remove('menu_reset');
メニュークラスResetMenu
はFlutter
、ネイティブUIの基本的な操作であるため、ここでは展開されません。効果を見るだけ
やっと
この記事では、AircraftWarsのパネル表示と再起動メニューについて説明します。この時点で、ゲーム全体が完全に完了しています。関連するロジックについては、Flameの公式例:flame / packages/flame_blocを参照してください。