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:
- 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;
}
- 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);
}
}