Commit 1
This commit is contained in:
89
Assets/Scripts/Player Controller/InputReader.cs
Normal file
89
Assets/Scripts/Player Controller/InputReader.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class InputReader : MonoBehaviour
|
||||
{
|
||||
// Continuous Inputs
|
||||
public virtual Vector2 MoveInput { get; protected set; }
|
||||
public virtual Vector2 LookInput { get; protected set; }
|
||||
public virtual Vector2 ScrollInput { get; protected set; }
|
||||
public virtual bool IsSprintHeld { get; protected set; } // Left Shift
|
||||
public virtual bool IsAttackHeld { get; protected set; } // Left Mouse Button
|
||||
|
||||
// One-shot Events
|
||||
public event Action OnJumpEvent; // Space
|
||||
public event Action OnDodgeEvent; // Right Mouse Button (RMB)
|
||||
public event Action OnAttackEvent; // Left Mouse Button (LMB)
|
||||
public event Action OnCrouchEvent; // C Key
|
||||
public event Action OnInteractEvent; // E Key
|
||||
public event Action OnNextInteractEvent; // R Key
|
||||
public event Action OnPreviousInteractEvent; // Q Key
|
||||
|
||||
public void OnAttack(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed)
|
||||
{
|
||||
OnAttackEvent?.Invoke();
|
||||
IsAttackHeld = true;
|
||||
}
|
||||
if (context.canceled)
|
||||
{
|
||||
IsAttackHeld = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnMove(InputAction.CallbackContext context)
|
||||
{
|
||||
MoveInput = context.ReadValue<Vector2>();
|
||||
}
|
||||
|
||||
public void OnLook(InputAction.CallbackContext context)
|
||||
{
|
||||
LookInput = context.ReadValue<Vector2>();
|
||||
}
|
||||
|
||||
public void OnScroll(InputAction.CallbackContext context)
|
||||
{
|
||||
ScrollInput = context.ReadValue<Vector2>();
|
||||
}
|
||||
|
||||
public void OnSprint(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed) IsSprintHeld = true;
|
||||
if (context.canceled) IsSprintHeld = false;
|
||||
}
|
||||
|
||||
public void OnJump(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed) OnJumpEvent?.Invoke();
|
||||
}
|
||||
|
||||
public void OnDodgeOrThrust(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed) OnDodgeEvent?.Invoke();
|
||||
}
|
||||
|
||||
public void OnCrouch(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed) OnCrouchEvent?.Invoke();
|
||||
}
|
||||
|
||||
public void OnInteract(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed) OnInteractEvent?.Invoke();
|
||||
}
|
||||
|
||||
public void OnNext(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed) OnNextInteractEvent?.Invoke();
|
||||
}
|
||||
|
||||
public void OnPrevious(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed) OnPreviousInteractEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/InputReader.cs.meta
Normal file
2
Assets/Scripts/Player Controller/InputReader.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5962d8f2c8e40e240a4a4907c7b539fa
|
||||
12
Assets/Scripts/Player Controller/ParkourAction.cs
Normal file
12
Assets/Scripts/Player Controller/ParkourAction.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Game_Definition
|
||||
{
|
||||
[CreateAssetMenu(fileName = "ParkourAction", menuName = "Parkour System/New Parkour Action")]
|
||||
public class ParkourAction : ScriptableObject
|
||||
{
|
||||
[SerializeField] string animationName;
|
||||
[SerializeField] private float minHeight;
|
||||
[SerializeField] private float maxHeight;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/ParkourAction.cs.meta
Normal file
2
Assets/Scripts/Player Controller/ParkourAction.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 985468dedaf32f44191b2cc29c813c8c
|
||||
59
Assets/Scripts/Player Controller/ParkourController.cs
Normal file
59
Assets/Scripts/Player Controller/ParkourController.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class ParkourController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private InputReader inputReader;
|
||||
EnvironmentScanner environmentScanner;
|
||||
Animator animator;
|
||||
PlayerController playerController;
|
||||
|
||||
bool inAction;
|
||||
private void Awake()
|
||||
{
|
||||
inputReader = GetComponent<InputReader>();
|
||||
environmentScanner = GetComponent<EnvironmentScanner>();
|
||||
animator = GetComponent<Animator>();
|
||||
playerController = GetComponent<PlayerController>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
inputReader.OnJumpEvent += HandleParkour;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
inputReader.OnJumpEvent -= HandleParkour;
|
||||
}
|
||||
|
||||
private void HandleParkour()
|
||||
{
|
||||
var hitData = environmentScanner.ObstacleCheck();
|
||||
|
||||
if (hitData.forwardHitFound)
|
||||
{
|
||||
StartCoroutine(DoParkourAction());
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator DoParkourAction()
|
||||
{
|
||||
inAction = true;
|
||||
playerController.SetControl(false);
|
||||
|
||||
animator.CrossFade("Step Up", 0.1f);
|
||||
yield return null;
|
||||
|
||||
var animationState = animator.GetNextAnimatorStateInfo(0);
|
||||
|
||||
yield return new WaitForSeconds(animationState.length);
|
||||
|
||||
playerController.SetControl(true);
|
||||
inAction = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0407ea3fbe445ac43b4f1ca3077ce283
|
||||
58
Assets/Scripts/Player Controller/PlayerAirDashState.cs
Normal file
58
Assets/Scripts/Player Controller/PlayerAirDashState.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerAirDashState : PlayerBaseState
|
||||
{
|
||||
private readonly int airDashHash = Animator.StringToHash("AirDash");
|
||||
private float dashDuration = 0.2f;
|
||||
private float dashTimer;
|
||||
private Vector3 dashDirection;
|
||||
|
||||
public PlayerAirDashState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
dashTimer = dashDuration;
|
||||
stateMachine.Anim.SetTrigger(airDashHash);
|
||||
|
||||
// Reset vertical velocity so we don't carry falling momentum into the dash
|
||||
stateMachine.VelocityY = 0f;
|
||||
|
||||
// Determine dash direction
|
||||
Vector2 input = stateMachine.Input.MoveInput;
|
||||
if (input != Vector2.zero)
|
||||
{
|
||||
dashDirection = new Vector3(input.x, 0f, input.y).normalized;
|
||||
|
||||
if (stateMachine.Cam != null)
|
||||
{
|
||||
dashDirection = stateMachine.Cam.PlanarRotation * dashDirection;
|
||||
}
|
||||
|
||||
stateMachine.transform.rotation = Quaternion.LookRotation(dashDirection);
|
||||
}
|
||||
else
|
||||
{
|
||||
dashDirection = stateMachine.transform.forward;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
dashTimer -= deltaTime;
|
||||
|
||||
// Move horizontally, ignoring gravity for this brief moment
|
||||
stateMachine.Controller.Move(dashDirection * stateMachine.DashForce * deltaTime);
|
||||
|
||||
// When the air dash ends, return to falling
|
||||
if (dashTimer <= 0f)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerFallState(stateMachine));
|
||||
}
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
public override void Exit() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43c3e5e400fea604fb432f84d5dd9ce1
|
||||
25
Assets/Scripts/Player Controller/PlayerBaseState.cs
Normal file
25
Assets/Scripts/Player Controller/PlayerBaseState.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public abstract class PlayerBaseState
|
||||
{
|
||||
protected PlayerStateMachine stateMachine;
|
||||
|
||||
// Constructor to pass the state machine reference
|
||||
public PlayerBaseState(PlayerStateMachine stateMachine)
|
||||
{
|
||||
this.stateMachine = stateMachine;
|
||||
}
|
||||
|
||||
// Called once when entering the state
|
||||
public abstract void Enter();
|
||||
|
||||
// Called every frame (equivalent to Update)
|
||||
public abstract void Tick(float deltaTime);
|
||||
|
||||
// Called every physics frame (equivalent to FixedUpdate)
|
||||
public abstract void PhysicsTick(float fixedDeltaTime);
|
||||
|
||||
// Called once before switching to a new state
|
||||
public abstract void Exit();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/PlayerBaseState.cs.meta
Normal file
2
Assets/Scripts/Player Controller/PlayerBaseState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af622221f1750284885366b02b8bfbba
|
||||
134
Assets/Scripts/Player Controller/PlayerController.cs
Normal file
134
Assets/Scripts/Player Controller/PlayerController.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerController : MonoBehaviour
|
||||
{
|
||||
private static readonly int MoveAmount = Animator.StringToHash("moveAmount");
|
||||
|
||||
[SerializeField] private InputReader inputReader;
|
||||
[SerializeField] private float rotationSpeed = 500f;
|
||||
[SerializeField] private float moveSpeed = 10f;
|
||||
[SerializeField] private float jumpHeight = 2f;
|
||||
[SerializeField] private float animationDamping = 0.2f;
|
||||
[SerializeField] private float groundCheckRadius = 0.2f;
|
||||
[SerializeField] private Vector3 groundCheckOffset;
|
||||
[SerializeField] private LayerMask groundMask;
|
||||
|
||||
CameraController cameraController;
|
||||
Animator animator;
|
||||
private CharacterController characterController;
|
||||
|
||||
Quaternion targetRotation;
|
||||
private float horizontal;
|
||||
private float vertical;
|
||||
bool isGrounded;
|
||||
private bool wasGrounded;
|
||||
private bool hasControl = true;
|
||||
private float ySpeed;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Camera.main != null) cameraController = Camera.main.GetComponent<CameraController>();
|
||||
animator = GetComponent<Animator>();
|
||||
characterController = GetComponent<CharacterController>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
inputReader.OnJumpEvent += HandleJump;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
inputReader.OnJumpEvent -= HandleJump;
|
||||
}
|
||||
|
||||
private void HandleJump()
|
||||
{
|
||||
if (isGrounded && hasControl)
|
||||
{
|
||||
// Công thức tính vận tốc nhảy: v = sqrt(h * -2 * g)
|
||||
ySpeed = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
horizontal = inputReader.MoveInput.x;
|
||||
vertical = inputReader.MoveInput.y;
|
||||
float moveAmount = Mathf.Clamp01(Math.Abs(horizontal) + Math.Abs(vertical));
|
||||
|
||||
var moveInput = (new Vector3(horizontal, 0, vertical)).normalized;
|
||||
|
||||
var moveDirection = cameraController.PlanarRotation * moveInput;
|
||||
|
||||
if (!hasControl)
|
||||
return;
|
||||
|
||||
wasGrounded = isGrounded;
|
||||
GroundCheck();
|
||||
|
||||
// Phát hiện tiếp đất (Landing)
|
||||
if (isGrounded && !wasGrounded && ySpeed < -1f)
|
||||
{
|
||||
// Rung camera khi tiếp đất mạnh
|
||||
if (cameraController != null)
|
||||
{
|
||||
cameraController.Shake(0.2f, 0.15f);
|
||||
}
|
||||
}
|
||||
|
||||
if (isGrounded && ySpeed < 0)
|
||||
{
|
||||
ySpeed = -2f; // Giữ nhân vật dính xuống mặt đất
|
||||
}
|
||||
else
|
||||
{
|
||||
ySpeed += Physics.gravity.y * Time.deltaTime;
|
||||
}
|
||||
|
||||
var velocity = moveDirection * moveSpeed;
|
||||
velocity.y = ySpeed;
|
||||
|
||||
characterController.Move(velocity * Time.deltaTime);
|
||||
|
||||
if (moveAmount > 0)
|
||||
{
|
||||
targetRotation = Quaternion.LookRotation(moveDirection);
|
||||
|
||||
}
|
||||
|
||||
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation,
|
||||
Time.deltaTime * rotationSpeed);
|
||||
|
||||
animator.SetFloat(MoveAmount, moveAmount, animationDamping, Time.deltaTime);
|
||||
}
|
||||
|
||||
void GroundCheck()
|
||||
{
|
||||
isGrounded =
|
||||
Physics.CheckSphere(transform.TransformPoint(groundCheckOffset), groundCheckRadius, groundMask);
|
||||
}
|
||||
|
||||
public void SetControl(bool control)
|
||||
{
|
||||
this.hasControl = control;
|
||||
characterController.enabled = hasControl;
|
||||
|
||||
if (!hasControl)
|
||||
{
|
||||
animator.SetFloat(MoveAmount, 0f);
|
||||
targetRotation = transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = new Color(0, 1, 0, 0.5f);
|
||||
Gizmos.DrawSphere(transform.TransformPoint(groundCheckOffset), groundCheckRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd897e8bcaabdc8408bb6aaf7c037537
|
||||
97
Assets/Scripts/Player Controller/PlayerCrouchState.cs
Normal file
97
Assets/Scripts/Player Controller/PlayerCrouchState.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerCrouchState : PlayerBaseState
|
||||
{
|
||||
private readonly int isCrouchingHash = Animator.StringToHash("IsCrouching");
|
||||
private readonly int crouchSpeedHash = Animator.StringToHash("CrouchSpeed");
|
||||
private const float AnimatorDampTime = 0.1f;
|
||||
|
||||
public PlayerCrouchState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
stateMachine.Anim.SetBool(isCrouchingHash, true);
|
||||
stateMachine.Input.OnCrouchEvent += OnCrouchToggle;
|
||||
stateMachine.Input.OnDodgeEvent += OnDodge;
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
Vector2 moveInput = stateMachine.Input.MoveInput;
|
||||
float currentSpeed = 0f;
|
||||
float animValue = 0f;
|
||||
|
||||
if (moveInput == Vector2.zero)
|
||||
{
|
||||
animValue = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stateMachine.Input.IsSprintHeld)
|
||||
{
|
||||
currentSpeed = stateMachine.SneakSpeed;
|
||||
animValue = 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSpeed = stateMachine.WalkSpeed;
|
||||
animValue = 1.0f;
|
||||
}
|
||||
|
||||
Vector3 moveDirection = new Vector3(moveInput.x, 0f, moveInput.y).normalized;
|
||||
|
||||
if (stateMachine.Cam != null)
|
||||
{
|
||||
moveDirection = stateMachine.Cam.PlanarRotation * moveDirection;
|
||||
}
|
||||
|
||||
// Apply horizontal movement
|
||||
Vector3 movement = moveDirection * currentSpeed;
|
||||
|
||||
// Apply gravity
|
||||
if (stateMachine.Controller.isGrounded)
|
||||
{
|
||||
stateMachine.VelocityY = -2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
stateMachine.VelocityY += stateMachine.Gravity * deltaTime;
|
||||
}
|
||||
|
||||
movement.y = stateMachine.VelocityY;
|
||||
|
||||
stateMachine.Controller.Move(movement * deltaTime);
|
||||
|
||||
Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
|
||||
stateMachine.transform.rotation = Quaternion.Slerp(
|
||||
stateMachine.transform.rotation,
|
||||
targetRotation,
|
||||
deltaTime * 10f
|
||||
);
|
||||
}
|
||||
|
||||
stateMachine.Anim.SetFloat(crouchSpeedHash, animValue, AnimatorDampTime, deltaTime);
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
stateMachine.Anim.SetBool(isCrouchingHash, false);
|
||||
stateMachine.Input.OnCrouchEvent -= OnCrouchToggle;
|
||||
stateMachine.Input.OnDodgeEvent -= OnDodge;
|
||||
}
|
||||
|
||||
private void OnCrouchToggle()
|
||||
{
|
||||
if (stateMachine.Input.MoveInput == Vector2.zero)
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
else
|
||||
stateMachine.SwitchState(new PlayerMoveState(stateMachine));
|
||||
}
|
||||
|
||||
private void OnDodge() => stateMachine.SwitchState(new PlayerDodgeState(stateMachine));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82b04241d720e444a937973caed8eb42
|
||||
86
Assets/Scripts/Player Controller/PlayerDashState.cs
Normal file
86
Assets/Scripts/Player Controller/PlayerDashState.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerDashState : PlayerBaseState
|
||||
{
|
||||
private readonly int dashHash = Animator.StringToHash("Dash"); // Trigger parameter
|
||||
private float dashDuration = 0.25f; // How long the burst lasts (tweak as needed)
|
||||
private float dashTimer;
|
||||
private Vector3 dashDirection;
|
||||
|
||||
public PlayerDashState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
dashTimer = dashDuration;
|
||||
|
||||
// Fire the Dash animation trigger
|
||||
stateMachine.Anim.SetTrigger(dashHash);
|
||||
|
||||
stateMachine.Input.OnJumpEvent += OnJump;
|
||||
|
||||
// Determine dash direction based on input, or default to forward if no input
|
||||
Vector2 input = stateMachine.Input.MoveInput;
|
||||
if (input != Vector2.zero)
|
||||
{
|
||||
dashDirection = new Vector3(input.x, 0f, input.y).normalized;
|
||||
|
||||
if (stateMachine.Cam != null)
|
||||
{
|
||||
dashDirection = stateMachine.Cam.PlanarRotation * dashDirection;
|
||||
}
|
||||
|
||||
// Instantly snap rotation to face the dash direction
|
||||
stateMachine.transform.rotation = Quaternion.LookRotation(dashDirection);
|
||||
}
|
||||
else
|
||||
{
|
||||
dashDirection = stateMachine.transform.forward;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
dashTimer -= deltaTime;
|
||||
|
||||
// Apply high speed for the dash (Burst speed)
|
||||
stateMachine.Controller.Move(dashDirection * stateMachine.SprintSpeed * deltaTime);
|
||||
|
||||
// When the dash finishes, decide the next state
|
||||
if (dashTimer <= 0f)
|
||||
{
|
||||
if (stateMachine.Input.IsSprintHeld && stateMachine.Input.MoveInput != Vector2.zero)
|
||||
{
|
||||
// Kept holding Shift -> Go to Sprint
|
||||
stateMachine.SwitchState(new PlayerRunState(stateMachine));
|
||||
}
|
||||
else if (stateMachine.Input.MoveInput != Vector2.zero)
|
||||
{
|
||||
// Released Shift but still moving -> Go to Walk
|
||||
stateMachine.SwitchState(new PlayerMoveState(stateMachine));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stopped moving -> Go to Idle
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
stateMachine.Input.OnJumpEvent -= OnJump;
|
||||
}
|
||||
|
||||
private void OnJump()
|
||||
{
|
||||
if (stateMachine.IsGrounded)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerJumpState(stateMachine, stateMachine.SprintSpeed));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/PlayerDashState.cs.meta
Normal file
2
Assets/Scripts/Player Controller/PlayerDashState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03721c0a319be8c4093b616e2f46afe8
|
||||
65
Assets/Scripts/Player Controller/PlayerDodgeState.cs
Normal file
65
Assets/Scripts/Player Controller/PlayerDodgeState.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerDodgeState : PlayerBaseState
|
||||
{
|
||||
private readonly int dodgeHash = Animator.StringToHash("Dodge");
|
||||
private float dodgeDuration = 0.4f; // Adjust based on your dodge animation length
|
||||
private float dodgeTimer;
|
||||
private Vector3 dodgeDirection;
|
||||
|
||||
public PlayerDodgeState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
dodgeTimer = dodgeDuration;
|
||||
stateMachine.Anim.SetTrigger(dodgeHash);
|
||||
|
||||
// Calculate dodge direction based on current input
|
||||
Vector2 input = stateMachine.Input.MoveInput;
|
||||
if (input != Vector2.zero)
|
||||
{
|
||||
// Dodge in the input direction (Left, Right, Back, Forward)
|
||||
dodgeDirection = new Vector3(input.x, 0f, input.y).normalized;
|
||||
|
||||
if (stateMachine.Cam != null)
|
||||
{
|
||||
dodgeDirection = stateMachine.Cam.PlanarRotation * dodgeDirection;
|
||||
}
|
||||
|
||||
// Instantly rotate the player to face the dodge direction
|
||||
stateMachine.transform.rotation = Quaternion.LookRotation(dodgeDirection);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no input, just roll straight forward
|
||||
dodgeDirection = stateMachine.transform.forward;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
dodgeTimer -= deltaTime;
|
||||
|
||||
// Apply movement force for the dodge (usually slightly slower than a Dash)
|
||||
stateMachine.Controller.Move(dodgeDirection * (stateMachine.DashForce * 0.8f) * deltaTime);
|
||||
|
||||
// Once the roll finishes, transition back to Idle or Move
|
||||
if (dodgeTimer <= 0f)
|
||||
{
|
||||
if (stateMachine.Input.MoveInput != Vector2.zero)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerMoveState(stateMachine));
|
||||
}
|
||||
else
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
public override void Exit() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71bce3b0373df774c8b1598487bcd4ee
|
||||
82
Assets/Scripts/Player Controller/PlayerFallState.cs
Normal file
82
Assets/Scripts/Player Controller/PlayerFallState.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerFallState : PlayerBaseState
|
||||
{
|
||||
private readonly int fallHash = Animator.StringToHash("Fall");
|
||||
private float fallSpeed;
|
||||
|
||||
public PlayerFallState(PlayerStateMachine stateMachine, float fallSpeed = -1f) : base(stateMachine)
|
||||
{
|
||||
if (fallSpeed < 0)
|
||||
{
|
||||
this.fallSpeed = stateMachine.WalkSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.fallSpeed = fallSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
stateMachine.Anim.SetTrigger(fallHash);
|
||||
stateMachine.Input.OnDodgeEvent += OnThrustPressed;
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
stateMachine.VelocityY += Physics.gravity.y * deltaTime;
|
||||
|
||||
Vector2 input = stateMachine.Input.MoveInput;
|
||||
Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized;
|
||||
Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir;
|
||||
|
||||
Vector3 velocity = moveDirection * fallSpeed;
|
||||
velocity.y = stateMachine.VelocityY;
|
||||
|
||||
stateMachine.Controller.Move(velocity * deltaTime);
|
||||
|
||||
if (stateMachine.Input.IsSprintHeld)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerAirDashState(stateMachine));
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateMachine.IsGrounded)
|
||||
{
|
||||
// Landing Shake from PlayerController.cs
|
||||
if (!stateMachine.WasGrounded && stateMachine.VelocityY < -1f)
|
||||
{
|
||||
if (stateMachine.Cam != null)
|
||||
{
|
||||
stateMachine.Cam.Shake(0.2f, 0.15f);
|
||||
}
|
||||
}
|
||||
|
||||
stateMachine.VelocityY = -2f;
|
||||
|
||||
if (input == Vector2.zero)
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
else
|
||||
{
|
||||
// Return to the appropriate movement state based on sprint input
|
||||
if (stateMachine.Input.IsSprintHeld)
|
||||
stateMachine.SwitchState(new PlayerRunState(stateMachine));
|
||||
else
|
||||
stateMachine.SwitchState(new PlayerMoveState(stateMachine));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnThrustPressed() => stateMachine.SwitchState(new PlayerThrustState(stateMachine));
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
stateMachine.Input.OnDodgeEvent -= OnThrustPressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/PlayerFallState.cs.meta
Normal file
2
Assets/Scripts/Player Controller/PlayerFallState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2b239715e32b6a4791f677e0159b862
|
||||
71
Assets/Scripts/Player Controller/PlayerIdleState.cs
Normal file
71
Assets/Scripts/Player Controller/PlayerIdleState.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerIdleState : PlayerBaseState
|
||||
{
|
||||
private readonly int speedHash = Animator.StringToHash("Speed");
|
||||
|
||||
public PlayerIdleState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
stateMachine.Input.OnJumpEvent += OnJump;
|
||||
stateMachine.Input.OnDodgeEvent += OnDodge;
|
||||
stateMachine.Input.OnCrouchEvent += OnCrouch;
|
||||
stateMachine.Input.OnInteractEvent += OnInteract;
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
stateMachine.Anim.SetFloat(speedHash, 0f, stateMachine.AnimationDamping, deltaTime);
|
||||
|
||||
if (stateMachine.Input.MoveInput != Vector2.zero)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerMoveState(stateMachine));
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateMachine.IsGrounded && stateMachine.VelocityY < 0)
|
||||
{
|
||||
stateMachine.VelocityY = -2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
stateMachine.VelocityY += Physics.gravity.y * deltaTime;
|
||||
}
|
||||
|
||||
stateMachine.Controller.Move(new Vector3(0, stateMachine.VelocityY, 0) * deltaTime);
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
stateMachine.Input.OnJumpEvent -= OnJump;
|
||||
stateMachine.Input.OnDodgeEvent -= OnDodge;
|
||||
stateMachine.Input.OnCrouchEvent -= OnCrouch;
|
||||
stateMachine.Input.OnInteractEvent -= OnInteract;
|
||||
}
|
||||
|
||||
private void OnJump()
|
||||
{
|
||||
if (stateMachine.IsGrounded)
|
||||
{
|
||||
if (stateMachine.Scanner != null)
|
||||
{
|
||||
var hitData = stateMachine.Scanner.ObstacleCheck();
|
||||
if (hitData.forwardHitFound)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerParkourState(stateMachine));
|
||||
return;
|
||||
}
|
||||
}
|
||||
stateMachine.SwitchState(new PlayerJumpState(stateMachine, stateMachine.WalkSpeed));
|
||||
}
|
||||
}
|
||||
private void OnDodge() => stateMachine.SwitchState(new PlayerDodgeState(stateMachine));
|
||||
private void OnCrouch() => stateMachine.SwitchState(new PlayerCrouchState(stateMachine));
|
||||
private void OnInteract() => stateMachine.SwitchState(new PlayerInteractState(stateMachine));
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/PlayerIdleState.cs.meta
Normal file
2
Assets/Scripts/Player Controller/PlayerIdleState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd83e34f11c7aaa4ab144ee59db04e8d
|
||||
38
Assets/Scripts/Player Controller/PlayerInteractState.cs
Normal file
38
Assets/Scripts/Player Controller/PlayerInteractState.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerInteractState : PlayerBaseState
|
||||
{
|
||||
public PlayerInteractState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
// Lấy vật thể đang được chọn (Index hiện tại)
|
||||
IInteractable interactable = stateMachine.GetInteractable();
|
||||
|
||||
if (interactable != null)
|
||||
{
|
||||
Debug.Log($"[Interaction] Interacting with: {interactable.InteractionPrompt}");
|
||||
interactable.OnInteract(stateMachine);
|
||||
|
||||
// Bạn có thể phát animation tương tác ở đây
|
||||
// stateMachine.Anim.CrossFadeInFixedTime("Interact", 0.1f);
|
||||
}
|
||||
|
||||
// Chuyển về trạng thái di chuyển hoặc đứng yên ngay lập tức
|
||||
if (stateMachine.Input.MoveInput == Vector2.zero)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
}
|
||||
else
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerMoveState(stateMachine));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime) { }
|
||||
public override void PhysicsTick(float fixedDeltaTime) { }
|
||||
public override void Exit() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b772d1c0c26c634fad5e71b43db9385
|
||||
52
Assets/Scripts/Player Controller/PlayerJumpState.cs
Normal file
52
Assets/Scripts/Player Controller/PlayerJumpState.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerJumpState : PlayerBaseState
|
||||
{
|
||||
private readonly int jumpHash = Animator.StringToHash("Jump");
|
||||
private float jumpSpeed;
|
||||
|
||||
public PlayerJumpState(PlayerStateMachine stateMachine, float jumpSpeed = -1f) : base(stateMachine)
|
||||
{
|
||||
if (jumpSpeed < 0)
|
||||
{
|
||||
this.jumpSpeed = stateMachine.WalkSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.jumpSpeed = jumpSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
stateMachine.Anim.SetTrigger(jumpHash);
|
||||
|
||||
// Physic formula: v = sqrt(h * -2 * g)
|
||||
stateMachine.VelocityY = Mathf.Sqrt(stateMachine.JumpHeight * -2f * Physics.gravity.y);
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
stateMachine.VelocityY += Physics.gravity.y * deltaTime;
|
||||
|
||||
Vector2 input = stateMachine.Input.MoveInput;
|
||||
Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized;
|
||||
Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir;
|
||||
|
||||
Vector3 velocity = moveDirection * jumpSpeed;
|
||||
velocity.y = stateMachine.VelocityY;
|
||||
|
||||
stateMachine.Controller.Move(velocity * deltaTime);
|
||||
|
||||
if (stateMachine.VelocityY <= 0f)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerFallState(stateMachine, jumpSpeed));
|
||||
}
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
public override void Exit() {}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/PlayerJumpState.cs.meta
Normal file
2
Assets/Scripts/Player Controller/PlayerJumpState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ab5bb519df10fe45a5af5f45111ed4d
|
||||
97
Assets/Scripts/Player Controller/PlayerMoveState.cs
Normal file
97
Assets/Scripts/Player Controller/PlayerMoveState.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerMoveState : PlayerBaseState
|
||||
{
|
||||
private readonly int speedHash = Animator.StringToHash("Speed");
|
||||
|
||||
public PlayerMoveState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
stateMachine.Input.OnJumpEvent += OnJump;
|
||||
stateMachine.Input.OnDodgeEvent += OnDodge;
|
||||
stateMachine.Input.OnCrouchEvent += OnCrouch;
|
||||
stateMachine.Input.OnInteractEvent += OnInteract;
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
Vector2 input = stateMachine.Input.MoveInput;
|
||||
float moveAmount = Mathf.Clamp01(Mathf.Abs(input.x) + Mathf.Abs(input.y));
|
||||
|
||||
if (moveAmount <= 0.01f)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateMachine.Input.IsSprintHeld)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerDashState(stateMachine));
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized;
|
||||
Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir;
|
||||
|
||||
Vector3 velocity = moveDirection * stateMachine.RunSpeed;
|
||||
|
||||
if (stateMachine.IsGrounded && stateMachine.VelocityY < 0)
|
||||
{
|
||||
stateMachine.VelocityY = -2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
stateMachine.VelocityY += Physics.gravity.y * deltaTime;
|
||||
}
|
||||
velocity.y = stateMachine.VelocityY;
|
||||
|
||||
stateMachine.Controller.Move(velocity * deltaTime);
|
||||
|
||||
if (moveDirection != Vector3.zero)
|
||||
{
|
||||
Quaternion targetRot = Quaternion.LookRotation(moveDirection);
|
||||
stateMachine.transform.rotation = Quaternion.RotateTowards(
|
||||
stateMachine.transform.rotation,
|
||||
targetRot,
|
||||
stateMachine.RotationSpeed * deltaTime
|
||||
);
|
||||
}
|
||||
|
||||
stateMachine.Anim.SetFloat(speedHash, 0.7f, stateMachine.AnimationDamping, deltaTime);
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
stateMachine.Input.OnJumpEvent -= OnJump;
|
||||
stateMachine.Input.OnDodgeEvent -= OnDodge;
|
||||
stateMachine.Input.OnCrouchEvent -= OnCrouch;
|
||||
stateMachine.Input.OnInteractEvent -= OnInteract;
|
||||
}
|
||||
|
||||
private void OnJump()
|
||||
{
|
||||
if (stateMachine.IsGrounded)
|
||||
{
|
||||
if (stateMachine.Scanner != null)
|
||||
{
|
||||
var hitData = stateMachine.Scanner.ObstacleCheck();
|
||||
if (hitData.forwardHitFound)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerParkourState(stateMachine));
|
||||
return;
|
||||
}
|
||||
}
|
||||
stateMachine.SwitchState(new PlayerJumpState(stateMachine, stateMachine.RunSpeed));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDodge() => stateMachine.SwitchState(new PlayerDodgeState(stateMachine));
|
||||
private void OnCrouch() => stateMachine.SwitchState(new PlayerCrouchState(stateMachine));
|
||||
private void OnInteract() => stateMachine.SwitchState(new PlayerInteractState(stateMachine));
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/PlayerMoveState.cs.meta
Normal file
2
Assets/Scripts/Player Controller/PlayerMoveState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a11b0f0549f3dbd45bbdfd8114586a43
|
||||
53
Assets/Scripts/Player Controller/PlayerParkourState.cs
Normal file
53
Assets/Scripts/Player Controller/PlayerParkourState.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerParkourState : PlayerBaseState
|
||||
{
|
||||
private readonly int parkourHash = Animator.StringToHash("Step Up");
|
||||
private float animationDuration;
|
||||
private float timer;
|
||||
|
||||
public PlayerParkourState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
// Play the Parkour animation (Step Up)
|
||||
stateMachine.Anim.CrossFadeInFixedTime(parkourHash, 0.1f);
|
||||
|
||||
// We'll wait for the animation to finish.
|
||||
// In a real project, you might get the exact duration from the Animator.
|
||||
// For now, we'll assume a fixed duration or check state.
|
||||
timer = 0f;
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
timer += deltaTime;
|
||||
|
||||
// Simple way to wait for animation: check normalized time of the current state
|
||||
var stateInfo = stateMachine.Anim.GetCurrentAnimatorStateInfo(0);
|
||||
if (stateInfo.shortNameHash == parkourHash && stateInfo.normalizedTime >= 1f)
|
||||
{
|
||||
if (stateMachine.Input.MoveInput == Vector2.zero)
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
else
|
||||
stateMachine.SwitchState(new PlayerMoveState(stateMachine));
|
||||
}
|
||||
|
||||
// Safety timeout if animation doesn't play or something
|
||||
if (timer > 2f)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
}
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime)
|
||||
{
|
||||
// Usually during parkour we disable gravity or handle it specially
|
||||
stateMachine.VelocityY = 0;
|
||||
}
|
||||
|
||||
public override void Exit() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9b73ee252f157e49a08f8f7566dc6ac
|
||||
94
Assets/Scripts/Player Controller/PlayerRunState.cs
Normal file
94
Assets/Scripts/Player Controller/PlayerRunState.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerRunState : PlayerBaseState
|
||||
{
|
||||
private readonly int speedHash = Animator.StringToHash("Speed");
|
||||
|
||||
public PlayerRunState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
stateMachine.Input.OnJumpEvent += OnJump;
|
||||
stateMachine.Input.OnDodgeEvent += OnDodge;
|
||||
stateMachine.Input.OnCrouchEvent += OnCrouch;
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
Vector2 input = stateMachine.Input.MoveInput;
|
||||
float moveAmount = Mathf.Clamp01(Mathf.Abs(input.x) + Mathf.Abs(input.y));
|
||||
|
||||
if (moveAmount <= 0.01f)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stateMachine.Input.IsSprintHeld)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerMoveState(stateMachine));
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized;
|
||||
Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir;
|
||||
|
||||
Vector3 velocity = moveDirection * stateMachine.SprintSpeed;
|
||||
|
||||
if (stateMachine.IsGrounded && stateMachine.VelocityY < 0)
|
||||
{
|
||||
stateMachine.VelocityY = -2f;
|
||||
}
|
||||
else
|
||||
{
|
||||
stateMachine.VelocityY += Physics.gravity.y * deltaTime;
|
||||
}
|
||||
velocity.y = stateMachine.VelocityY;
|
||||
|
||||
stateMachine.Controller.Move(velocity * deltaTime);
|
||||
|
||||
if (moveDirection != Vector3.zero)
|
||||
{
|
||||
Quaternion targetRot = Quaternion.LookRotation(moveDirection);
|
||||
stateMachine.transform.rotation = Quaternion.RotateTowards(
|
||||
stateMachine.transform.rotation,
|
||||
targetRot,
|
||||
stateMachine.RotationSpeed * deltaTime
|
||||
);
|
||||
}
|
||||
|
||||
stateMachine.Anim.SetFloat(speedHash, 1f, stateMachine.AnimationDamping, deltaTime);
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
stateMachine.Input.OnJumpEvent -= OnJump;
|
||||
stateMachine.Input.OnDodgeEvent -= OnDodge;
|
||||
stateMachine.Input.OnCrouchEvent -= OnCrouch;
|
||||
}
|
||||
|
||||
private void OnJump()
|
||||
{
|
||||
if (stateMachine.IsGrounded)
|
||||
{
|
||||
if (stateMachine.Scanner != null)
|
||||
{
|
||||
var hitData = stateMachine.Scanner.ObstacleCheck();
|
||||
if (hitData.forwardHitFound)
|
||||
{
|
||||
stateMachine.SwitchState(new PlayerParkourState(stateMachine));
|
||||
return;
|
||||
}
|
||||
}
|
||||
stateMachine.SwitchState(new PlayerJumpState(stateMachine, stateMachine.SprintSpeed));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDodge() => stateMachine.SwitchState(new PlayerDodgeState(stateMachine));
|
||||
private void OnCrouch() => stateMachine.SwitchState(new PlayerCrouchState(stateMachine));
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Player Controller/PlayerRunState.cs.meta
Normal file
2
Assets/Scripts/Player Controller/PlayerRunState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef096feea261a8d449384a68f71470b3
|
||||
177
Assets/Scripts/Player Controller/PlayerStateMachine.cs
Normal file
177
Assets/Scripts/Player Controller/PlayerStateMachine.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
[RequireComponent(typeof(CharacterController), typeof(InputReader), typeof(Animator))]
|
||||
public class PlayerStateMachine : MonoBehaviour
|
||||
{
|
||||
[field: Header("References")]
|
||||
[field: SerializeField] public CharacterController Controller { get; private set; }
|
||||
[field: SerializeField] public virtual InputReader Input { get; private set; }
|
||||
[field: SerializeField] public Animator Anim { get; private set; }
|
||||
[field: SerializeField] public EnvironmentScanner Scanner { get; private set; }
|
||||
public CameraController Cam { get; private set; }
|
||||
|
||||
[field: Header("Movement Settings")]
|
||||
[field: SerializeField] public float WalkSpeed { get; private set; } = 3f;
|
||||
[field: SerializeField] public float RunSpeed { get; private set; } = 6f;
|
||||
[field: SerializeField] public float SprintSpeed { get; private set; } = 9f; // 150% of RunSpeed
|
||||
[field: SerializeField] public float SneakSpeed { get; private set; } = 1.5f;
|
||||
[field: SerializeField] public float DashForce { get; private set; } = 10f;
|
||||
[field: SerializeField] public float RotationSpeed { get; private set; } = 500f;
|
||||
[field: SerializeField] public float AnimationDamping { get; private set; } = 0.2f;
|
||||
|
||||
[field: Header("Airborne Settings")]
|
||||
[field: SerializeField] public float JumpHeight { get; private set; } = 2f;
|
||||
[field: SerializeField] public float Gravity { get; private set; } = -9.81f;
|
||||
[field: SerializeField] public float ThrustDownwardForce { get; private set; } = -20f;
|
||||
|
||||
[field: Header("Ground Check")]
|
||||
[field: SerializeField] public float GroundCheckRadius { get; private set; } = 0.2f;
|
||||
[field: SerializeField] public Vector3 GroundCheckOffset { get; private set; }
|
||||
[field: SerializeField] public LayerMask GroundMask { get; private set; }
|
||||
|
||||
[field: Header("Interaction")]
|
||||
[field: SerializeField] public float InteractionRange { get; private set; } = 2f;
|
||||
[field: SerializeField] public LayerMask InteractionMask { get; private set; }
|
||||
|
||||
public float VelocityY { get; set; }
|
||||
public bool IsGrounded { get; private set; }
|
||||
public bool WasGrounded { get; private set; }
|
||||
|
||||
// Interaction system variables
|
||||
private List<IInteractable> interactablesNearby = new List<IInteractable>();
|
||||
private int currentInteractableIndex = 0;
|
||||
|
||||
public string CurrentStateName => currentState != null ? currentState.GetType().Name : "None";
|
||||
|
||||
private PlayerBaseState currentState;
|
||||
private bool hasControl = true;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Controller = GetComponent<CharacterController>();
|
||||
Input = GetComponent<InputReader>();
|
||||
Anim = GetComponentInChildren<Animator>();
|
||||
Scanner = GetComponent<EnvironmentScanner>();
|
||||
Cam = Camera.main?.GetComponent<CameraController>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SwitchState(new PlayerIdleState(this));
|
||||
|
||||
// Subscribe to cycle events
|
||||
Input.OnNextInteractEvent += OnNextInteract;
|
||||
Input.OnPreviousInteractEvent += OnPreviousInteract;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Input != null)
|
||||
{
|
||||
Input.OnNextInteractEvent -= OnNextInteract;
|
||||
Input.OnPreviousInteractEvent -= OnPreviousInteract;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (!hasControl) return;
|
||||
|
||||
WasGrounded = IsGrounded;
|
||||
CheckGround();
|
||||
UpdateInteractablesList();
|
||||
|
||||
currentState?.Tick(Time.deltaTime);
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!hasControl) return;
|
||||
currentState?.PhysicsTick(Time.fixedDeltaTime);
|
||||
}
|
||||
|
||||
private void CheckGround()
|
||||
{
|
||||
IsGrounded = Physics.CheckSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius, GroundMask);
|
||||
}
|
||||
|
||||
private void UpdateInteractablesList()
|
||||
{
|
||||
interactablesNearby.Clear();
|
||||
Collider[] colliders = Physics.OverlapSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange, InteractionMask);
|
||||
foreach (var col in colliders)
|
||||
{
|
||||
if (col.TryGetComponent(out IInteractable interactable))
|
||||
{
|
||||
if (!interactablesNearby.Contains(interactable))
|
||||
interactablesNearby.Add(interactable);
|
||||
}
|
||||
}
|
||||
|
||||
if (interactablesNearby.Count == 0)
|
||||
{
|
||||
currentInteractableIndex = 0;
|
||||
}
|
||||
else if (currentInteractableIndex >= interactablesNearby.Count)
|
||||
{
|
||||
currentInteractableIndex = interactablesNearby.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNextInteract()
|
||||
{
|
||||
if (interactablesNearby.Count <= 1) return;
|
||||
currentInteractableIndex = (currentInteractableIndex + 1) % interactablesNearby.Count;
|
||||
Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}");
|
||||
}
|
||||
|
||||
private void OnPreviousInteract()
|
||||
{
|
||||
if (interactablesNearby.Count <= 1) return;
|
||||
currentInteractableIndex--;
|
||||
if (currentInteractableIndex < 0) currentInteractableIndex = interactablesNearby.Count - 1;
|
||||
Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}");
|
||||
}
|
||||
|
||||
public IInteractable GetInteractable()
|
||||
{
|
||||
if (interactablesNearby.Count == 0) return null;
|
||||
return interactablesNearby[currentInteractableIndex];
|
||||
}
|
||||
|
||||
public void SetGroundCheck(float radius, Vector3 offset)
|
||||
{
|
||||
GroundCheckRadius = radius;
|
||||
GroundCheckOffset = offset;
|
||||
}
|
||||
|
||||
public void SwitchState(PlayerBaseState newState)
|
||||
{
|
||||
currentState?.Exit();
|
||||
currentState = newState;
|
||||
currentState?.Enter();
|
||||
}
|
||||
|
||||
public void SetControl(bool control)
|
||||
{
|
||||
hasControl = control;
|
||||
Controller.enabled = control;
|
||||
if (!control)
|
||||
{
|
||||
Anim.SetFloat("Speed", 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = new Color(0, 1, 0, 0.5f);
|
||||
Gizmos.DrawSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius);
|
||||
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawWireSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 848ad6fdeb60b254497391392419b063
|
||||
43
Assets/Scripts/Player Controller/PlayerThrustState.cs
Normal file
43
Assets/Scripts/Player Controller/PlayerThrustState.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class PlayerThrustState : PlayerBaseState
|
||||
{
|
||||
private readonly int thrustHash = Animator.StringToHash("Thrust");
|
||||
|
||||
public PlayerThrustState(PlayerStateMachine stateMachine) : base(stateMachine) {}
|
||||
|
||||
public override void Enter()
|
||||
{
|
||||
stateMachine.Anim.SetTrigger(thrustHash);
|
||||
|
||||
// Immediately set a massive downward velocity
|
||||
stateMachine.VelocityY = stateMachine.ThrustDownwardForce;
|
||||
}
|
||||
|
||||
public override void Tick(float deltaTime)
|
||||
{
|
||||
// Keep applying heavy gravity just in case
|
||||
stateMachine.VelocityY += (stateMachine.Gravity * 2f) * deltaTime;
|
||||
|
||||
// Move the player straight down (no horizontal movement allowed during thrust)
|
||||
Vector3 fallMovement = new Vector3(0f, stateMachine.VelocityY, 0f);
|
||||
stateMachine.Controller.Move(fallMovement * deltaTime);
|
||||
|
||||
// When we smash into the ground...
|
||||
if (stateMachine.Controller.isGrounded)
|
||||
{
|
||||
stateMachine.VelocityY = -2f;
|
||||
|
||||
// TODO: Add impact effects, screen shake, or damage area here!
|
||||
|
||||
// Return to idle after landing
|
||||
stateMachine.SwitchState(new PlayerIdleState(stateMachine));
|
||||
}
|
||||
}
|
||||
|
||||
public override void PhysicsTick(float fixedDeltaTime) {}
|
||||
public override void Exit() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e76cfd208cc6f44b8b954147ee1defb
|
||||
Reference in New Issue
Block a user