[Notas de desarrollo para Aircraft Wars basado en Flutter&Flame] Uso de bloque para administrar el estado del juego

prefacio

Continúe desarrollando la guerra de aviones, se ha realizado la composición básica del juego. El resto son funciones del panel, como la salud, la puntuación y los accesorios de misiles que no se han implementado antes . Este artículo documentará cómo usarlo blocpara la gestión estatal .

El autor ha incluido esta serie de artículos en las siguientes columnas, y los estudiantes interesados ​​pueden leer:

Notas de desarrollo para Aircraft Wars basadas en Flutter&Flame

El uso del bloque en el juego.

Dado que el enfoque de este artículo no es comprender el modo de desarrollo, aquí hay un artículo para presentar el bloque, que puede ayudarlo a comprender este modo de desarrollo más a fondo.

Administración de estados de aleteo BLoC

Necesita agregar dependencias equatable, flame_bloc,flutter_bloc

dependencies:
  flutter:
    sdk: flutter
  flame: ^1.2.0
  flame_audio: ^1.0.2
  equatable: ^2.0.3
  flame_bloc: ^1.6.0
  flutter_bloc: ^8.0.1

Basado en la idea, el autor blocha diseñado varias clases para el estado del juego:

  • GameStatusBloc: bloccapa, que es responsable de procesar lo que se pasa desde la capa de UI 事件eventy actualizar el estado . PD: Dado que no existe una lógica complicada en la guerra de aeronaves por el momento, el procesamiento aquí es básicamente recibir un evento y luego actualizar un estado.
  • GameStatusState: Estado, aquí representa el estado global del juego , que actualmente incluye salud, puntaje, estado del juego ( playing、gameover...), etc.
  • GameStatusEvent: Eventos, que representan los eventos globales del juego, como el inicio del juego, el final del juego, el aumento o disminución de la salud, etc.

Tome el evento de inicio del juego como ejemplo para ver cómo va el flujo de datos aproximado:imagen.png

Evento GameStatusEvent

Definir un evento como el inicio del juego , heredado deGameStatusEvent

abstract class GameStatusEvent extends Equatable {
  const GameStatusEvent();
}

class GameStart extends GameStatusEvent {
  const GameStart();

  @override
  List<Object?> get props => [];
}

Estado GameStatusStatus

这里对游戏运行状态有一个枚举GameStatus的定义

enum GameStatus {
  initial, // 初始化
  playing, // 游戏中
  gameOver // 游戏结束
}

GameStatusState的定义包括生命值、分数、导弹道具数、游戏运行状态

class GameStatusState extends Equatable {
  final int score;
  final int lives;
  final GameStatus status;
  final int bombSupplyNumber;
  。。。

bloc层 GameStatusBloc

GameStatusBloc定义了接收到事件GameStart后,如何更新状态GameStatusState

class GameStatusBloc extends Bloc<GameStatusEvent, GameStatusState> {
  GameStatusBloc() : super(const GameStatusState.empty()) {
    。。。
    
    on<GameStart>((event, emit) {
      emit(state.copyWith(status: GameStatus.playing));
    });
    
    。。。
  }
}

这里是将游戏运行状态GameStatus更新为playing

GameStatusBloc的对象会被保存在Game中,当游戏开始时,就会调用Game#gameStart()将事件发送出去。ps:这里类名被修改成SpaceGame,与之前的文章有些不同。

class SpaceGame extends FlameGame with HasDraggables, HasCollisionDetection {
  final GameStatusBloc gameStatusBloc;

  SpaceGame({required this.gameStatusBloc});

  。。。

  void gameStart() {
    gameStatusBloc.add(const GameStart());
  }

  。。。
}

这样再结合上述的流程图,一个基于bloc管理的全局状态雏型就出来了。可以注意到上述的GameStatusBloc是通过构造方法传递下来的,接下来看看它真正创建的地方在哪。

结合flutter_bloc

GameStatusBloc是通过BlocProvider从Flutter的父Widget传递下去的,这里使用MultiBlocProvider支持多个provider。笔者对之前的代码进行了扩展,GameView里面包含了Flame中的GameWidget。这样做主要是想利用Flutter的控件来编写面板展示的逻辑,这个本文不涉及所以可暂不理会。

void main() {
  runApp(const MaterialApp(
    home: GamePage(),
  ));
}

class GamePage extends StatelessWidget {
  const GamePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: MultiBlocProvider(
        providers: [
          // GameStatusBloc的创建
          BlocProvider<GameStatusBloc>(create: (_) => GameStatusBloc())
        ],
        child: SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: const GameView()),
      ),
    );
  }
}

// class GameView
GameWidget(game: SpaceGame(gameStatusBloc: context.read<GameStatusBloc>())

然后再回去看看Game#onLoad方法,在Flame中可以通过FlameBlocProviderGameStatusBloc传递给子Component子Component可对此进行状态监听。这里使用FlameMultiBlocProvider,支持多个provider

@override
Future<void> onLoad() async {
  final ParallaxComponent parallax = await loadParallaxComponent(
      [ParallaxImageData('background.png')],
      repeat: ImageRepeat.repeatY, baseVelocity: Vector2(0, 25));
  add(parallax);

  await add(FlameMultiBlocProvider(providers: [
    FlameBlocProvider<GameStatusBloc, GameStatusState>.value(
        value: gameStatusBloc)
  ], children: [
    player = Player(
        initPosition: Vector2((size.x - 75) / 2, size.y + 100),
        size: Vector2(75, 100)),
    EnemyCreator(),
    GameStatusController(),
  ]));
}

上述代码可知,这里的Component树层级关系与之前有所不同 imagen.png

这样在FlameMultiBlocProvider下的子Component就能监听到GameStatusState的变化了。

监听GameStatusState变化

继续利用上面的游戏开始事件为例,笔者在Player#onLoad中添加了一个进场效果,用的是之前的MoveEffect

// class Player
@override
Future<void> onLoad() async {
  。。。

  add(MoveEffect.to(Vector2(position.x, gameRef.size.y * 0.75),
      EffectController(duration: 1.5, curve: Curves.easeOutSine))
    ..onComplete = () {
      gameRef.gameStart();
    });

  add(FlameBlocListener<GameStatusBloc, GameStatusState>(
      listenWhen: (pState, nState) {
    return pState.status != nState.status;
  }, onNewState: (state) {
    if (state.status == GameStatus.playing) {
      _shootingTimer.start();
    } else if (state.status == GameStatus.gameOver) {
      _shootingTimer.stop();
      if (_bulletUpgradeTimer.isRunning()) _bulletUpgradeTimer.stop();
      current = GameStatus.gameOver;
    }
  }));
}
  • 进场效果完成后,会调用Game#gameStart(),这样就与前面的逻辑形成闭环了,经过bloc的处理,GameStatusState就更新为playing了。
  • 还记得这里之前有一个Timer用于定时发射子弹吗?之前的开启和停止是依赖onMount/onRemove的,这里就通过FlameBlocListener回调的游戏状态决定了。
  • 笔者将Player改成一个SpriteAnimationGroupComponent了,主要是方便作战机Component被击毁的效果,这个与之前的Enemy类似就不多赘述了。【基于Flutter&Flame 的飞机大战开发笔记】重构敌机

ps:之前的EnemyCreator定时生成的逻辑也是同理。

最后

本文主要记录基于bloc管理飞机大战的全局状态,相关逻辑参考Flame官方的例子:flame/packages/flame_bloc。后续会基于此状态来添加游戏面板的逻辑。

Supongo que te gusta

Origin juejin.im/post/7120188013543424031
Recomendado
Clasificación