Player controller

 avatar
unknown
csharp
4 days ago
8.5 kB
13
Indexable
using System;
using System.Collections;
using UnityEngine;

public class PlayerController : MonoBehaviour, IPlayerController
{
    [SerializeField] private ScriptablePlayerMovement stats;
    [SerializeField] private InputManager inputManager;

    private Rigidbody2D _rb;
    private CapsuleCollider2D _capsuleCollider;
    private LayerMask _playerLayer;
    private Vector2 _velocity;
    private FrameInput _frameInput;
    private RaycastHit2D _hitGround;

    public Vector2 moveInput => _frameInput.Move; // Property to get current frame input
    public event Action<bool, float> GroundedChanged;
    public event Action Jumped;
    public event Action<bool, bool> IsMoving; // Event for movement state changes

    private float _time;
    [SerializeField] 
    public bool isInFrontCase { private get; set; }

    public static PlayerController Instance;

    private void Awake()
    {
        if (Instance != null)
        {
            Debug.LogWarning("There is more than one instance of PlayerMovement");
            return;
        }
        Instance = this;

        _rb = GetComponent<Rigidbody2D>();
        _playerLayer = gameObject.layer;
        _capsuleCollider = GetComponent<CapsuleCollider2D>();
    }

    private void Update()
    {
        _time += Time.deltaTime;
        ManagerInput();
    }

    private void ManagerInput()
    {
        _frameInput = new FrameInput
        {
            JumpDown = inputManager.JumpDown,
            JumpHeld = inputManager.JumpHeld,
            Move = inputManager.MoveInput,
            RunInput = inputManager.RunInput
        };

        if (_frameInput.JumpDown)
        {
            _jumpToConsume = true;
            _timeJumpWasPressed = _time;
        }
    }

    private void FixedUpdate()
    {
        CheckCollisions();
        HandleJump();
        HandleDirection();
        HandleGravity();

        ApplyMovement();
    }

    #region Collision

    private bool _isGrounded = false;
    private float _frameLeftGrounded = float.MinValue; //Get the minimum value to be sure that under the frame
    private bool _stopSliding = false; // Flag to indicate if sliding should be stopped

    private void CheckCollisions()
    {
        //~ _playerLayer to detect all other colliders except the player and default
        _hitGround = Physics2D.CapsuleCast(_capsuleCollider.bounds.center, _capsuleCollider.bounds.size, CapsuleDirection2D.Vertical, 0, Vector2.down, stats.grounderDistance, ~_playerLayer);
        bool ceilingHit = Physics2D.CapsuleCast(_capsuleCollider.bounds.center, _capsuleCollider.bounds.size, CapsuleDirection2D.Vertical, 0, Vector2.up, stats.grounderDistance, ~_playerLayer);

        _isGrounded = _hitGround;

        if (ceilingHit) _velocity.y = Mathf.Min(0, _velocity.y); //If velocity is positive, set it to 0, otherwise keep it

        if (_isGrounded)
        {
            _stopSliding = Vector2.Angle(_hitGround.normal, Vector2.up) < stats.maxSlopeAngle;
            _coyoteUsable = true;
            _endedJumpEarly = false;
            GroundedChanged?.Invoke(true, Mathf.Abs(_velocity.y));
        }
        else
        {
            _stopSliding = false;
            _frameLeftGrounded = _time;
            GroundedChanged?.Invoke(false, 0);
        }
    }

    #endregion

    #region Jump

    private bool _jumpToConsume; // Flag to indicate if jump should be consumed
    private bool _endedJumpEarly; // Flag to indicate if jump ended early
    private bool _coyoteUsable; // Flag to indicate if coyote time is usable
    private float _timeJumpWasPressed; // Time when jump was pressed
    private bool _jumpHeld;

    private bool CanUseCoyote => _coyoteUsable && !_isGrounded && (_time < (_frameLeftGrounded + stats.coyoteTime)); // Check if coyote time can be used

    private void HandleJump()
    {
        if(!_endedJumpEarly && _isGrounded && _jumpHeld && _rb.linearVelocity.y >0 ) _endedJumpEarly = true;

        if(!_jumpToConsume) return;

        if (_isGrounded || CanUseCoyote) ExecuteJump();

        _jumpToConsume = false;
    }

