prefacio
El anterior estado global basadobloc
en la gestión ha tomado forma. Este artículo usará estos datos para crear un panel de visualización para el juego basado en los controles de Flutter , así como la lógica del menú para reiniciar el juego .
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
panel de visualización
¿Recuerdas el empaque de antes GameView
? Aquí está toda la lógica, GameWidget
y hay una capa encima Stack
para mostrar los diferentes paneles. Cabe señalar que GameView
es父Widget
MultiBlocProvider
para que se 子Widget
pueda obtener 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()),
],
),
)],
))
],
);
}
}
panel de salud
Hablar solo con el panel de salud es en realidad un modo regular bloc
Al BlocBuilder
escuchar GameStatusState
, el método se activará builder
para actualizar la interfaz de usuario después de la actualización. Este es Flutter
el punto de conocimiento en el nivel original.
Cabe señalar que aquí se utiliza Offstage
para ocultar y mostrarGameStatus
la vista, la condición es el estado de ejecución del juego mencionado en el artículo anterior .
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(),
),
);
});
}
}
Efecto
Echemos un vistazo al efecto de la pantalla del panel.
- Hay tres aviones pequeños en la esquina inferior derecha del panel de salud, el monitoreo es
GameStatusState.lives
. - Hay dos más, uno para puntales de misiles y otro para puntuar. Estos dos son similares a los anteriores y no se discutirán aquí.
- 导弹道具面板,在左下角,顺带一提之前没有提及这个功能,它是在游戏中通过道具获得,和子弹补给同理。监听的是
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
, reanudandogameRef.resumeEngine()
el tiempo de ejecución del juego y eliminando el menú en este momento. Por cierto, hay partes que deben eliminarse aquí , por ejemplo . Uno necesita ser agregado nuevamente porque el anterior ha sido eliminado .Component
敌机Component、补给Component、子弹Component
战机Component
Menú GameOver
GameWidget
Proporcione una overlayBuilderMap
propiedad, puede pasar una key-value
. value es el builder
método de esta vista.
GameWidget(
game: SpaceGame(gameStatusBloc: context.read<GameStatusBloc>()),
overlayBuilderMap: {
'menu_reset': (_, game) {
return ResetMenu(game: game as SpaceGame);
}
},
)
Cuando necesite mostrar y ocultar, como arriba, llame al add/remove
método.
// 显示
gameRef.overlays.add('menu_reset');
// 隐藏
gameRef.overlays.remove('menu_reset');
La clase de menú ResetMenu
, dado que es Flutter
la operación básica de la interfaz de usuario nativa, no se expandirá aquí. Solo mira el efecto
Al final
Este artículo documenta la visualización del panel y el menú de reinicio de Aircraft Wars. En este punto, todo el juego es bastante completo. Para conocer la lógica relacionada, consulte el ejemplo oficial de Flame: flame/packages/flame_bloc .