Play animation once and stay on last frame

Godot Version

v4.5.stable.mono.official [876b29033]

Question

This is my code:

public void HandleAnimation(IHasState owner, float moveDirection,
                            bool isJumping, bool isFalling) 
{
  if (owner.State == PlayerState.Default) {
    HandleHorizontalFlip(moveDirection);
    HandleMoveAnimation(moveDirection);
    HandleJumpAnimation(isJumping, isFalling);
  } else if (owner.State == PlayerState.Hold) {
    HandleHorizontalFlip(moveDirection);
    HandleHoldAnimation(moveDirection);
  } else if (owner.State == PlayerState.Frozen) {
    animationPlayer.Play("get_hit");
  } else if (owner.State == PlayerState.Defeated) {
    animationPlayer.Play("defeat");
  }
}

The problem - this method is being called on loop in PhysicsProcess of the Player class. Frozen means the player has been damaged. Defeated means player health is zero. In both cases player will stay in this period for a short while at least, in case of defeat as long as defeat popup menu is active. And during that time those animations are constantly called on loop, so they play on loop, instead of playing only once and staying on last frame.

Animation Player has an option to customize animation looping: play once, loop, or play from start to end and from end to start. I have it set to play once, but animation player is not really concerned about it. I understand why, but it’s very unintuitive.

So I want to figure out how to bypass this problem. I have a bunch of code-first solutions in mind, but I want to know if there is very easy or GUI based solution for it. I don’t want to take out cannons to kill a fly.

I have managed to find pretty clean solution (at least it seems to work for now).
Add this property:

private string lastAnimation = string.Empty;

Add this method:

    private void PlayOnce(string animationName)
    {
        if (lastAnimation == animationName)
            return;
        
        animationPlayer.Play(animationName);
    }

And update HandleAnimation:

    public void HandleAnimation(IHasState owner, float moveDirection, bool isJumping, bool isFalling)
    {
        if (owner.State == PlayerState.Default)
        {
            HandleHorizontalFlip(moveDirection);
            HandleMoveAnimation(moveDirection);
            HandleJumpAnimation(isJumping, isFalling);
        }
        else if (owner.State == PlayerState.Hold)
        {
            HandleHorizontalFlip(moveDirection);
            HandleHoldAnimation(moveDirection);
        }
        else if (owner.State == PlayerState.Frozen)
        {
            PlayOnce("get_hit");
        }
        else if (owner.State == PlayerState.Defeated)
        {
            PlayOnce("defeat");
        }
        if(!string.IsNullOrEmpty(animationPlayer.CurrentAnimation))
            lastAnimation = animationPlayer.CurrentAnimation;
    }

And all of it just works with minimal overhead. I will mark my own code as solution for future generations, unless someone provides better solution. Preferably no-code, because maybe there is a setting for that that I’m just missing, but from what I’ve seen on the internet people solve it with code, and sometimes that code seems too complex.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.