一、 新的可能性
Google I/O
2022 对于 Flutter
而言,将 休闲游戏
带入了大众的视野。让 Flutter
除了应用开发之外,有了新的可能性。其中作为游戏开发引擎的 Flame ,也为更多人所知晓。说实话,之前我并不怎么看得上 Flame
,无论是官网还是文档,内容都很少,感觉非常小众。我期待着官方可以出一个游戏引擎,但现在看来,官方也倾向于使用 Flame
引擎来开发休闲的 2D
游戏,那我无需再等,开摆 。
- 【Flutter&Flame 游戏 - 壹】开启新世界的大门
- 【Flutter&Flame 游戏 - 贰】操纵杆与角色移动
- 【Flutter&Flame 游戏 - 叁】键盘事件与手势操作
- 【Flutter&Flame 游戏 - 肆】精灵图片加载方式
- 【Flutter&Flame 游戏 - 伍】Canvas 参上 | 角色的血条
- 【Flutter&Flame 游戏 - 陆】暴击 Dash | 文字构件的使用
- 【Flutter&Flame 游戏 - 柒】人随指动 | 动画点触与移动
- 【Flutter&Flame 游戏 - 捌】装弹完毕 | 角色武器发射
- 【Flutter&Flame 游戏 - 玖】探索构件 | Component 是什么
- 【Flutter&Flame 游戏 - 拾】探索构件 | Component 生命周期回调
- 【Flutter&Flame 游戏 - 拾壹】探索构件 | Component 使用细节
- 【Flutter&Flame 游戏 - 拾贰】探索构件 | 角色管理
- 【Flutter&Flame 游戏 - 拾叁】碰撞检测 | CollisionCallbacks
- 【Flutter&Flame 游戏 - 拾肆】碰撞检测 | 之前代码优化
- 【Flutter&Flame 游戏 - 拾伍】粒子系统 | ParticleSystemComponent
- 【Flutter&Flame 游戏 - 拾陆】粒子系统 | 粒子的种类
- 【Flutter&Flame 游戏 - 拾柒】构件特效 | 了解 Effect 体系
- 【Flutter&Flame 游戏 - 拾捌】构件特效 | ComponentEffect 一族
- 【Flutter&Flame 游戏 - 拾玖】构件特效 | 了解 EffectController 体系
- 【Flutter&Flame 游戏 - 贰拾】构件特效 | 其他 EffectControler
- 【Flutter&Flame 游戏 - 贰壹】视差组件 | ParallaxComponent
- 【Flutter&Flame 游戏 - 贰贰】菜单、字体和浮层
未完待续 ~
1. 弹球开源弹球游戏 pinball
在 I/O 2022
中, 官方用 Flutter
写的弹球小游戏,确实让我眼前一亮,开源地址在:【pinball】。这说明基本的碰撞、音乐、动画没有什么问题,用来做休闲小游戏是足够的,我也就没什么好担心的了。
所以,接下来将开启一个系列,研究 Flutter&Flame
的游戏 2D
休闲游戏开发。另外,为了录屏、截图方便,这里主要在 macOS
平台上运行,实现桌面版的 Flutter
游戏。
2. 本文目标
本文作为 Flame
最简使用,相当于一个 Hello World
级别的案例。
- 项目搭建与资源配置
- 播放背景音乐
- 显示如下的人物动作
二、项目搭建
1. 依赖与资源配置
首先在 pubspec.yaml
中引入 flame
和 flame_audio
包。
---->[]----
dependencies:
#...
flame: ^1.1.1
flame_audio: ^1.0.2
然后在根目录下创建 assets
目录,其中图片放在 images
文件夹下,音乐放在 audio
下,并在 pubspec.yaml
中配置对应的文件夹。这里的背景音乐,取自【pinball】 中,图片资源在网上找的。
2. 最简代码
这里先实现一下静态图片的展示 背景音乐播放:代码 【tag1-1】
目前 lib
代码结构如下:
├── lib
│ ├── component.dart
│ └── main.dart
在 main.dart
里,runApp
方法传入 GameWidget
组件,其中 game
入参对象是自定义的 TolyGame
。继承自 FlameGame
,并重写 onLoad
方法,添加一个自定义的 HeroComponent
。另外通过 FlameAudio.play
方法播放音乐。
---->[main.dart]----
main() {
runApp(GameWidget(game: TolyGame()));
FlameAudio.play('background.mp3');
}
class TolyGame extends FlameGame {
@override
Future<void> onLoad() async {
await add(HeroComponent());
}
}
在 component.dart
中,HeroComponent
继承自 SpriteComponent
。在 super
构造中指定 size
和 anchor
参数。覆写 onLoad
方法,为 sprite
成员实例化。在 onGameResize
中将位置居中,这样运行项目,就可以得到如下的效果:
---->[component.dart]----
class HeroComponent extends SpriteComponent {
HeroComponent() : super(size: Vector2(50,37), anchor: Anchor.center);
@override
Future<void> onLoad() async {
sprite = await Sprite.load('adventurer/adventurer-bow-00.png');
}
@override
void onGameResize(Vector2 gameSize) {
super.onGameResize(gameSize);
position = gameSize / 2;
}
}
3. 初步分析
在自定义的 HeroComponent
中,我们操作了两个没有声明的对象,这说明肯定是在父类中声明过了。其中sprite
成员定义在 SpriteComponent
中,position
成员定义在 PositionComponent
中。
---->[源码-sprite_component.dart]----
class SpriteComponent extends PositionComponent with HasPaint {
Sprite? sprite;
---->[源码-position_component.dart]----
@override
set position(Vector2 position) => transform.position = position;
简单瞄一下源码可以看到继承关系,所以在子类中可直接使用这两个属性。position
是 Vector2
对象,可以确定位置,sprite
是 Sprite
对象,可以确定资源。
PositionComponent
|--- SpriteComponent
|--- HeroComponent (自定义)
从这里可以简单感知到,在 Flame
中 Component
是一个比较重要的概念,它可以决定显示。为了避免和 Flutter
中的 Widget 组件
语义冲突, 这里称 Component
为 构件
。
三、多图人物的帧动画
上面简单地实现了展示一张图片,下面来看一下多帧的图片如何显示:代码 【tag1-2】
1. 代码实现
之所以看到射手在动,是因为在不断播放,如下文件夹是不同帧对应的图片,adventurer-bow
有 9
帧。
实现起来也非常简单,单图有 SpriteComponent
构件,多图也有对应的 SpriteAnimationComponent
构件。该类中内置声明了SpriteAnimation
类型的 animation
对象,所以在 onLoad
中初始化即可。其中 stepTime
用于控制运动的速度,这里 0.15
比较正常,数值越小,运动越快。
class HeroComponent extends SpriteAnimationComponent {
HeroComponent() : super(size: Vector2(50,37), anchor: Anchor.center);
@override
Future<void> onLoad() async {
List<Sprite> sprites = [];
for(int i=0;i<=8;i ){
sprites.add(await Sprite.load('adventurer/adventurer-bow-0$i.png'));
}
animation = SpriteAnimation.spriteList(sprites, stepTime: 0.15);
}
@override
void onGameResize(Vector2 gameSize) {
super.onGameResize(gameSize);
position = gameSize / 2;
}
}
2. 本文小结
通过这个小案例,我们见到了几个类,这里来梳理一下。其中 GameWidget
是继承自 GameWidget
的组件,构造时必须传入 Game
类型的 game
入参。
class GameWidget<T extends Game> extends StatefulWidget {
final T game;
const GameWidget({
Key? key,
required this.game,
另外, FlameGame
类继承自 Component
并且混入 Game
,这也是为什么 FlameGame
子类可以作为 game
参数的原因。
class FlameGame extends Component with Game {
最后,最重要的莫过于 Component
构件,可以看出我们当前的代码都是围绕 Component
一族展开的。我们自定义类中覆写的 onLoad
、onGameResize
方法,都是定义在 Component
中的。另外 add
方法,可以添加一个 Component
对象,这是很明显的组合设计模式。
---->[源码-component.dart]----
Future<void>? onLoad() => null;
@mustCallSuper
void onGameResize(Vector2 size) => handleResize(size);
Future<void>? add(Component component) => component.addToParent(this);
在后面我们应该还会遇到功能各异的 Component
,或也可能自定义一个 Component
来实现某种特殊的功能。本文作为一个简单的引子,想介绍的就这么多,那就到这里,明天见 ~
@张风捷特烈 2022.05.26 未允禁转
我的 公众号: 编程之王
我的 掘金主页
: 张风捷特烈我的 B站主页
: 张风捷特烈我的 github 主页
: toly1994328