Player controller
unknown
csharp
7 months ago
8.5 kB
25
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