[Flutter&Flameに基づく航空機戦争の開発ノート]表示パネルと再起動メニュー

序文

以前の管理ベースのblocグローバルな状態が形になりました。この記事では、このデータを使用して、Flutterのコントロールに基づいたゲームの表示パネルと、ゲームを再開するためのメニューロジックを作成します。

著者はこのシリーズの記事を次のコラムに含めており、興味のある学生は次の記事を読むことができます。

Flutter&Flameに基づく航空機戦争の開発ノート

パネルディスプレイ

以前のパッケージを覚えていGameViewますか?これがすべてのロジックであり、その上にさまざまなパネルを表示するためのGameWidgetレイヤーがあります。Stackなお、入手できるようするGameView父WidgetMultiBlocProviderためです子WidgetGameStatusBloc

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(),
        ),
      );
    });
  }
}

効果

パネル表示の効果を見てみましょう

スクリーンショット_2022-07-15-15-23-31-22_13914082904e1b7ce2b619733dc8fcfe.jpg
  • ヘルスパネルの右下隅に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プロパティを提供し、overlayBuilderMap1つを渡すことができますkey-valuevalueは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');

メニュークラスResetMenuFlutter、ネイティブUIの基本的な操作であるため、ここでは展開されません。効果を見るだけ

Record_2022-07-15-16-20-36_13914082904e1b7ce2b619733dc8fcfe_.gif

やっと

この記事では、AircraftWarsのパネル表示と再起動メニューについて説明します。この時点で、ゲーム全体が完全に完了しています。関連するロジックについては、Flameの公式例:flame / packages/flame_blocを参照してください。

おすすめ

転載: juejin.im/post/7120516724151025672