【基于Flutter&Flame 的飞机大战开发笔记】子弹发射及碰撞检测
前言
有了前面的基础,战机和敌机的环境已经搭建好了。故本文着重记录基于Flame
实现飞机大战的战机子弹发射以及基于碰撞检测实现的子弹设计敌机和战机与敌机相遇场景。
子弹Component
创建一个继承自SpriteAnimationComponent
的类Bullet1
作为子弹的实现类。这个和之前的一样都当是一个序列帧处理。
class Bullet1 extends SpriteAnimationComponent with CollisionCallbacks {
double speed = 200;
Bullet1() : super();
@override
Future<void> onLoad() async {
List<Sprite> sprites = [];
sprites.add(await Sprite.load('bullet/bullet1.png'));
final SpriteAnimation spriteAnimation =
SpriteAnimation.spriteList(sprites, stepTime: 0.15);
animation = spriteAnimation;
}
为了让子弹Component
运动起来,沿用之前敌机Component
的做法。在其update
回调后更新position
。
@override
void update(double dt) {
super.update(dt);
Vector2 ds = Vector2(0, -1) * speed * dt;
position.add(ds);
if (position.y < 0) {
removeFromParent();
}
}
这个与之前类似,只是子弹是往y轴负方向移动至屏幕外的,所以这里是Vector2(0, -1)
。同样也是s = v * t
的体现。
子弹发射
这里需要令战机Component
定时发射出子弹。还是使用之前讲到的Timer
,间隔0.5s
发射一次。
class Player extends SpriteAnimationComponent with HasGameRef, Draggable {
...
late Timer _shootingTimer;
@override
Future<void> onLoad() async {
...
_shootingTimer = Timer(0.5, onTick: _addBullet, repeat: true);
}
- 定义
_shootingTimer
作为子弹Component
的生成定时,与前面类似。 - 定时触发方法
_addBullet
。
void _addBullet() {
final Bullet1 bullet1 = Bullet1();
bullet1.size = Vector2(5, 11);
bullet1.priority = 1;
priority = 2;
bullet1.position = Vector2(position.x + size.x / 2, position.y);
gameRef.add(bullet1);
}
_addBullet
方法新建一个子弹Component
,这是上文创建的实体。需要注意的是
- 由于子弹是相对于屏幕运动的,而不是
战机Component
。所以这里需要添加到Game
也就是最外层。 - 后加入的
Component
会之于前面的Component
之上(类似帧布局),所以这里为了使效果更像是从战机发射出来的,需要调整两者的优先级。 子弹Component
的初始位置会在战机Component
上方的中心点上Vector2(position.x + size.x / 2, position.y)
。ps:这是默认战机Component
的锚点在右上角的计算结果。
击中敌机及战机与敌机碰撞
要实现子弹击中敌机的效果,我们需要知道子弹Component
和敌机Component
的相遇或者说是碰撞的时机。这里我们运用Flame
提供的碰撞检测机制(CollisionCallbacks
)实现。
我们分别对类Enemy1
和Bullet1
添加CollisionCallbacks
的混入
class Bullet1 extends SpriteAnimationComponent with CollisionCallbacks {
class Enemy1 extends SpriteAnimationComponent
with HasGameRef, CollisionCallbacks {
同时我们需要在两个Component
上添加一个RectangleHitbox
,这个在之前调试位置的时候有用到。碰撞检测的默认机制是通过两个RectangleHitbox
进行的。如果玩过我的世界的同学应该对碰撞箱有所耳闻,这个其实也是类似。
@override
Future<void> onLoad() async {
add(RectangleHitbox());
}
实现CollisionCallbacks
中的onCollisionCallback
,这个回调的是碰撞发生时的事件。以敌机Component
为例:
// class Enemy1
@override
CollisionCallback<PositionComponent>? get onCollisionCallback =>
(Set<Vector2> intersectionPoints, PositionComponent other) {
if (other is Bullet1 || other is Player) {
removeFromParent();
}
};
回调了一个PositionComponent
对象,即为与其发生碰撞的Component
。这里可以判断其类型是子弹Component
和战机Component
时将敌机Component
移除。ps:这里先默认敌机Component
的生命值为1。
ps:CollisionCallbacks
还可以回调碰撞开始和碰撞结束事件:
// collision_callbacks.dart
/// [onCollision] is called in every tick when this object is colliding with
/// [other].
@mustCallSuper
void onCollision(Set<Vector2> intersectionPoints, T other) {
onCollisionCallback?.call(intersectionPoints, other);
}
/// [onCollisionStart] is called in the first tick when this object starts
/// colliding with [other].
@mustCallSuper
void onCollisionStart(Set<Vector2> intersectionPoints, T other) {
activeCollisions.add(other);
onCollisionStartCallback?.call(intersectionPoints, other);
}
/// [onCollisionEnd] is called once when this object has stopped colliding
/// with [other].
@mustCallSuper
void onCollisionEnd(T other) {
activeCollisions.remove(other);
onCollisionEndCallback?.call(other);
}
在Game
这一层还需要添加HasCollisionDetection
混入,这样碰撞检测才能开启并往下传递。
class Game extends FlameGame with HasDraggables, HasCollisionDetection {
这样就能实现击中敌机及战机与敌机碰撞的效果了,其他地方的碰撞检测同理,这里就不一一赘述了。来看看效果吧
转载自:https://juejin.cn/post/7117584272458219533