前言
这是一套 张风捷特烈 出品的 Flutter&Flame
系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列文章一览:
- 【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. Flame 官方案例
在 github
仓库中 flame/examples
中是官方的案例,对于入门而言是很有参考意义的。 但它也不是非常惊艳,作为一个游戏引擎的官方案例来说,还是太过简陋。
其中介绍了很多基本模块,每个模块中的案例都能很方便地找到对应的源码,这一点还是很值得肯定的。
本文我们将基于如下的 Joystick
案例,介绍一下操纵杆的使用,以及角色的移动。移动是最基础的游戏交互,还是先介绍为好。
本文的效果如下,通过左下角的操纵杆,来移动角色:本文源码于 【lib/02】
2. 操纵杆的使用
操纵杆的原理非常简单,如下以大圆中心为原点建立坐标系,正方向分别向 右
和 下
。通过小圆心的坐标就可以确定偏移量以及旋转角度。这里主要使用偏移量来修改角色的 position
位置。
同样,操纵杆本身也是 Component
构建。如下,在 TolyGame
的 onLoad
中构造 JoystickComponent
对象,通过 add
方法加入到游戏中。主要这里的 TolyGame
需要混入 HasDraggables
,才能支持操纵杆拖拽。
---->[02/game.dart]----
class TolyGame extends FlameGame with HasDraggables {
late final JoystickComponent joystick;
@override
Future<void> onLoad() async {
final knobPaint = BasicPalette.blue.withAlpha(200).paint();
final backgroundPaint = BasicPalette.blue.withAlpha(100).paint();
joystick = JoystickComponent(
knob: CircleComponent(radius: 25, paint: knobPaint),
background: CircleComponent(radius: 60, paint: backgroundPaint),
margin: const EdgeInsets.only(left: 40, bottom: 40),
);
add(joystick);
}
现在操纵杆已经加入到了 游戏场景
之中,接下来把角色加入进来。方式也很简单,创建 HeroComponent
对象,再添加到场景中即可。代码如下:
---->[02/game.dart]----
late final HeroComponent player;
---->[onLoad 方法]----
player = HeroComponent();
add(player);
这就说明,游戏中的各种角色,都是 Component
构件,添加到游戏场景之中,后添加的在上层。游戏的核心是维护各个对象数据间的关系。
3. 角色的移动
在上一篇中,我们介绍了 PositionComponent
一族的构件中有 position
属性,来定位角色位置。也就是说,只要根据操纵杆的偏移量,对 position
属性进行修改即可。另外说一下,在一个 Component
中添加 RectangleHitbox
,就可以有如上的紫色信息框,便于查看角色所占区域即位置。
class HeroComponent extends SpriteAnimationComponent with HasGameRef {
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 gameRef.loadSprite('adventurer/adventurer-bow-0$i.png'));
}
animation = SpriteAnimation.spriteList(sprites, stepTime: 0.15);
position = gameRef.size / 2;
add(RectangleHitbox()..debugMode = true);
}
double speed = 200.0; // Pixels/ 秒
void move(Vector2 ds){
position.add(ds);
}
}
这里定义一个 move
方法,接受 Vector2
位移量,类中定义了一个 speed
,用于控制移动速度,值越大就表示每秒运动的位移越长。
4. 世界的刷新
我们日常生活中有钟表计时,可以明确时间的概念,现实中时间是不断进行的,永不停息。在游戏开发中也是类似,默认情况下世界处于不断刷新渲染之中,每次的刷新渲染成为一帧。如果每秒渲染 60
次,那就说明游戏每秒可达 60
帧,也就是常说的 60fps
。不过游戏中的时间是可以暂停的。
另外,在 Component
类中定义了 update
方法,可以覆写它来监听每次刷新的事件。前面我们知道 FlameGame
本身也是 Component
,所以在子类 TolyGame
中可以覆写 update
来监听帧的更新。通过打印日志可以看出来,会不断触发,其中 dt
回调表示两帧之间的时间差。而且每帧之间约等于 0.01666 秒
,也就是 16.6 ms
,即每秒可刷新 60
次。
---->[02/game.dart/TolyGame]----
@override
void update(double dt) {
super.update(dt);
print(dt);
}
使用,只要在 update
回调中,执行 player
的 move
方法即可修改角色位置。其中 joystick.relativeDelta
是偏移量和外圆半径的比值,也就是指移动的百分比。根据物理学公式,可以计算出偏移位移
ds = v * t
其中速度是一个二维的向量,是速度值和 joystick.relativeDelta
向量结合获得的。从而达到操纵杆百分比越大,速度越快的效果。
@override
void update(double dt) {
super.update(dt);
if (!joystick.delta.isZero()) {
Vector2 ds = joystick.relativeDelta * player.speed * dt;
player.move(ds);
}
}
另外可以通过 joystick.delta.screenAngle()
获取操纵杆的旋转角度,也就是可以对角色进行旋转操作,如下所示:
在 PositionComponent
中除了 Vector2
类型的 position
进行定位;还有double
类型的 angle
用于控制旋转角度;以及 Vector2
类型的 scale
控制缩放。
---->[HeroComponent#rotateTo]----
void rotateTo(double deg){
angle = deg;
}
在 joystick
偏移了非零时,获取角度为 player
设置旋转角度即可。另外,如果操纵杆偏移量为 0
,恢复原位。
---->[TolyGame#update]----
// 角色旋转
if (!joystick.delta.isZero()) {
player.rotateTo(joystick.delta.screenAngle());
}else{
player.rotateTo(0);
}
5. 小结
本文主要简单认识了一下 JoystickComponent
操纵杆构件,并基于此实现了对角色的移动和旋转操作。也简单认识了一下世界的刷新的触发,这里简单瞄一下源码,其实刷新的触发和 Flutter
原生的 Animation
动画刷新是类似的,都是基于 Ticker
来触发。
Flame
引擎中的 GameLoop
就相当于一个没有停止时间,不断运行的动画。看过《动画小册》的应该对这些比较清楚,这里不过多引申,后面有机会再掰扯掰扯源码。动画和游戏有种类似的本质,都是连续变化的帧。只不过游戏有大量的交互和对象间关系的处理,逻辑非常复杂而已。那本文就到这里,明天见 ~
@张风捷特烈 2022.05.27 未允禁转
我的 公众号: 编程之王
我的 掘金主页
: 张风捷特烈我的 B站主页
: 张风捷特烈我的 github 主页
: toly1994328