【Flutter&Flame游戏 - 捌】装弹完毕 | 角色武器发射

2022-06-19 16:10:30 浏览数 (1)

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


前言

这是一套 张风捷特烈 出品的 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://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4b601aaef5cb4287abd7c73b2b6d8778~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?

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


2. 主动触发帧动画

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

https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2a438c942fc34707908c777305bdf0a1~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?


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

代码语言:javascript复制
---->[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 即方法可。

代码语言:javascript复制
void shoot() {
  playing = true;
  animation!.reset();
}
复制代码

3. 子弹的发射

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

代码语言:javascript复制
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】

https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/faf10574769142b89a4e6b74ef4593ed~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?

代码语言:javascript复制
---->[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】

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d9486ed49a154b6ea914be1347f0c2a0~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?

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

代码语言:javascript复制
@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 效果、相机操作、高级的碰撞检测等。在后续会逐步介绍,那本文就到这里,明天见 ~

  • @张风捷特烈 2022.06.02 未允禁转
  • 我的 掘金主页 : 张风捷特烈
  • 我的 B站主页 : 张风捷特烈
  • 我的 github 主页 : toly1994328

0 人点赞