2026-02-10 21:27:46 +00:00
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.AI;
|
|
|
|
|
using UnityEngine.InputSystem;
|
|
|
|
|
|
|
|
|
|
public class ClickToMoveInputSystem : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
[Header("References")]
|
|
|
|
|
public Camera cam;
|
|
|
|
|
public LayerMask movementLayers; // ground
|
|
|
|
|
public LayerMask selectionLayers; // enemies, NPCs, interactables
|
|
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
[Header("Options")]
|
|
|
|
|
[SerializeField]
|
|
|
|
|
private bool useLegacyMouseFallback = false;
|
|
|
|
|
[SerializeField]
|
|
|
|
|
private bool enableDebugLogs = false;
|
|
|
|
|
|
|
|
|
|
Camera cachedCam;
|
|
|
|
|
Vector2 pointerScreenPos;
|
|
|
|
|
PlayerInteractionController cachedInteraction;
|
|
|
|
|
NavMeshMovementController cachedMovement;
|
|
|
|
|
|
2026-02-10 21:27:46 +00:00
|
|
|
void Awake()
|
|
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
CacheCameraReference();
|
|
|
|
|
CachePlayerReferences();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CacheCameraReference()
|
|
|
|
|
{
|
|
|
|
|
if (cam != null)
|
|
|
|
|
{
|
|
|
|
|
cachedCam = cam;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cachedCam = Camera.main;
|
|
|
|
|
if (cachedCam == null)
|
|
|
|
|
Debug.LogError("ClickToMoveInputSystem: No camera found! Assign one in the inspector or ensure Camera.main exists.", gameObject);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CachePlayerReferences()
|
|
|
|
|
{
|
|
|
|
|
if (Player.current == null)
|
|
|
|
|
{
|
|
|
|
|
cachedInteraction = null;
|
|
|
|
|
cachedMovement = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cachedInteraction = Player.current.GetComponent<PlayerInteractionController>();
|
|
|
|
|
cachedMovement = Player.current.MovementController;
|
|
|
|
|
if (cachedMovement == null)
|
|
|
|
|
{
|
|
|
|
|
cachedMovement = Player.current.GetComponent<NavMeshMovementController>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PlayerInteractionController GetInteraction()
|
|
|
|
|
{
|
|
|
|
|
if (Player.current == null) return null;
|
|
|
|
|
if (cachedInteraction == null || cachedInteraction.gameObject != Player.current.gameObject)
|
|
|
|
|
{
|
|
|
|
|
CachePlayerReferences();
|
|
|
|
|
}
|
|
|
|
|
return cachedInteraction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NavMeshMovementController GetMovement()
|
|
|
|
|
{
|
|
|
|
|
if (Player.current == null) return null;
|
|
|
|
|
if (cachedMovement == null || cachedMovement.gameObject != Player.current.gameObject)
|
|
|
|
|
{
|
|
|
|
|
CachePlayerReferences();
|
|
|
|
|
}
|
|
|
|
|
return cachedMovement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Log(string message)
|
|
|
|
|
{
|
|
|
|
|
if (enableDebugLogs)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log(message, gameObject);
|
|
|
|
|
}
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hook this to PointerPos action (Value Vector2)
|
|
|
|
|
public void OnPointerPos(InputAction.CallbackContext ctx)
|
|
|
|
|
{
|
|
|
|
|
pointerScreenPos = ctx.ReadValue<Vector2>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hook this to LeftClick action (Button)
|
|
|
|
|
public void OnLeftClick(InputAction.CallbackContext ctx)
|
|
|
|
|
{
|
|
|
|
|
if (!ctx.performed) return;
|
2026-02-11 17:30:49 +00:00
|
|
|
HandleLeftClick();
|
|
|
|
|
}
|
2026-02-10 21:27:46 +00:00
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
private void HandleLeftClick()
|
|
|
|
|
{
|
|
|
|
|
if (cachedCam == null) return;
|
|
|
|
|
|
|
|
|
|
Ray ray = cachedCam.ScreenPointToRay(pointerScreenPos);
|
|
|
|
|
|
2026-02-10 21:27:46 +00:00
|
|
|
if (Physics.Raycast(ray, out RaycastHit hit, 500f, selectionLayers))
|
|
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
// Try NPC first
|
|
|
|
|
var npc = hit.collider.GetComponentInParent<NPC>();
|
|
|
|
|
if (npc != null)
|
|
|
|
|
{
|
|
|
|
|
// Check if NPC has a Selectable component
|
|
|
|
|
var selectable = npc.GetComponent<Selectable>();
|
|
|
|
|
if (selectable != null && SelectionManager.Instance != null)
|
|
|
|
|
{
|
|
|
|
|
SelectionManager.Instance.Select(selectable);
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Selected NPC: {npc.npcName} ({npc.npcType})");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try building first
|
|
|
|
|
var building = hit.collider.GetComponentInParent<Building>();
|
|
|
|
|
if (building != null)
|
|
|
|
|
{
|
|
|
|
|
if (SelectionManager.Instance != null)
|
|
|
|
|
SelectionManager.Instance.Select(building);
|
|
|
|
|
Log($"Selected building: {building.buildingName}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to find a Selectable (Character, NPC, etc.)
|
|
|
|
|
var selectable2 = hit.collider.GetComponentInParent<Selectable>();
|
|
|
|
|
if (selectable2 != null)
|
|
|
|
|
{
|
|
|
|
|
if (SelectionManager.Instance != null)
|
|
|
|
|
SelectionManager.Instance.Select(selectable2);
|
|
|
|
|
|
|
|
|
|
// Set player target if it's a character
|
|
|
|
|
var character = selectable2.GetComponent<Character>();
|
|
|
|
|
if (character != null && Player.current != null)
|
|
|
|
|
{
|
|
|
|
|
Player.current.SetTarget(character);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log($"Selected: {selectable2.displayName}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for interactive objects (gates, bridges, ledges without Selection Manager)
|
|
|
|
|
var interactiveObject = hit.collider.GetComponentInParent<IInteractiveObject>();
|
|
|
|
|
if (interactiveObject != null)
|
2026-02-10 21:27:46 +00:00
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
Log($"Selected interactive object: {interactiveObject.GetDisplayName()}");
|
2026-02-10 21:27:46 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
// Generic interactable (if needed)
|
2026-02-10 21:27:46 +00:00
|
|
|
var interactable = hit.collider.GetComponentInParent<Interactable>();
|
2026-02-11 17:30:49 +00:00
|
|
|
if (interactable != null)
|
|
|
|
|
{
|
|
|
|
|
interactable.OnSelect();
|
|
|
|
|
Log($"Interacted with: {interactable.name}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
// Nothing selected, clear selection and target
|
|
|
|
|
if (SelectionManager.Instance != null)
|
|
|
|
|
SelectionManager.Instance.ClearSelection();
|
|
|
|
|
if (Player.current != null)
|
|
|
|
|
Player.current.SetTarget(null);
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hook this to RightClick action (Button)
|
|
|
|
|
public void OnRightClick(InputAction.CallbackContext ctx)
|
|
|
|
|
{
|
|
|
|
|
if (!ctx.performed) return;
|
2026-02-11 17:30:49 +00:00
|
|
|
HandleRightClick();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void HandleRightClick()
|
|
|
|
|
{
|
|
|
|
|
if (cachedCam == null || Player.current == null) return;
|
|
|
|
|
|
|
|
|
|
Ray ray = cachedCam.ScreenPointToRay(pointerScreenPos);
|
|
|
|
|
|
|
|
|
|
// First check if right-clicking on a building to interact with it
|
|
|
|
|
if (Physics.Raycast(ray, out RaycastHit hit, 500f, selectionLayers))
|
|
|
|
|
{
|
|
|
|
|
// Check for NPC interaction first
|
|
|
|
|
var npc = hit.collider.GetComponentInParent<NPC>();
|
|
|
|
|
if (npc != null)
|
|
|
|
|
{
|
|
|
|
|
var interaction = GetInteraction();
|
|
|
|
|
if (interaction != null)
|
|
|
|
|
{
|
|
|
|
|
interaction.SetPendingNPCInteraction(npc);
|
|
|
|
|
Log($"Set interaction target: {npc.npcName}");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Fallback: move to NPC and look at them
|
|
|
|
|
var movement = GetMovement();
|
|
|
|
|
if (movement != null)
|
|
|
|
|
{
|
|
|
|
|
movement.MoveToAndLookAt(npc.transform.position, npc.transform.position);
|
|
|
|
|
Log($"Moving to NPC: {npc.npcName}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var building = hit.collider.GetComponentInParent<Building>();
|
|
|
|
|
if (building != null)
|
|
|
|
|
{
|
|
|
|
|
// If building can be entered and has an entry point, move to the door
|
|
|
|
|
if (building.canEnter && building.entryPoint != null)
|
|
|
|
|
{
|
|
|
|
|
var interaction = GetInteraction();
|
|
|
|
|
if (interaction != null)
|
|
|
|
|
{
|
|
|
|
|
interaction.SetPendingBuildingInteraction(building);
|
|
|
|
|
Log($"Set interaction target: {building.buildingName}");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Fallback: move to entry point and look at it
|
|
|
|
|
var movement = GetMovement();
|
|
|
|
|
if (movement != null)
|
|
|
|
|
{
|
|
|
|
|
movement.MoveToAndLookAt(building.entryPoint.position, building.transform.position);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Cannot enter building - move as close as possible via NavMesh and look at building
|
|
|
|
|
var movement = GetMovement();
|
|
|
|
|
if (movement != null)
|
|
|
|
|
{
|
|
|
|
|
// Move toward the hit point and set look target to building center
|
|
|
|
|
movement.MoveToAndLookAt(hit.point, building.transform.position);
|
|
|
|
|
Log($"Moving toward building: {building.buildingName}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for other interactive objects (gates, bridges, ledges, etc.)
|
|
|
|
|
var interactiveObject = hit.collider.GetComponentInParent<IInteractiveObject>();
|
|
|
|
|
if (interactiveObject != null)
|
|
|
|
|
{
|
|
|
|
|
var interaction = GetInteraction();
|
|
|
|
|
if (interaction != null)
|
|
|
|
|
{
|
|
|
|
|
Vector3 interactionPoint = interactiveObject.GetInteractionPoint();
|
|
|
|
|
interaction.SetPendingInteraction(interactiveObject, interactionPoint);
|
|
|
|
|
Log($"Set interaction target: {interactiveObject.GetDisplayName()}");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Fallback if no PlayerInteractionController - move to interaction point directly and look at it
|
|
|
|
|
var movement = GetMovement();
|
|
|
|
|
if (movement != null)
|
|
|
|
|
{
|
|
|
|
|
Vector3 interactionPoint = interactiveObject.GetInteractionPoint();
|
|
|
|
|
movement.MoveToAndLookAt(interactionPoint, interactionPoint);
|
|
|
|
|
Log($"Moving to interactive object: {interactiveObject.GetDisplayName()}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-10 21:27:46 +00:00
|
|
|
|
2026-02-11 17:30:49 +00:00
|
|
|
// If not an interactive object, check for movement destination
|
|
|
|
|
if (Physics.Raycast(ray, out RaycastHit groundHit, 500f, movementLayers))
|
2026-02-10 21:27:46 +00:00
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
var movement = GetMovement();
|
2026-02-10 21:27:46 +00:00
|
|
|
if (movement != null)
|
|
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
movement.MoveTo(groundHit.point);
|
|
|
|
|
Log($"Moving to point: {groundHit.point}");
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// fallback: try to find NavMeshAgent on player root
|
2026-02-11 17:30:49 +00:00
|
|
|
var agent = Player.current.GetComponent<NavMeshAgent>();
|
|
|
|
|
if (agent != null) agent.SetDestination(groundHit.point);
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optional fallback if PointerPos not wired
|
|
|
|
|
void Update()
|
|
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
if (!useLegacyMouseFallback) return;
|
|
|
|
|
if (cachedCam == null) return;
|
2026-02-10 21:27:46 +00:00
|
|
|
if (Mouse.current == null) return;
|
|
|
|
|
|
|
|
|
|
pointerScreenPos = Mouse.current.position.ReadValue();
|
|
|
|
|
|
|
|
|
|
if (Mouse.current.leftButton.wasPressedThisFrame)
|
|
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
HandleLeftClick();
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Mouse.current.rightButton.wasPressedThisFrame)
|
|
|
|
|
{
|
2026-02-11 17:30:49 +00:00
|
|
|
HandleRightClick();
|
2026-02-10 21:27:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|