Wall Slide

This state can be entered while a character is falling and touching a wall to grab onto it and slow their fall. The Player has it as the first state in their Idle Multi-State so that any time they could be playing one of their standard Locomotion animations (Idle, Walk, Run, or Fall) it will first check if they should be in this state instead.


The field tooltips explain what they each do:

public class WallSlideState : CharacterState
    private ClipTransition _Animation;

    [Range(0, 90)]
    [Tooltip("The maximum angle allowed between horizontal and a contact normal for it to be considered a wall")]
    private float _Angle = 40;

    [Tooltip("The amount of friction used while moving normally")]
    private float _Friction = 8;

    [Tooltip("The amount of friction used while attempting to run")]
    private float _RunFriction = 12;

The OnValidate ensures that the fields always have valid values:

    protected override void OnValidate()
        PlatformerUtilities.Clamp(ref _Angle, 0, 90);
        PlatformerUtilities.NotNegative(ref _Friction);
        PlatformerUtilities.NotNegative(ref _RunFriction);

Entry Conditions

When attempting to enter this state, most of the requirements can be checked easily to prevent entry:

    public override bool CanEnterState
            if (Character.Brain.MovementDirection.x == 0 ||
                Character.Body.IsGrounded ||
                Character.Body.Velocity.y > 0)
                return false;

Otherwise the character must be trying to move and falling so it just needs to check if they are touching a wall in the direction they are trying to move. To do that, it gets the ContactFilter2D used by the Character Body and adds angle limits to ensure that the contact normal is within the threshold specified by the _Angle field:

            var filter = Character.Body.TerrainFilter;
            var angle = Character.Brain.MovementDirection.x > 0 ? 180 : 0;
            filter.SetNormalAngle(angle - _Angle, angle + _Angle);

Then it simply gets the contacts which meet that filter. It doesn't actually do anything with the contact details, it just checks that at least one met the criteria:

            var count = Character.Body.Rigidbody.GetContacts(filter, PlatformerUtilities.OneContact);
            return count > 0;

State Execution

When entering this state it simply plays the animation and applies the first FixedUpdate immediately (because it wouldn't normally get called on the same frame this component is enabled):

    public override void OnEnterState()

Every frame in this state it simply determines which friction value to use depending on whether the Brain wants to run or not then uses that value to slow down the character's vertical velocity:

    private void FixedUpdate()
        var velocity = Character.Body.Velocity;
        var friction = Character.Brain.Run ? _RunFriction : _Friction;
        velocity.y *= 1 - Math.Min(friction * Time.deltaTime, 1);
        Character.Body.Velocity = velocity;

And while in this state the character can still move normally to either stay touching the wall or move away from it:

    public override float MovementSpeedMultiplier => 1;