Land

This state simply plays a quick animation when the character lands on a platform before going to their regular Idle state. It's self-contained and automatically starts itself as necessary like the Introduction state. The video to the right is slowed down to properly show the animation since it is so quick.

Fields

The field tooltips explain what they each do:

public class LandState : CharacterState
{
    [SerializeField]
    private ClipTransition _Animation;

    [SerializeField, MetersPerSecond]
    [Tooltip("This state will only activate if the character is moving at least this fast downwards when they land")]
    private float _MinimumVerticalSpeed = 7;

    [SerializeField, Range(0, 1)]
    [Tooltip("The multiple of the character's regular speed at which they can move while in this state")]
    private float _MovementSpeedMultiplier = 1;

    public override float MovementSpeedMultiplier => _MovementSpeedMultiplier;

The _MovementSpeedMultiplier field controls how fast the character can move during the Landing animation. The Player has it set to the default 1 so the animation is purely cosmetic and doesn't influence their actual movement.

The OnValidate ensures that the fields always have valid values and also logs a warning if this component isn't on the same GameObject as a Collider2D because it needs to receive the OnCollisionEnter2D Message:

#if UNITY_EDITOR
    protected override void OnValidate()
    {
        base.OnValidate();
        PlatformerUtilities.NotNegative(ref _MinimumVerticalSpeed);
        PlatformerUtilities.Clamp(ref _MovementSpeedMultiplier, 0, 1);

        if (GetComponent<Collider2D>() == null)
            Debug.LogWarning($"{nameof(LandState)} must be on the same {nameof(GameObject)} as a {nameof(Collider2D)}" +
                $" so that it can receive {nameof(OnCollisionEnter2D)} messages.", this);
    }
#endif

Initialization

On startup, it sets the animation's End Event to enter the character's default state (the Character.Idle state) and also registers its OnGroundedChanged method to be called when the Character Body changes from grounded to airborne or vide versa:

    private void Awake()
    {
        _Animation.Events.OnEnd += Character.StateMachine.ForceSetDefaultState;

        Character.Body.OnGroundedChanged += OnGroundedChanged;
    }

State Entry

Entering this state is a two step process:

  1. First, the OnCollisionEnter2D message must be received with a sufficiently fast downward speed (so that tiny drops don't cause the Landing animation to play). When that happens, store the amount of time that has passed since the scene was loaded:
    private float _GroundContactTime = float.NaN;

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.relativeVelocity.y >= _MinimumVerticalSpeed)
            _GroundContactTime = Time.timeSinceLevelLoad;
    }
  1. Then, if the Character Body becomes grounded within one FixedUpdate of the last valid collision the character enters this state.
    private void OnGroundedChanged(bool isGrounded)
    {
        if (isGrounded &&
            _GroundContactTime >= Time.timeSinceLevelLoad - Time.fixedDeltaTime * 1.5f)
            Character.StateMachine.TrySetState(this);
    }

Entering this state simply plays the animation:

    public override void OnEnterState()
    {
        base.OnEnterState();
        Character.Animancer.Play(_Animation);
    }
}