using Interaction; using Interaction.Conditions; using UnityEngine; namespace Player { public class PlayerAimOutlineHighlighter : MonoBehaviour { [Header("Raycast")] [SerializeField] private Transform cameraTransform; [SerializeField] private float interactRange = 3f; [SerializeField] private LayerMask interactLayer; [SerializeField] private float refreshInterval = 0.05f; [Header("Outline")] [SerializeField] private Material outlineMaterial; [SerializeField] private Color canInteractColor = new Color(0.1f, 1f, 0.25f, 1f); [SerializeField] private Color lockedColor = new Color(1f, 0.55f, 0.1f, 1f); [SerializeField] private Color blockedColor = new Color(1f, 0.55f, 0.1f, 1f); [Header("Behavior")] [SerializeField] private bool useLockedColorWhenLocked = true; [Header("Debug")] [SerializeField] private bool debugLog; private float nextTime; private InteractOutlineTarget currentTarget; private Color currentColor; private void Awake() { if (cameraTransform == null) { cameraTransform = GetComponentInChildren()?.transform; } } private void Update() { if (cameraTransform == null) return; if (outlineMaterial == null) return; if (refreshInterval > 0f && Time.time < nextTime) return; nextTime = refreshInterval > 0f ? Time.time + refreshInterval : Time.time; Ray ray = new Ray(cameraTransform.position, cameraTransform.forward); if (!Physics.Raycast(ray, out RaycastHit hit, interactRange, interactLayer)) { Clear(); return; } var hitCollider = hit.collider; if (hitCollider == null) { Clear(); return; } var interactable = hitCollider.GetComponentInParent(); if (interactable == null) { Clear(); return; } var conditions = hitCollider.GetComponentsInParent(); bool allowed = true; string failReason = null; for (int i = 0; i < conditions.Length; i++) { var condition = conditions[i]; if (condition == null) continue; if (!condition.CanInteract(gameObject, out failReason)) { allowed = false; break; } } bool locked = false; if (!allowed && useLockedColorWhenLocked) { var unlockGate = hitCollider.GetComponentInParent(); var unlockState = hitCollider.GetComponentInParent(); if (unlockState != null && !unlockState.IsUnlocked && (unlockGate != null || unlockState.BlocksInteractionWhenLocked)) { locked = true; } } Color color = allowed ? canInteractColor : (locked ? lockedColor : blockedColor); var target = hitCollider.GetComponentInParent(); if (target == null) { target = interactable is MonoBehaviour mb ? mb.GetComponentInParent() : null; } if (target == null) { Clear(); if (debugLog) { Debug.Log($"[PlayerAimOutlineHighlighter] Missing InteractOutlineTarget on '{hitCollider.name}'", hitCollider); } return; } if (currentTarget != target) { Clear(); currentTarget = target; currentTarget.SetOutlineMaterial(outlineMaterial); } if (currentTarget != null && currentColor != color) { currentColor = color; currentTarget.SetHighlighted(true, currentColor); } else if (currentTarget != null && currentColor == color) { currentTarget.SetHighlighted(true, currentColor); } } private void Clear() { if (currentTarget != null) { currentTarget.SetHighlighted(false, currentColor); currentTarget = null; } } } }