Files
Echo/Assets/_Project/Scripts/Player/PlayerAimOutlineHighlighter.cs
2026-07-08 20:42:51 +08:00

139 lines
4.6 KiB
C#

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<Camera>()?.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<IInteractable>();
if (interactable == null)
{
Clear();
return;
}
var conditions = hitCollider.GetComponentsInParent<InteractConditionBehaviour>();
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<RequireUnlockedInteractCondition>();
var unlockState = hitCollider.GetComponentInParent<InteractUnlockState>();
if (unlockState != null && !unlockState.IsUnlocked && (unlockGate != null || unlockState.BlocksInteractionWhenLocked))
{
locked = true;
}
}
Color color = allowed ? canInteractColor : (locked ? lockedColor : blockedColor);
var target = hitCollider.GetComponentInParent<InteractOutlineTarget>();
if (target == null)
{
target = interactable is MonoBehaviour mb ? mb.GetComponentInParent<InteractOutlineTarget>() : 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;
}
}
}
}