持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 8 天,点击查看活动详情
前言
这是一套 张风捷特烈 出品的 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. 本文目标
之前的主角是通过键盘来控制移动,但移动设备中一般不通过键盘操作,而是点击操作。比如下面的人物在点击时,会动画移动到点击的位置,这就涉及到构件的动画移动。
另外还有个小细节,就是点击时触点的动画,其特点是在点击后显示,一会便自动消失。本文的目标就是实现这两个小功能。
https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cce1c676d91649b28deb5cd95882379e~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
2. 点触动画
这里点击时一闪的小星星,本质上是一个序列动画,如下是序列图片:
我们在 【第一篇】 就介绍了通过 SpriteAnimationComponent
构件对序列帧进行动画播放,但当时并没有细说。 类型维护的 SpriteAnimation
对象是由 Sprite
列表构成的列表,本质上就是在 update
方法中,根据时长来不断更新显示的帧索引而已。
这里把触点指示器封装成一个构件 TouchIndicator
,由外界提供 position
确定位置。在 onLoad
回调中,加载序列帧图片形成 SpriteAnimation
。注意一点,默认情况下序列帧动画是在不断运行的,可以指定 loop: false
设置播放一次。
class TouchIndicator extends SpriteAnimationComponent {
TouchIndicator({required Vector2 position})
: super(
size: Vector2(30, 30),
anchor: Anchor.center,
position: position,
);
@override
Future<void> onLoad() async {
List<Sprite> sprites = [];
for(int i=1;i<=10;i ){
sprites.add(await Sprite.load('touch/star_${'$i'.padLeft(2,'0')}.png'));
}
removeOnFinish = true;
animation = SpriteAnimation.spriteList(sprites, stepTime: 1/15,loop: false);
}
}
复制代码
另外 SpriteAnimationComponent
内部维护了 removeOnFinish
成员,用于表示是否在序列帧播放完毕时移除当前构件,这里设置为 true
即可实现如下效果:代码见 【07/01】
https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ee948b27ce364e73be33ee44d4875935~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
TolyGame
中,只需要监听 onPanDown
事件,添加 TouchIndicator
构件即可。
class TolyGame extends FlameGame with TapDetector, PanDetector {
@override
void onPanDown(DragDownInfo info) {
add(TouchIndicator(position: info.eventPosition.global));
}
}
复制代码
3. 移动点触
点击显示 TouchIndicator
完成了,其实移动也就非常简单,覆写 onPanUpdate
方法,在其中添加 TouchIndicator
即可。但这里有个问题,onPanUpdate
更新的频率是非常快的,直接添加就会是如下效果,密密麻麻的一坨。这显然并非我们期望的:
https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/65c80a63cdad49e3bf7b7f4baa2b5c54~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
代码语言:javascript复制@override
void onPanUpdate(DragUpdateInfo info) {
add(TouchIndicator(position: info.eventPosition.global));
}
复制代码
这个问题的解决方式其实很简单:只要在更新期间,校验一下偏移量是否大于某个值,再添加即可。效果如下,当间隔的位移长度大于 10
时,才添加 TouchIndicator
:
https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c9ff0ae2278a40009f4b4fc11c525c96~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
代码语言:javascript复制double ds = 0;
@override
void onPanUpdate(DragUpdateInfo info) {
ds = info.delta.global.length;
if (ds > 10) {
add(TouchIndicator(position: info.eventPosition.global));
ds = 0;
}
}
复制代码
4.构建的的移动效果: MoveEffect
需要让角色动画移动到某个点,可以添加 MoveEffect
构件。如下,在 Adventurer
类中定义toTarget
方法,使用 MoveEffect.to
创建效果对象并进行添加。代码详见:【07/02】
https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7eb84802c5a747ab9311a36b89863319~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
其中 EffectController
可以指定很多动画的参数,比如重复次数、动画曲线、动画时长等。后面会就各种 Effect
进行专文介绍,这里暂时不深入,简单使用以下即可:
---->[07/02/Adventurer]----
final double _speed = 100;
void toTarget(Vector2 target) {
double timeMs = (target-position).length/_speed;
add(
MoveEffect.to(
target,
EffectController(
duration: timeMs,
),
),
);
}
复制代码
然后在 TolyGame
中,点击屏幕时,执行 player.toTarget
方法。角色就会从当前位置,动画移动到指定的 target
位置。
@override
void onPanDown(DragDownInfo info) {
Vector2 target = info.eventPosition.global;
add(TouchIndicator(position: target));
player.toTarget(target);
}
复制代码
4. Effect 效果的移除
上面的处理会出现一个问题,如下图所示:当前一次移动动画没有结束前,点一下其他位置,由于两个动画效果同时作用在构建上,所以无法正常完成移动到某点的任务。
https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6f00db242f5b48d09d55d2c461f674bb~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
解决的思路是:当点击时,应该要移除之前的 MoveEffect
,避免对接下来的移动效果产生影响。每个 Component
中都有 children
成员,表示子构件列表;通过 whereType
可以获取指定类型的子构件列表;然后使用 removeAll
将其移除即可。这样点哪里,角色就是动画运动到哪里。
https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cce1c676d91649b28deb5cd95882379e~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
代码语言:javascript复制void toTarget(Vector2 target) {
removeAll(children.whereType<MoveEffect>());
double timeMs = (target-position).length/_speed;
add(
MoveEffect.to(
target,
EffectController(
duration: timeMs,
),
),
);
}
复制代码
本文主要介绍了如何让帧动画在播放完后自动移除,以此实现点触时和移动时的闪光动画。接着介绍了使用 MoveEffect
构件完成动画移动的效果。这两者结合起来,就完成了对角色通过触点来控制移动的需求。那本文就到这里,明天见 ~
@张风捷特烈 2022.06.01 未允禁转
我的 掘金主页
: 张风捷特烈我的 B站主页
: 张风捷特烈我的 github 主页
: toly1994328