    private void ExecuteJump()
    {
        _endedJumpEarly = false;
        _timeJumpWasPressed = 0;
        _coyoteUsable = false;
        _velocity.y = stats.jumpForce; // Set the vertical velocity to jump power
        Jumped?.Invoke(); // Invoke the jump event
    }

    #endregion

    #region Movement

    [HideInInspector] public bool isOnLadder;
    [HideInInspector] public bool isClimbing;

    private void ClimbLadder()
    {
        IsMoving?.Invoke(false, false);
        _velocity.x = 0;

        if (_frameInput.Move.y > 0)
        {
            _velocity.y = stats.ladderSpeed;
        }
    }

    private void Climbing()
    {
        //TODO: Detect the top of the collider and move the bottom of the player at this position
        //TODO: Stop the climbing after the end of the animation
        transform.localPosition = new Vector2(transform.localPosition.x, transform.localPosition.y + stats.climbingHeight);
        isClimbing = false;
    }

    private void HandleDirection()
    {
        if (isOnLadder)
        {
            ClimbLadder();
            return;
        }

        if (isClimbing && !isOnLadder)
        {
            Climbing();
            return;
        }

        Move();
    }

    private void Move()
    {
        // TODO: Make velocity of the player to 0 when the player change direction (Use Vector2.Dot)

        if(_frameInput.Move.x == 0)
        {
            // if (_stopSliding)
            // {
            //     Debug.Log("Stop sliding");
            //     _velocity.x = 0;
            // }
            // else
            // {
            //     float deceleration = _isGrounded ? stats.groundDeceleration : stats.airDeceleration;
            //     _velocity.x = Mathf.MoveTowards(_velocity.x, 0, deceleration * Time.fixedDeltaTime); // Decelerate to zero depending on the state
            // }
            float deceleration = _isGrounded ? stats.groundDeceleration : stats.airDeceleration;
            _velocity.x = Mathf.MoveTowards(_velocity.x, 0, deceleration * Time.fixedDeltaTime); // Decelerate to zero depending on the state
            IsMoving?.Invoke(false, false); // Invoke movement event
        }
        else
        {
            float speedToReach;
            bool isRunning = false;
            if (_frameInput.RunInput)
            {
                speedToReach = stats.maxSpeedRun;
                isRunning = true;
            }
            else
            {
                speedToReach = stats.maxSpeedWalk;
                isRunning = false;
            }
            //Know if the player is running or walking

            //Move the initial velocity to the max speed with acceleration
            _velocity.x = Mathf.MoveTowards(_velocity.x, _frameInput.Move.x * speedToReach,
                stats.acceleration * Time.fixedDeltaTime);
            IsMoving?.Invoke(true, isRunning); // Invoke movement event
        }
    }

    #endregion

    #region Gravity

    private void HandleGravity()
    {
        if(!_isGrounded)
        {
            //Can use *Time.deltaTime to make it gradual
            float inAirGravity = stats.fallAcceleration;
            if(_endedJumpEarly && _velocity.y > 0) inAirGravity *= stats.jumpEndEarlyGravityModifier; // Apply early jump gravity modifier
            //Apply fall speed limit
            //inAirGravity is the maximum delta that can be reach between the current and the target velocity during one frame
            _velocity.y = Mathf.MoveTowards(_velocity.y, -stats.maxFallSpeed, inAirGravity * Time.fixedDeltaTime);
        }
    }

    #endregion

    private void ApplyMovement() => _rb.linearVelocity = _velocity;

}

public struct FrameInput // Struct to hold frame input data
{
    public bool JumpDown; // Indicates if jump button was pressed
    public bool JumpHeld; // Indicates if jump button is held
    public bool RunInput;
    public Vector2 Move; // Vector2 for movement input
}

public interface IPlayerController
{
    public event Action<bool, float> GroundedChanged; // Event for grounded state changes
    public event Action Jumped; // Event for jump action
    public Vector2 moveInput { get; } // Property to get current frame input
    //First bool isMoving, second bool isRunning
    public event Action<bool, bool> IsMoving;
    // public event Action<bool> isClimbing; 
    // public event Action<bool> isOnLadder; 
}
Editor is loading...
Leave a Comment