using UnityEngine; using UnityEngine.AI; using UnityEngine.InputSystem; [RequireComponent(typeof(CharacterController))] [RequireComponent(typeof(CombatController))] 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 & Combat Settings")] [SerializeField] private LayerMask interactableLayer = ~0; [SerializeField] private float defaultInteractionRadius = 2.0f; [Tooltip("If true, the player automatically turns to attack any enemy that damages them (RuneScape style).")] [SerializeField] private bool autoRetaliate = true; [Header("Visual Feedback Prefabs (Optional)")] [SerializeField] private GameObject moveClickEffectPrefab; [SerializeField] private GameObject interactClickEffectPrefab; private Camera mainCamera; private CharacterController characterController; private NavMeshAgent navAgent; private CombatController combatController; private Vector3 targetPosition; private IInteractable targetInteractable; private bool isMovingToInteractable = false; private bool isMoving = false; private void Awake() { mainCamera = Camera.main; characterController = GetComponent(); navAgent = GetComponent(); combatController = GetComponent(); targetPosition = transform.position; if (combatController != null && combatController.Stats != null) { combatController.Stats.onHealthChanged.AddListener(OnHealthChanged); } } private void Update() { HandleInput(); ProcessMovement(); ProcessInteractionCheck(); ProcessCombatCheck(); } 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 or attack enemy 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(); combatController.ClearTarget(); // 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); // Check for Raycast hit on Interactable or Enemy if (Physics.Raycast(ray, out RaycastHit hit, 100f, interactableLayer)) { // Check if object is an Enemy / CharacterStats for Combat CharacterStats enemyStats = hit.collider.GetComponent() ?? hit.collider.GetComponentInParent() ?? hit.collider.GetComponentInChildren(); if (enemyStats != null && enemyStats != combatController.Stats && !enemyStats.IsDead) { ClearTargetInteractable(); combatController.SetTarget(enemyStats); SetDestination(enemyStats.transform.position); SpawnEffect(interactClickEffectPrefab, hit.point); return; } // Check if object is an IInteractable IInteractable interactable = hit.collider.GetComponent() ?? hit.collider.GetComponentInParent() ?? hit.collider.GetComponentInChildren(); if (interactable != null) { combatController.ClearTarget(); targetInteractable = interactable; isMovingToInteractable = true; SetDestination(interactable.GetTransform().position); SpawnEffect(interactClickEffectPrefab, hit.point); return; } } // Right-clicked ground with no interactable/enemy if (Physics.Raycast(ray, out RaycastHit groundHit, 100f, groundLayer)) { ClearTargetInteractable(); combatController.ClearTarget(); } } private void ProcessCombatCheck() { if (combatController.CurrentTarget == null) return; if (combatController.CurrentTarget.IsDead) { combatController.ClearTarget(); return; } // Update destination to track moving target targetPosition = combatController.CurrentTarget.transform.position; if (navAgent != null && navAgent.enabled) { navAgent.stoppingDistance = combatController.Stats.AttackRange * 0.9f; navAgent.SetDestination(targetPosition); } // Check if inside attack range if (combatController.IsInAttackRange(combatController.CurrentTarget)) { isMoving = false; if (navAgent != null && navAgent.enabled && navAgent.hasPath) { navAgent.ResetPath(); } combatController.TryExecuteAttack(); } } private void OnHealthChanged(float currentHealth, float maxHealth) { if (!autoRetaliate || combatController.CurrentTarget != null) return; // Scan nearby hostile objects / enemies that are attacking the player Collider[] hits = Physics.OverlapSphere(transform.position, 10.0f, interactableLayer); foreach (var hit in hits) { CharacterStats attackerStats = hit.GetComponent() ?? hit.GetComponentInParent() ?? hit.GetComponentInChildren(); if (attackerStats != null && attackerStats != combatController.Stats && !attackerStats.IsDead) { combatController.SetTarget(attackerStats); SetDestination(attackerStats.transform.position); Debug.Log($"[PlayerController] Auto-retaliating against '{attackerStats.CharacterName}'!"); break; } } } 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) { float targetStoppingDistance = isMovingToInteractable && targetInteractable != null ? (targetInteractable.GetInteractionRadius() > 0 ? targetInteractable.GetInteractionRadius() : defaultInteractionRadius) * 0.9f : stoppingDistance; navAgent.stoppingDistance = targetStoppingDistance; 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() > 0 ? targetInteractable.GetInteractionRadius() : defaultInteractionRadius) * 0.9f : 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; Vector3 flatPlayerPos = new Vector3(transform.position.x, 0, transform.position.z); Vector3 flatTargetPos = new Vector3(targetInteractable.GetTransform().position.x, 0, targetInteractable.GetTransform().position.z); float distance = Vector3.Distance(flatPlayerPos, flatTargetPos); float radius = targetInteractable.GetInteractionRadius() > 0 ? targetInteractable.GetInteractionRadius() : defaultInteractionRadius; if (distance <= radius + 0.1f) { // 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); } }