在教程二中主场景创建了所有需要的东西,接着我们具体看看这些component的具体实现,真的是保姆级别的教程了,多多收藏点赞啊。
world
class CollisionDetectionWorld extends World with HasCollisionDetection {}
这里继承下HasCollisionDetection
,为的是启用碰撞检测器,角色碰撞吃金币要用到。
手柄
class MyJoystickComponent extends JoystickComponent {
MyJoystickComponent()
: super(
margin: const EdgeInsets.only(left: 20, bottom: 20),
knob: CircleComponent(radius: 30, paint: BasicPalette.blue.withAlpha(200).paint()),
background: CircleComponent(radius: 70, paint: BasicPalette.blue.withAlpha(100).paint()),
) {
priority = 99;
}
}
继承系统手柄,主要就是调下样式颜色、大小和离屏幕的边距,设置下权重避免被覆盖。
主角
class PlayerComponent extends SpriteAnimationComponent with HasGameRef, CollisionCallbacks, PlayerSkills, EatCoin {
///移动速度
double maxSpeed = 300.0;
///攻击速度
double fireSpeed = 1000;
DateTime? lastDateTime;
late final ui.Image personImage;
final JoystickComponent joystick;
late final SpriteAnimation upAnimation;
late final SpriteAnimation leftAnimation;
late final SpriteAnimation rightAnimation;
late final SpriteAnimation downAnimation;
/// 碰撞时禁止行进的方向
SpriteAnimation? isStop;
Direction direction = Direction.down;
late TextComponent name;
PlayerComponent(this.joystick) : super(size: Vector2(32, 48));
@override
Future<void> onLoad() async {
personImage = await Flame.images.load('brave_man.png');
leftAnimation = SpriteAnimation.fromFrameData(
personImage,
SpriteAnimationData.sequenced(
amount: 4,
stepTime: 0.2,
textureSize: Vector2(32, 48),
texturePosition: Vector2(0, 48),
),
);
rightAnimation = SpriteAnimation.fromFrameData(
personImage,
SpriteAnimationData.sequenced(
amount: 4,
stepTime: 0.2,
textureSize: Vector2(32, 48),
texturePosition: Vector2(0, 96),
),
);
upAnimation = SpriteAnimation.fromFrameData(
personImage,
SpriteAnimationData.sequenced(
amount: 4,
stepTime: 0.2,
textureSize: Vector2(32, 48),
texturePosition: Vector2(0, 144),
),
);
downAnimation = SpriteAnimation.fromFrameData(
personImage,
SpriteAnimationData.sequenced(
amount: 4,
stepTime: 0.15,
textureSize: Vector2(32, 48),
texturePosition: Vector2(0, 0),
),
);
animation = downAnimation;
position = gameRef.size / 2;
add(RectangleHitbox()
..isSolid = true
..debugMode = false);
name = TextComponent(position: Vector2(0, -24));
add(FlameBlocListener<GlobalCubit, GlobalState>(onNewState: (state) {
name.text = state.count.toString();
}));
name.text = app.globalCubit.state.count.toString();
add(name);
}
@override
void update(double dt) {
super.update(dt);
if (!joystick.delta.isZero()) {
var delta = Vector2(joystick.relativeDelta.x, joystick.relativeDelta.y);
var screenAngle = joystick.delta.screenAngle();
if (-pi / 4 <= screenAngle && pi / 4 >= screenAngle) {
if (isStop == upAnimation) {
return;
}
if (isStop == leftAnimation || isStop == rightAnimation) {
delta.setValues(0, delta.y);
}
animation = upAnimation;
direction = Direction.up;
} else if (pi / 4 < screenAngle && pi * 3 / 4 > screenAngle) {
if (isStop == rightAnimation) {
return;
}
if (isStop == upAnimation || isStop == downAnimation) {
delta.setValues(delta.x, 0);
}
animation = rightAnimation;
direction = Direction.right;
} else if (-pi / 4 > screenAngle && -pi * 3 / 4 < screenAngle) {
if (isStop == leftAnimation) {
return;
}
if (isStop == upAnimation || isStop == downAnimation) {
delta.setValues(delta.x, 0);
}
animation = leftAnimation;
direction = Direction.left;
} else {
if (isStop == downAnimation) {
return;
}
if (isStop == leftAnimation || isStop == rightAnimation) {
delta.setValues(0, delta.y);
}
animation = downAnimation;
direction = Direction.down;
}
position.add(delta * maxSpeed * dt);
} else {
animation!.loop = false;
}
}
@override
void onCollisionStart(Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
isStop = animation;
}
@override
void onCollisionEnd(PositionComponent other) {
super.onCollisionEnd(other);
isStop = null;
}
@override
attack() {
if (lastDateTime == null ||
DateTime.now().millisecondsSinceEpoch - lastDateTime!.millisecondsSinceEpoch > fireSpeed) {
lastDateTime = DateTime.now();
var bullet = BulletComponent(
position: Vector2.copy(position + size / 2),
direction: direction,
);
parent!.add(
bullet,
);
}
}
@override
doEatCoin() {
app.globalCubit.increment();
}
}
- 主角: 因为主角有动画所以继承的是
SpriteAnimationComponent
,只要创建个animation就能动起来了,很方便。 - 动画:主角的资源图片大概是长这样的
amount:多少个为一组,stepTime: 动画间隔时间,textureSize: 每块材料的大小,texturePosition: 材料的起始位置。根据这些就可以将上下左右四组动画跑起来了。
-
移动:在update方法中,我们获取手柄的偏移量relativeDelta,然后position加上偏移量,主角就跟着手柄动起来了。根据你自己的实际情况加上一些速度、方向的控制逻辑即可。isStop我这里是为了后面撞墙用的。
-
碰撞:
add(RectangleHitbox()..isSolid = true..debugMode = false);
加入碰撞检测边界,debugMode=true,可以显示边界线; 混入碰撞检测回调CollisionCallbacks:- onCollisionStart:碰撞开始,我这里主要是赋值撞墙的方向,在update中清掉该方向的偏移量,就实现了撞墙的效果。
- onCollisionEnd:碰撞结束,清除撞墙状态。
-
攻击:
attack
是混的自定义的事件:PlayerSkills
,主要和技能盘对应,有四个方法,这里只是示意下,就只实现了普通攻击,发射子弹bullet。 -
吃金币:
doEatCoin
是混的自定义的事件:EatCoin
,我这里是金币CoinComponent
被碰撞后去触发主角吃金币的。你也可以在onCollisionStart
判断被碰撞对象other的类型直接触发吃金币。 -
状态监听:
add(FlameBlocListener<GlobalCubit, GlobalState>(onNewState: (state) { name.text = state.count.toString(); }));
添加状态监听,当状态改变时,我们要修改相应组件的内容。