flame-engine / flame

A Flutter based game engine.
https://flame-engine.org
MIT License
9.28k stars 913 forks source link

Resetting an animation is not working #2584

Closed KurtLa closed 1 year ago

KurtLa commented 1 year ago

Current bug behavior

An animation cannot be reset after it is done, the old animation system could be reset. It gets stuck at the last frame. tooling

Expected behavior

When you reset an animation, it should behave as if it were new.

Steps to reproduce

You can use my repository to reproduce it: https://bitbucket.org/Jinairu/islandsurvivor_bugs/src/master/

clone, flutter pub get, run press F to use the tool or W, A, S, D to navigate

Flutter doctor output

flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.10.2, on macOS 13.2.1 22D68 darwin-arm64, locale en-DE) [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 14.2) [✓] Chrome - develop for the web [✓] Android Studio (version 2021.2) [✓] VS Code (version 1.79.2) [✓] Connected device (2 available) [✓] Network resources

• No issues found!

More environment information

Flame version: 1.8.0 Platform affected: all

More information

It feels like SpriteAnimationTicker is not resetting as expected. Also: Why is it necessary to maintain a separate ticker? Shouldn't a default ticker per animation be kept? If an animation is not active, then the ticker does not have to be updated.

ufrshubham commented 1 year ago

From a quick look it seems you are creating and resetting a complete separate ticker than what is used by the SpriteAnimationGroupComponent.

SpriteAnimationGroupComponent maintains a map of tickers for each of the animation states. You should get the active ticker using the animationTicker getter from the SpriteAnimationGroupComponent.

Additional info: You shouldn't create your own ticker unless you have a raw SpriteAnimation and want to manually tick it.

So to answer your question, you shouldn't maintain any ticker yourself 😄

ufrshubham commented 1 year ago

@KurtLa here is your fixed Player class. See if this is what your intended to achieve.

