flutter游戏开发flame(三)word、手柄、主角

160 阅读3分钟

在教程二中主场景创建了所有需要的东西,接着我们具体看看这些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();
  }
}
  1. 主角: 因为主角有动画所以继承的是SpriteAnimationComponent,只要创建个animation就能动起来了,很方便。
  2. 动画:主角的资源图片大概是长这样的

image.png

amount:多少个为一组,stepTime: 动画间隔时间,textureSize: 每块材料的大小,texturePosition: 材料的起始位置。根据这些就可以将上下左右四组动画跑起来了。

  1. 移动:在update方法中,我们获取手柄的偏移量relativeDelta,然后position加上偏移量,主角就跟着手柄动起来了。根据你自己的实际情况加上一些速度、方向的控制逻辑即可。isStop我这里是为了后面撞墙用的。

  2. 碰撞:add(RectangleHitbox()..isSolid = true..debugMode = false);加入碰撞检测边界,debugMode=true,可以显示边界线; 混入碰撞检测回调CollisionCallbacks:

    • onCollisionStart:碰撞开始,我这里主要是赋值撞墙的方向,在update中清掉该方向的偏移量,就实现了撞墙的效果。
    • onCollisionEnd:碰撞结束,清除撞墙状态。
  3. 攻击:attack是混的自定义的事件:PlayerSkills,主要和技能盘对应,有四个方法,这里只是示意下,就只实现了普通攻击,发射子弹bullet。

  4. 吃金币:doEatCoin是混的自定义的事件:EatCoin,我这里是金币CoinComponent被碰撞后去触发主角吃金币的。你也可以在onCollisionStart判断被碰撞对象other的类型直接触发吃金币。

  5. 状态监听:add(FlameBlocListener<GlobalCubit, GlobalState>(onNewState: (state) { name.text = state.count.toString(); }));添加状态监听,当状态改变时,我们要修改相应组件的内容。