302 lines
9.5 KiB
C#
302 lines
9.5 KiB
C#
using System;
|
|
using Core.Domain.Status;
|
|
using Core.Domain.Status.Effects;
|
|
using KBCore.Refs;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
namespace Infrastructure.Unity
|
|
{
|
|
[RequireComponent(typeof(Rigidbody))]
|
|
public class PlayerController : MonoBehaviour
|
|
{
|
|
[Header("Movement Settings")]
|
|
[SerializeField]
|
|
private float moveSpeed = 8f;
|
|
[SerializeField] private float maxVelocityChange = 10f;
|
|
[SerializeField] private float snapForce = 15f;
|
|
|
|
[Header("Jump Settings")]
|
|
[SerializeField] private float jumpForce = 12f; // Adjusted for snappy feel
|
|
[SerializeField] private float coyoteTime = 0.1f; // Grace period after falling
|
|
[SerializeField] private float gravityMultiplier = 2.5f; // Stronger gravity for less "floaty" feel
|
|
|
|
[Header("Controls")]
|
|
[SerializeField] private bool useCameraRelativeMovement = true;
|
|
|
|
[Header("Interaction")]
|
|
[SerializeField] private LayerMask tileLayer;
|
|
[SerializeField] private float groundCheckDistance = 1.1f; // Slightly more than half player height
|
|
|
|
[Self][SerializeField] private Rigidbody rb;
|
|
[Self][SerializeField] private MeshRenderer meshRenderer;
|
|
|
|
private InputSystem_Actions _actions;
|
|
private Vector2 _moveInput;
|
|
private Transform _camTransform;
|
|
|
|
private MaterialPropertyBlock _propBlock;
|
|
private static readonly int ColorProperty = Shader.PropertyToID("_BaseColor");
|
|
private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor");
|
|
private Color _defaultColor = Color.white;
|
|
|
|
private bool _isGrounded;
|
|
private float _coyoteTimeCounter;
|
|
private bool _jumpPressed;
|
|
private Vector3 _currentTileCentroid;
|
|
|
|
public Rigidbody Rigidbody => rb;
|
|
public StatusManager Status { get; private set; }
|
|
|
|
private void OnEnable()
|
|
{
|
|
_actions.Player.Enable();
|
|
|
|
_actions.Player.Move.performed += OnMovePerformed;
|
|
_actions.Player.Move.canceled += OnMoveCanceled;
|
|
_actions.Player.Jump.performed += OnJumpPerformed;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
_actions.Player.Move.performed -= OnMovePerformed;
|
|
_actions.Player.Move.canceled -= OnMoveCanceled;
|
|
_actions.Player.Jump.performed -= OnJumpPerformed;
|
|
|
|
_actions.Player.Disable();
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
_actions = new InputSystem_Actions();
|
|
Status = new StatusManager();
|
|
|
|
if (Camera.main)
|
|
{
|
|
_camTransform = Camera.main.transform;
|
|
}
|
|
|
|
rb.freezeRotation = true;
|
|
rb.useGravity = false;
|
|
|
|
_propBlock = new MaterialPropertyBlock();
|
|
if (meshRenderer.material.HasProperty(ColorProperty))
|
|
{
|
|
_defaultColor = meshRenderer.material.GetColor(ColorProperty);
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
Status.Tick(Time.deltaTime);
|
|
UpdateVisuals();
|
|
|
|
if (_isGrounded)
|
|
{
|
|
_coyoteTimeCounter = coyoteTime;
|
|
}
|
|
else
|
|
{
|
|
_coyoteTimeCounter -= Time.deltaTime;
|
|
}
|
|
|
|
if (_jumpPressed)
|
|
{
|
|
_jumpPressed = false;
|
|
if (_coyoteTimeCounter > 0f || Status.CurrentCapabilities.CanHover)
|
|
{
|
|
PerformJump();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
CheckGround();
|
|
HandleMovement();
|
|
ApplyGravity();
|
|
|
|
// Interaction with tiles (Decay Logic)
|
|
if (_isGrounded && Status.CurrentCapabilities.CanTriggerDecay)
|
|
{
|
|
InteractWithGround();
|
|
}
|
|
}
|
|
|
|
private void CheckGround()
|
|
{
|
|
if (Physics.SphereCast(transform.position, 0.3f, Vector3.down, out var hit, groundCheckDistance, tileLayer))
|
|
{
|
|
_isGrounded = true;
|
|
_currentTileCentroid = hit.collider.transform.position;
|
|
}
|
|
else
|
|
{
|
|
_isGrounded = false;
|
|
}
|
|
}
|
|
|
|
private void PerformJump()
|
|
{
|
|
var vel = rb.linearVelocity;
|
|
vel.y = 0;
|
|
rb.linearVelocity = vel;
|
|
|
|
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
|
|
|
|
_coyoteTimeCounter = 0f;
|
|
_isGrounded = false;
|
|
}
|
|
|
|
private void ApplyGravity()
|
|
{
|
|
if (Status.CurrentCapabilities.CanHover) return;
|
|
|
|
var gravity = Physics.gravity.y * gravityMultiplier;
|
|
rb.AddForce(Vector3.up * gravity, ForceMode.Acceleration);
|
|
}
|
|
|
|
private void HandleMovement()
|
|
{
|
|
var currentSpeed = moveSpeed * Status.CurrentCapabilities.SpeedMultiplier;
|
|
var targetVelocity = Vector3.zero;
|
|
var snapAxis = Vector3.zero;
|
|
|
|
Vector3 desiredDirection;
|
|
|
|
if (_moveInput.sqrMagnitude < 0.01f)
|
|
{
|
|
desiredDirection = Vector3.zero;
|
|
}
|
|
else if (useCameraRelativeMovement && _camTransform)
|
|
{
|
|
var camForward = _camTransform.forward;
|
|
var camRight = _camTransform.right;
|
|
camForward.y = 0;
|
|
camRight.y = 0;
|
|
camForward.Normalize();
|
|
camRight.Normalize();
|
|
|
|
desiredDirection = (camForward * _moveInput.y + camRight * _moveInput.x).normalized;
|
|
}
|
|
else
|
|
{
|
|
desiredDirection = new Vector3(_moveInput.x, 0, _moveInput.y).normalized;
|
|
}
|
|
|
|
|
|
if (desiredDirection.sqrMagnitude > 0.01f)
|
|
{
|
|
if (Mathf.Abs(desiredDirection.x) > Mathf.Abs(desiredDirection.z))
|
|
{
|
|
targetVelocity = new Vector3(Mathf.Sign(desiredDirection.x) * currentSpeed, 0, 0);
|
|
snapAxis = Vector3.forward;
|
|
}
|
|
else
|
|
{
|
|
targetVelocity = new Vector3(0, 0, Mathf.Sign(desiredDirection.z) * currentSpeed);
|
|
snapAxis = Vector3.right;
|
|
}
|
|
}
|
|
|
|
var velocity = rb.linearVelocity;
|
|
var velocityChange = (targetVelocity - velocity);
|
|
|
|
velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
|
|
velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
|
|
velocityChange.y = 0f;
|
|
|
|
rb.AddForce(velocityChange, ForceMode.VelocityChange);
|
|
|
|
if (snapAxis != Vector3.zero)
|
|
{
|
|
ApplySnapping(snapAxis);
|
|
}
|
|
else
|
|
{
|
|
ApplySnapping(Vector3.right);
|
|
ApplySnapping(Vector3.forward);
|
|
}
|
|
}
|
|
|
|
private void ApplySnapping(Vector3 axis)
|
|
{
|
|
if (!_isGrounded) return;
|
|
|
|
var targetPos = Vector3.Dot(_currentTileCentroid, axis);
|
|
var currentPos = Vector3.Dot(transform.position, axis);
|
|
|
|
var diff = targetPos - currentPos;
|
|
|
|
|
|
var currentVelOnAxis = Vector3.Dot(rb.linearVelocity, axis);
|
|
var targetVel = diff * snapForce;
|
|
|
|
var correction = axis * (targetVel - currentVelOnAxis);
|
|
|
|
rb.AddForce(correction, ForceMode.VelocityChange);
|
|
}
|
|
|
|
private void InteractWithGround()
|
|
{
|
|
if (Physics.SphereCast(transform.position, 0.3f, Vector3.down, out var hit, groundCheckDistance, tileLayer))
|
|
{
|
|
if (hit.collider.TryGetComponent<TileViewAdapter>(out var tileAdapter))
|
|
{
|
|
tileAdapter.OnPlayerStep();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnMovePerformed(InputAction.CallbackContext ctx)
|
|
{
|
|
_moveInput = ctx.ReadValue<Vector2>();
|
|
}
|
|
|
|
private void OnMoveCanceled(InputAction.CallbackContext ctx)
|
|
{
|
|
_moveInput = Vector2.zero;
|
|
}
|
|
|
|
private void OnJumpPerformed(InputAction.CallbackContext ctx)
|
|
{
|
|
if (ctx.performed) _jumpPressed = true;
|
|
}
|
|
|
|
private void UpdateVisuals()
|
|
{
|
|
var targetColor = _defaultColor;
|
|
var emissionColor = Color.black;
|
|
|
|
var caps = Status.CurrentCapabilities;
|
|
|
|
if (caps.CanHover)
|
|
{
|
|
targetColor = EffectColors.HoverColor;
|
|
emissionColor = EffectColors.HoverColor * 0.5f;
|
|
}
|
|
else if (!caps.CanTriggerDecay)
|
|
{
|
|
targetColor = EffectColors.LightFootedColor;
|
|
}
|
|
else if (caps.SpeedMultiplier > 1.2f)
|
|
{
|
|
targetColor = EffectColors.SpeedBoostColor;
|
|
emissionColor = EffectColors.SpeedBoostColor * 0.5f;
|
|
}
|
|
|
|
meshRenderer.GetPropertyBlock(_propBlock);
|
|
|
|
var currentColor = _propBlock.GetColor(ColorProperty);
|
|
if (currentColor.a == 0) currentColor = _defaultColor;
|
|
|
|
var newColor = Color.Lerp(currentColor, targetColor, Time.deltaTime * 5f);
|
|
|
|
_propBlock.SetColor(ColorProperty, newColor);
|
|
_propBlock.SetColor(EmissionColorProperty, emissionColor);
|
|
|
|
meshRenderer.SetPropertyBlock(_propBlock);
|
|
}
|
|
}
|
|
} |