class Player extends SpriteAnimationGroupComponent
    with HasGameRef<MainGame>, CollisionCallbacks {
  PlayerStats playerStats = PlayerStats();
  LookDirection currentLookDirection = LookDirection.down;
  Set<CollisionSide> collisionSides = {};

  bool canMove = true;
  late Vector2 previousPosition;

  @override
  FutureOr<void> onLoad() {
    initializeAnimations();
    setupHitbox();
    previousPosition = position.clone();
    return super.onLoad();
  }

  void initializeAnimations() {
    offsetByRow(int row) => Vector2(0, row * 92.0);

    animations = {
      PlayerState.runTop: createAnimation(offsetByRow(0)),
      PlayerState.runLeft: createAnimation(offsetByRow(1)),
      PlayerState.runDown: createAnimation(offsetByRow(2)),
      PlayerState.runRight: createAnimation(offsetByRow(3)),
      PlayerState.idleTop: createAnimation(offsetByRow(0), loop: false),
      PlayerState.idleLeft: createAnimation(offsetByRow(1), loop: false),
      PlayerState.idleDown: createAnimation(offsetByRow(2), loop: false),
      PlayerState.idleRight: createAnimation(offsetByRow(3), loop: false),
      PlayerState.hammerTop: createAnimation(offsetByRow(5), loop: false),
      PlayerState.hammerLeft: createAnimation(offsetByRow(4), loop: false),
      PlayerState.hammerDown: createAnimation(offsetByRow(6), loop: false),
      PlayerState.hammerRight: createAnimation(offsetByRow(7), loop: false),
    };

    current = PlayerState.idleLeft;

    animationTickers![PlayerState.hammerTop]!.onComplete =
        () => _resetOnComplete(PlayerState.hammerTop);
    animationTickers![PlayerState.hammerLeft]!.onComplete =
        () => _resetOnComplete(PlayerState.hammerLeft);
    animationTickers![PlayerState.hammerDown]!.onComplete =
        () => _resetOnComplete(PlayerState.hammerDown);
    animationTickers![PlayerState.hammerRight]!.onComplete =
        () => _resetOnComplete(PlayerState.hammerRight);

    anchor = const Anchor(0.5, 0.65);
    size = size / 1.5;
    position = gameRef.survivalGame.roundProperties.spawnPosition.clone();
    anchor = Anchor.center;
  }

  void _resetOnComplete(PlayerState stateToReset) {
    animationTickers![stateToReset]!.reset();
    beIdle();
  }

  SpriteAnimation createAnimation(Vector2 texturePosition, {bool loop = true}) {
    return SpriteAnimation.fromFrameData(
      Flame.images.fromCache("player/player.png"),
      SpriteAnimationData.sequenced(
        amount: loop ? 9 : 6,
        stepTime: 0.1,
        textureSize: Vector2(92, 92),
        texturePosition: texturePosition,
        loop: loop,
      ),
    );
  }

  void setupHitbox() {
    add(RectangleHitbox(
      position: Vector2(size.x / 2, size.y / 1.5),
      size: size / 4,
      anchor: Anchor.center,
    ));
  }

  @override
  void update(double dt) {
    super.update(dt);
    priority = (position.y).round() + 1;
  }

  void useTool() {
    final animation = getPlayerAnimationForTool();
    current = animation.state;
  }

  PlayerAnimation getPlayerAnimationForTool() {
    switch (currentLookDirection) {
      case LookDirection.left:
        return PlayerAnimation(
            PlayerState.hammerLeft, animations![PlayerState.hammerLeft]!);
      case LookDirection.down:
        return PlayerAnimation(
            PlayerState.hammerDown, animations![PlayerState.hammerDown]!);
      case LookDirection.right:
        return PlayerAnimation(
            PlayerState.hammerRight, animations![PlayerState.hammerRight]!);
      case LookDirection.top:
        return PlayerAnimation(
            PlayerState.hammerTop, animations![PlayerState.hammerTop]!);
    }
  }

  @override
  void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
    // if (other is ResourcesObject) {
    //   position.sub(position - previousPosition);
    // }
    super.onCollision(intersectionPoints, other);
  }

  void savePosition() {
    previousPosition = position.clone();
  }

  void move(Vector2 displacement) {
    if (displacement != Vector2.zero()) {
      setPlayerStateAndLookDirection(displacement);
      savePosition();
      position.add(displacement);
      updatePlayerPosition();
    } else {
      beIdle();
    }
  }

  void setPlayerStateAndLookDirection(Vector2 displacement) {
    if (displacement.x < 0) {
      current = PlayerState.runLeft;
      currentLookDirection = LookDirection.left;
    } else if (displacement.x > 0) {
      current = PlayerState.runRight;
      currentLookDirection = LookDirection.right;
    } else if (displacement.y < 0) {
      current = PlayerState.runTop;
      currentLookDirection = LookDirection.top;
    } else if (displacement.y > 0) {
      current = PlayerState.runDown;
      currentLookDirection = LookDirection.down;
    }
  }

  void updatePlayerPosition() {
    gameRef.survivalGame.gameStatus.currentPlayerPosition = position.clone();
    gameRef.survivalGame.gameStatus.currentPlayerTile = position.clone()
      ..x = (x ~/ 32).toDouble()
      ..y = (y ~/ 32).toDouble();
  }

  void beIdle() {
    switch (currentLookDirection) {
      case LookDirection.left:
        current = PlayerState.idleLeft;
        break;
      case LookDirection.down:
        current = PlayerState.idleDown;
        break;
      case LookDirection.right:
        current = PlayerState.idleRight;
        break;
      case LookDirection.top:
        current = PlayerState.idleTop;
        break;
    }
  }

  void executeTool(PlayerTools tool) {
    switch (tool) {
      case PlayerTools.hammer:
        break;
    }
  }
}
KurtLa commented 1 year ago

@ufrshubham Ah, i see. I got my information from the SpriteAnimationComponent Screenshot 2023-06-21 at 21 06 21

Perfect, your solution works like a charm, thank you very much!

spydon commented 1 year ago

@ufrshubham Ah, i see. I got my information from the SpriteAnimationComponent Screenshot 2023-06-21 at 21 06 21

Perfect, your solution works like a charm, thank you very much!

Those docs should probably be updated to not use a separate ticker, you're not the first that has been confused by that. 😅