前言
前面根据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(),
),
);
});
}
}
作用
来看看面板展现的作用吧
- 生命值面板在右下角三个小飞机,监听的是
GameStatusState.lives
。 - 还有两个,一个是导弹道具,一个是计分。这两个和上述的大同小异,这儿就不打开说了。
- 导弹道具面板,在左下角,顺带一提之前没有提及这个功能,它是在游戏中经过道具取得,和子弹补给同理。监听的是
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
属性,能够传一个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的基本操作,这儿就不打开了。直接看看作用吧
最终
本文记录了飞机大战的面板展现与从头开端菜单。至此,整个游戏就适当完整了。相关逻辑参考Flame官方的比如:flame/packages/flame_bloc。