Wall Jump

This state Inherits from the base HoldJumpState and changes its entry conditions so that it can be used while in the air next to a wall as well as applying the jump force away from that wall instead of just upwards.

public class WallJumpState : HoldJumpState
    [SerializeField, Meters]
    [Tooltip("The wall detection range")]
    private float _DetectionDistance = 0.2f;
    public float DetectionDistance => _DetectionDistance;

    [Tooltip("The amount of horizontal force applied (relative to the vertical force)")]
    private float _HorizontalMultiplier = 1;
    public float HorizontalMultiplier => _HorizontalMultiplier;

    private Vector2 _WallNormal;
    protected override void OnValidate()
        PlatformerUtilities.NotNegative(ref _DetectionDistance);
        PlatformerUtilities.NotNegative(ref _HorizontalMultiplier);
    public override bool CanEnterState
            if (Character.Body.IsGrounded ||
                _DetectionDistance <= 0)
                return false;

            // Check in the direction you are facing first.

            var direction = new Vector2(Character.Animancer.FacingX, 0);
            if (CheckForWallJump(direction))
                return true;

            // If that failed, check the opposite direction.

            if (CheckForWallJump(-direction))
                return true;

            return false;
    private bool CheckForWallJump(Vector2 direction)
        var bounds = Character.Body.Collider.bounds;
        var layers = Character.Body.TerrainFilter.layerMask;
        var count = Physics2D.BoxCastNonAlloc(
            bounds.center, bounds.size, Character.Body.Rotation, direction,
            PlatformerUtilities.OneRaycastHit, _DetectionDistance, layers);

        if (count > 0)
            _WallNormal = -direction;
            return true;
        else return false;
    public override void OnEnterState()
        Character.Body.IsGrounded = true;
        Character.Body.IsGrounded = false;
        _WallNormal = default;
    public override bool CanExitState => Animation.State.IsPlaying;
    public override Vector2 CalculateJumpVelocity()
        AnimancerUtilities.Assert(_HorizontalMultiplier == 0 || _WallNormal != default,
            $"{nameof(WallJumpState)} can't calculate the jump velocity without the wall normal." +
            $" This likely means it was forced to enter without checking {nameof(CanEnterState)}");

        if (Character.Brain.MovementDirection.x == 0)
            Character.Animancer.Facing = _WallNormal;

        var speed = CalculateJumpSpeed(Height);

        var velocity = Character.Body.Velocity;
        velocity.x = 0;
        velocity.y *= Inertia;
        velocity.y += speed;
        velocity += _WallNormal * (speed * _HorizontalMultiplier);
        return velocity;