【Flutter&Flame游戏 - 捌】装弹完毕 角色

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 9 天,点击查看活动详情


前言

这是一套 张风捷特烈 出品的 Flutter&Flame 系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列文章一览:

第一季完结,谢谢支持 ~


1. 本文目标

今天来看一下角色如何发射子弹,这里把 子弹 作为 发射物 的统称。少数人不要杠,明明是弓箭,非说是子弹。关于子弹,有些注意点,首先它是基于某个角色进行产出的;其次,它会被频繁创建和销毁。它被销毁的时机包括:命中物体时,移出屏幕,或者超出射程,又或者固定在诞生几秒后自动移除等。

这里使用射程来对子弹进行移除,对水平发射而言,射程就是子弹在水平方向上的偏移距离,如下图蓝框所示区域:


2. 主动触发帧动画

前面我们的弓手是不断循环的帧动画,现在来先看一下如何主动触发:比如下面案例中,按下键盘的 J 键就执行一次动画,代码详见 【08/01】


AdventureronLoad 方法中,指定 playingfalse 可以在开始不会执行帧动画。将 loop 置为 false ,帧就不会重复执行;通过 animationonComplete 回调方法,可以监听到帧动画结束的时机。这里当结束时,触发 _onLastFrame ,置为第一帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dart复制代码---->[08/01/Adventurer$onLoad]----
playing = false;
animation = SpriteAnimation.spriteList(
sprites,
stepTime: 0.15,
loop: false,
);
animation!.onComplete = _onLastFrame;

---->[08/01/Adventurer$_onLastFrame]----
void _onLastFrame() {
animation!.currentIndex = 0;
animation!.update(0);
}

那如何让执行帧动画呢,很简单:将 playing 置为 true ,然后触发 animationreset 方法即可。如下通过 shoot 方法完成,只要在监听 J 按键,触发 shoot 即方法可。

1
2
3
4
dart复制代码void shoot() {
playing = true;
animation!.reset();
}

3. 子弹的发射

如下,定义 Bullet 构建来表述子弹角色,在构造时指定图片 sprite 和最大射程 maxRange 。子弹在诞生之后,就会一直处于运动状态,可以覆写 update 方法,根据时间和速度计算偏移量。如下 tag1 处所示:当偏移总量大于 maxRange 时,进行移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dart复制代码class Bullet extends SpriteComponent {
double _speed = 200;
final double maxRange;

Bullet({required Sprite sprite, required this.maxRange})
: super(sprite: sprite);

double _length = 0;

@override
void update(double dt) {
super.update(dt);
Vector2 ds = Vector2(1, 0) * _speed * dt;
_length += ds.length;
position.add(ds);
if (_length > maxRange) { // tag1
_length = 0;
removeFromParent();
}
}
}

接下来只要在 Adventurer 动画序列完成后,也就是 _onLastFrame 回调方法中添加子弹即可。这里有两个知识点,其一 priority 可以确定构件的优先级,默认情况下,后被添加的的显示在上层。这里要让子弹在角色下方,把角色优先级高于子弹即可。

第二点是:这里使用 gameRef 添加子弹,而添加入 Adventurer 自身中。因为如果添加到 Adventurer ,其作为子构件,会伴随 Adventurer 移动,这并不符合尝试。比如你扔个石头,离手后它不会随着你的移动而移动。代码详见:【08/02】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dart复制代码---->[08/02/Adventurer]----
late Sprite bulletSprite;

---->[onload]----
bulletSprite = await gameRef.loadSprite('adventurer/weapon_arrow.png');

void _onLastFrame() async{
animation!.currentIndex = 0;
animation!.update(0);

// 添加子弹
Bullet bullet = Bullet(sprite: bulletSprite,maxRange: 200);
bullet.size = Vector2(32, 32);
bullet.anchor = Anchor.center;
bullet.priority = 1;
priority = 2;
bullet.position = position-Vector2(0,-3);
gameRef.add(bullet);
}

4. 命中处理 - 极简版

如下图所示,接下来把前几篇的知识串联一下:综合角色移动、子弹发射、怪兽受伤害,做个小场景。其中弓箭和怪物的碰撞检测,使用最精简的方式:矩形区域。代码详见:【08/03】

这种校验的思路是:在每帧触发 update 时,校验怪物的矩形区域是否包含某点。比如说,当弓箭的中心在怪物的矩形域中,就表示命中。代码处理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dart复制代码@override
void update(double dt){
super.update(dt);
final Iterable<Bullet> bullets = children.whereType<Bullet>();
for(Bullet bullet in bullets){
if(bullet.shouldRemove){
continue;
}
if(monster.containsPoint(bullet.absoluteCenter)){ // tag1
bullet.removeFromParent();
monster.loss(50);
break;
}
}
}

其中上面tag1 处的 absoluteCenter 代表构件中心的绝对坐标,如下以该点为圆心画了一个小圆示意:


另外,大家可以基于此自己尝试实现怪兽不断发射子弹,攻击主角的功能。经历了这八篇的研究,完成了一个小的交互,也借此简单认识了一下 Flame 框架的使用。到现在算是个尝鲜,还有一些比较重要的基础概念还没涉及:比如 Component 的生命周期、各种 Effect 效果、相机操作、高级的碰撞检测等。在后续会逐步介绍,那本文就到这里,明天见 ~

\

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%