前言
最近在学习基于Flutter的游戏引擎Flame
,并尝试自己编写一个飞机大战。本文将记录工程搭建和玩家战机创建的过程。强烈推荐在阅读文章前,优先食用以下内容,这将会对你有一定的Flame
知识基础。当然,Flutter的基础也是必要的。
环境及资源搭建
新建项目后,在pubspec.yaml
中添加Flame的核心依赖。ps:这里笔者使用的是当前最新的1.2.0版本。
environment:
sdk: ">=2.17.5 <3.0.0"
dependencies:
flutter:
sdk: flutter
flame: ^1.2.0
还有就是常规的Flutter静态资源配置了。ps:这些资源不全是在本文中使用,图片资源都是在网上找到的,是微信飞机大战的资源仅为学习所用。
创建战机
游戏环境
void main() {
runApp(GameWidget(game: Game()));
}
class Game extends FlameGame with HasDraggables {
@override
Future<void> onLoad() async {}
}
在Flutter的main
函数中,runApp
传入一个GameWidget
,实际是一个StatefulWidget
。Game
继承自FlameGame
,它是Flame定义的一个Component
。Flame依赖于Component
进行展开,类比于Flutter依赖于Widget
。后续在游戏里的对象都会是在FlameGame
对象中添加Component
实现。
// game_widget.dart
class GameWidget<T extends Game> extends StatefulWidget {
final T? game;
...
onLoad
是Component
中的生命周期之一,学习过Android的话可以和Activity的onCreate进行类比。
玩家战机
class Player extends SpriteAnimationComponent {
Player({required Vector2 initPosition, required Vector2 size})
: super(position: initPosition, size: size);
@override
Future<void> onLoad() async {
List<Sprite> sprites = [];
for (int i = 1; i <= 2; i++) {
sprites.add(await Sprite.load('player/me$i.png'));
}
final spriteAnimation = SpriteAnimation.spriteList(sprites, stepTime: 0.15);
animation = spriteAnimation;
add(RectangleHitbox()..debugMode = true);
}
创建类Player
:
- 继承自
SpriteAnimationComponent
,这是一个可以加载动画的Component
,也可以理解为序列帧。这里战机共两帧,设置切换间隔为0.15s
。ps:默认是循环播放的,这里没有特殊需求循环即可。
// sprite_animation.dart
factory SpriteAnimation.spriteList(
List<Sprite> sprites, {
required double stepTime,
bool loop = true,
}) {
return SpriteAnimation(
sprites.map((sprite) => SpriteAnimationFrame(sprite, stepTime)).toList(),
loop: loop,
);
}
- 构造方法需要传入
initPosition
,size
,即初始位置和大小。两者都为Vector2
。ps:由于SpriteAnimationComponent
继承自PositionComponent
,所以会有anchor
锚点的概念,默认是左上角的,锚点的变更会影响position的值,以及后续在布局上的参考位置。 - 添加一个
RectangleHitbox
,可以观察战机当前的位置。
控制战机
在手机上操作战机一般会使用拖拽,或者是拖动的交互。可以在Player中加入HasGameRef
和Draggable
的混入。前者是用于获取最上层FlameGame
对象,即我们自定义的Game对象(ps:加入的Component也是一个树状结构);后者用于实现单个对象的拖拽效果。
class Player extends SpriteAnimationComponent with HasGameRef, Draggable {
Player({required Vector2 initPosition, required Vector2 size})
: super(position: initPosition, size: size);
@override
Future<void> onLoad() async {
// 。。。
}
@override
bool onDragUpdate(DragUpdateInfo info) {
final willToPosition = position + info.delta.global;
double x = willToPosition.x;
double y = willToPosition.y;
if (x < 0) {
x = 0;
} else if (x > gameRef.size.x - size.x) {
x = gameRef.size.x - size.x;
}
if (y < 0) {
y = 0;
} else if (y > gameRef.size.y - size.y) {
y = gameRef.size.y - size.y;
}
position = Vector2(x, y);
return true;
}
onDragUpdate
方法会返回一个DragUpdateInfo
对象,这里取info.delta.global
,即为相对于当前位置的移动差值。正常情况下只需要叠加到position即可。考虑到边界问题,所以需要获取Game对象的大小进行计算,再更新position。
ps:Draggable
还有onDragStart
、onDragEnd
等方法可实现,机制上类似原生的触摸事件机制。
在Component
最外层也就是Game需要添加HasDraggables
的混入,这样事件才能往下传递。
class Game extends FlameGame with HasDraggables {
实现效果
最后来看看实现效果,这里还加了一个视差组件作为背景。
最后
本文记录了基本的Flame环境搭建以及飞机大战中的玩家战机创建。后续会记录有关战机子弹的逻辑。