前言
敌机Component
重构之后,飞机大战的基础能力就基本成型了。本文会对子弹Component
作一次升级,添加游戏中的子弹道具,以及它与战机Component
的碰撞处理。
笔者将这一系列文章收录到以下专栏,欢迎有兴趣的同学阅读:
子弹特性
前面只是简单的定义了类Bullet1
作为子弹Component
,根据已有的属性可以定义一个属于子弹的父类Bullet
,这里继续使用抽象类。
abstract class Bullet extends SpriteAnimationComponent with CollisionCallbacks {
Bullet({required this.speed, required this.attack})
: super(size: Vector2(5, 11));
double speed;
int attack;
Future<SpriteAnimation> bulletAnimation();
@override
Future<void> onLoad() async {
animation = await bulletAnimation();
add(MoveEffect.to(
Vector2(position.x, -size.y), EffectController(speed: speed),
onComplete: () {
removeFromParent();
}));
add(RectangleHitbox());
}
void loss() {
removeFromParent();
}
}
定义比较简单
- 子弹拥有属于自己的速度
speed
,伤害attack
。 - 固定
size = 5 * 11
。 - 由于不同子弹的贴图不同,定义一个抽象方法
bulletAnimation()
由子类加载。 - 使用
MoveEffect
代替之前的s = v * t
控制移动,这个前文有讲到。这里是由初始位置(一般是战机Component
的头部)移动到屏幕最上方。 loss()
方法是与敌机Component
发生碰撞时的调用。
以上所列出的就是目前子弹的共有特性了。
升级子弹
游戏中战机Component
会通过获得道具的方式,短暂获取强化版子弹。这里用类Bullet2
表示
class Bullet2 extends Bullet {
Bullet2({required super.speed, required super.attack});
@override
Future<SpriteAnimation> bulletAnimation() async {
List<Sprite> sprites = [];
sprites.add(await Sprite.load('bullet/bullet2.png'));
final SpriteAnimation spriteAnimation =
SpriteAnimation.spriteList(sprites, stepTime: 0.15);
return spriteAnimation;
}
}
这里会加载另外一种贴图作为子弹的样式。
先来看看最终效果吧
子弹补给
如上面的效果,游戏中会随机生成子弹补给。这里定义一个抽象类Supply
。补给Component
的效果是匀速向下移动并带有小幅度的晃动。
- 匀速向下移动继续采用
MoveEffect
的方式。 - 小幅度的晃动需要使用
RotateEffect
做一个小幅度的旋转。由于需要一个吊起来来回晃动的效果,所以我们还需要将锚点设置为topCenter
,令旋转支点为上方居中的位置。
故最后的逻辑为
abstract class Supply extends SpriteComponent with HasGameRef {
Supply({position, size})
: super(position: position, size: size, anchor: Anchor.topCenter);
@override
Future<void> onLoad() async {
add(MoveEffect.to(
Vector2(position.x, gameRef.size.y), EffectController(speed: 40.0),
onComplete: () {
removeFromParent();
}));
add(RotateEffect.by(15 / 180 * pi,
EffectController(duration: 2, reverseDuration: 2, infinite: true)));
add(RectangleHitbox()..debugMode = true);
}
}
- 移动距离是从屏幕最上方向屏幕最下方移动。
- 旋转角度是弧度,这里是
(15 / 180)pi
,需要来回晃动,所以这里设置了正反方向的持续时间且效果是无限循环的。 - 最后需要添加
RectangleHitbox
,作为与战机Component
的碰撞检测。ps:碰撞检测的逻辑在子类实现,这里目前是类BulletSupply
。
子类的逻辑几乎只有碰撞检测,这个在下文会讲到,先来看看这个实现的效果
升级与恢复
在子弹补给Component
中,碰撞检测与战机Component
发生碰撞后
- 会触发其
upgradeBullet()
方法,此时会变更bulletType
,即子弹类型。 - 添加一个
0.15s
的ColorEffect
,作为子弹升级的反馈。 - 开启一个
Timer
,定时5s
,作为子弹恢复回正常的计时。ps:_bulletUpgradeTimer
设置了autoStart = false
,不允许自动开启。当调用start()
时,进度会重置,可理解为连续获得补给计时会重置。
// class BulletSupply
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
if (other is Player) {
other.upgradeBullet();
removeFromParent();
}
}
// class Player
int bulletType = 1;
_bulletUpgradeTimer = Timer(5, onTick: _downgradeBullet, autoStart: false);
void upgradeBullet() {
bulletType = 2;
add(ColorEffect(Colors.blue.shade900, const Offset(0.3, 0.0),
EffectController(duration: 0.15)));
_bulletUpgradeTimer.start();
}
void _downgradeBullet() {
bulletType = 1;
}
// timer.dart
/// Start the timer from 0.
void start() {
reset();
resume();
}
由于战机Component
,类Player
本身拥有一个定时器用于发射子弹,故在子弹类型bulletType
修改后,下一次发射子弹的逻辑就需要变更。
// class Player
void _addBullet() {
if (bulletType == 2) {
final Bullet2 bullet2 = Bullet2(speed: 400, attack: 2);
bullet2.priority = 1;
bullet2.position = Vector2(position.x + size.x / 2 - 10, position.y + 10);
final Bullet2 bullet2a = Bullet2(speed: 400, attack: 2);
bullet2a.priority = 1;
bullet2a.position =
Vector2(position.x + size.x / 2 + 10, position.y + 10);
gameRef.add(bullet2);
gameRef.add(bullet2a);
} else {
final Bullet1 bullet1 = Bullet1(speed: 200, attack: 1);
bullet1.priority = 1;
bullet1.position = Vector2(position.x + size.x / 2, position.y);
gameRef.add(bullet1);
}
}
补给生成
还记得那个敌机生成器EnemyCreator
吗?这里暂时通过它的定时来生成补给
// class EnemyCreator
void _createEnemy() {
final width = gameRef.size.x;
double x = _random.nextDouble() * width;
final double random = _random.nextDouble();
if (random < 0.05) {
final size = Vector2(60, 75);
if (width - x < size.x / 2) {
x = width - size.x / 2;
} else if (x < size.x / 2) {
x = size.x / 2;
}
final enemySupply =
BulletSupply(position: Vector2(x, -size.y), size: size);
add(enemySupply);
return;
}
。。。
需要注意的是:由于补给Component
的锚点被修改为topCenter
,所以position
的位置也被移动到了topCenter
。这里的边界计算需要按此情况作适配。
最后
本文记录子弹类型的扩展、子弹补给的效果和生成实现以及战机Component
的子弹升级的逻辑。至此,飞机大战的功能基本上都实现了,剩下附加道具、计分系统以及生命值系统等功能。后续会考虑参考官方的bloc
例子对整个项目的结构进行修改。