204 lines
6.5 KiB
C#
204 lines
6.5 KiB
C#
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
using UnityEngine.InputSystem;
|
|
|
|
[RequireComponent(typeof(CharacterController))]
|
|
public class PlayerController : MonoBehaviour
|
|
{
|
|
[Header("Movement Settings")]
|
|
[SerializeField] private float moveSpeed = 6.0f;
|
|
[SerializeField] private float rotationSpeed = 12.0f;
|
|
[SerializeField] private float stoppingDistance = 0.2f;
|
|
[SerializeField] private LayerMask groundLayer = ~0; // Default to all layers
|
|
|
|
[Header("Interaction Settings")]
|
|
[SerializeField] private LayerMask interactableLayer = ~0;
|
|
[SerializeField] private float defaultInteractionRadius = 2.0f;
|
|
|
|
[Header("Visual Feedback Prefabs (Optional)")]
|
|
[SerializeField] private GameObject moveClickEffectPrefab;
|
|
[SerializeField] private GameObject interactClickEffectPrefab;
|
|
|
|
private Camera mainCamera;
|
|
private CharacterController characterController;
|
|
private NavMeshAgent navAgent;
|
|
|
|
private Vector3 targetPosition;
|
|
private IInteractable targetInteractable;
|
|
private bool isMovingToInteractable = false;
|
|
private bool isMoving = false;
|
|
|
|
private void Awake()
|
|
{
|
|
mainCamera = Camera.main;
|
|
characterController = GetComponent<CharacterController>();
|
|
navAgent = GetComponent<NavMeshAgent>();
|
|
targetPosition = transform.position;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
HandleInput();
|
|
ProcessMovement();
|
|
ProcessInteractionCheck();
|
|
}
|
|
|
|
private void HandleInput()
|
|
{
|
|
// Using New Unity Input System (UnityEngine.InputSystem)
|
|
Mouse currentMouse = Mouse.current;
|
|
if (currentMouse == null) return;
|
|
|
|
Vector2 screenMousePos = currentMouse.position.ReadValue();
|
|
|
|
// Left Click / Hold: Click or drag to move continuously
|
|
if (currentMouse.leftButton.isPressed)
|
|
{
|
|
bool isFirstPress = currentMouse.leftButton.wasPressedThisFrame;
|
|
HandleLeftClickOrDrag(screenMousePos, isFirstPress);
|
|
}
|
|
|
|
// Right Click: Interact with object
|
|
if (currentMouse.rightButton.wasPressedThisFrame)
|
|
{
|
|
HandleRightClick(screenMousePos);
|
|
}
|
|
}
|
|
|
|
private void HandleLeftClickOrDrag(Vector2 screenPosition, bool isFirstPress)
|
|
{
|
|
Ray ray = mainCamera.ScreenPointToRay(screenPosition);
|
|
if (Physics.Raycast(ray, out RaycastHit hit, 100f, groundLayer))
|
|
{
|
|
SetDestination(hit.point);
|
|
ClearTargetInteractable();
|
|
|
|
// Only spawn click effect on the initial click frame (not every frame while dragging)
|
|
if (isFirstPress)
|
|
{
|
|
SpawnEffect(moveClickEffectPrefab, hit.point);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandleRightClick(Vector2 screenPosition)
|
|
{
|
|
Ray ray = mainCamera.ScreenPointToRay(screenPosition);
|
|
if (Physics.Raycast(ray, out RaycastHit hit, 100f, interactableLayer))
|
|
{
|
|
IInteractable interactable = hit.collider.GetComponentInParent<IInteractable>();
|
|
if (interactable != null)
|
|
{
|
|
targetInteractable = interactable;
|
|
isMovingToInteractable = true;
|
|
SetDestination(interactable.GetTransform().position);
|
|
SpawnEffect(interactClickEffectPrefab, hit.point);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Right-clicked ground with no interactable
|
|
if (Physics.Raycast(ray, out RaycastHit groundHit, 100f, groundLayer))
|
|
{
|
|
ClearTargetInteractable();
|
|
}
|
|
}
|
|
|
|
private void SetDestination(Vector3 destination)
|
|
{
|
|
targetPosition = destination;
|
|
isMoving = true;
|
|
|
|
if (navAgent != null && navAgent.enabled)
|
|
{
|
|
navAgent.SetDestination(destination);
|
|
}
|
|
}
|
|
|
|
private void ProcessMovement()
|
|
{
|
|
if (!isMoving) return;
|
|
|
|
// If using NavMeshAgent
|
|
if (navAgent != null && navAgent.enabled)
|
|
{
|
|
if (!navAgent.pathPending && navAgent.remainingDistance <= navAgent.stoppingDistance)
|
|
{
|
|
if (!navAgent.hasPath || navAgent.velocity.sqrMagnitude == 0f)
|
|
{
|
|
isMoving = false;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Fallback: Manual Transform/CharacterController movement
|
|
Vector3 direction = (targetPosition - transform.position);
|
|
direction.y = 0; // Keep movement top-down flat
|
|
|
|
float distance = direction.magnitude;
|
|
float currentStoppingDistance = isMovingToInteractable && targetInteractable != null
|
|
? targetInteractable.GetInteractionRadius()
|
|
: stoppingDistance;
|
|
|
|
if (distance > currentStoppingDistance)
|
|
{
|
|
// Smooth Rotation
|
|
Quaternion targetRotation = Quaternion.LookRotation(direction);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
|
|
|
|
// Move
|
|
Vector3 moveVelocity = direction.normalized * moveSpeed * Time.deltaTime;
|
|
characterController.Move(moveVelocity);
|
|
}
|
|
else
|
|
{
|
|
isMoving = false;
|
|
}
|
|
}
|
|
|
|
private void ProcessInteractionCheck()
|
|
{
|
|
if (!isMovingToInteractable || targetInteractable == null) return;
|
|
|
|
float distance = Vector3.Distance(transform.position, targetInteractable.GetTransform().position);
|
|
float radius = targetInteractable.GetInteractionRadius() > 0 ? targetInteractable.GetInteractionRadius() : defaultInteractionRadius;
|
|
|
|
if (distance <= radius)
|
|
{
|
|
// Face target interactable
|
|
Vector3 dir = (targetInteractable.GetTransform().position - transform.position);
|
|
dir.y = 0;
|
|
if (dir != Vector3.zero)
|
|
{
|
|
transform.rotation = Quaternion.LookRotation(dir);
|
|
}
|
|
|
|
// Trigger interaction
|
|
targetInteractable.Interact(gameObject);
|
|
ClearTargetInteractable();
|
|
}
|
|
}
|
|
|
|
private void ClearTargetInteractable()
|
|
{
|
|
targetInteractable = null;
|
|
isMovingToInteractable = false;
|
|
}
|
|
|
|
private void SpawnEffect(GameObject effectPrefab, Vector3 position)
|
|
{
|
|
if (effectPrefab != null)
|
|
{
|
|
Instantiate(effectPrefab, position + Vector3.up * 0.05f, Quaternion.identity);
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
Gizmos.color = Color.green;
|
|
Gizmos.DrawWireSphere(targetPosition, 0.3f);
|
|
Gizmos.DrawLine(transform.position, targetPosition);
|
|
}
|
|
}
|