Files
aibis-dream/Assets/Scripts/FixSystem/Effect/MouseFollowEffect.cs
T

419 lines
14 KiB
C#

using AibisDream.Framework;
using DG.Tweening;
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
namespace AibisDream
{
public class MouseFollowAndZoom : MonoBehaviour, IInteraction
{
public GameObject targetIndicator;
public TMP_Text targetNameText;
public Vector2 sceneBoundsMin;
public Vector2 sceneBoundsMax;
[Header("Cinemachine Pan")]
[Tooltip("平移目标;留空则平移本物体。Eye 模块应指向 Eye DeepCamera 虚拟相机 Transform。")]
[SerializeField] private Transform panTransform;
[Tooltip("虚拟相机可移动的世界区域(与 ViewCamera / EyeSystem 聚焦边界一致)。")]
[SerializeField] private Vector2 worldRegionMin = new(30.2f, -5f);
[SerializeField] private Vector2 worldRegionMax = new(50.2f, 5f);
[Header("Input")]
[SerializeField] private float mouseScreenOffsetX;
[FormerlySerializedAs("viewCamera")]
[SerializeField] private Camera legacyInputCamera;
public float parallaxEffectMultiplier = 0.1f;
public Vector2 maxOffset = new Vector2(2f, 2f);
[Header("Interaction")]
[SerializeField] private bool _isInteractionActive = true;
[SerializeField] private bool _isInteractionAvailable = true;
[SerializeField] private float _dialogEndCooldown = 0.1f;
private EyeSystem eyemanager;
private EyeTarget currentTarget;
private bool isActive;
private bool isLocked;
private float _lastDialogEndTime = -1f;
private bool _isDialogCooldownActive;
private Vector3 initialPosition;
private Vector3 currentOffset;
private Tween focusTween;
private Transform PanTransform => panTransform != null ? panTransform : transform;
private bool UsesCinemachinePan => panTransform != null;
public Vector3 ViewPosition
{
get
{
Camera cameraForInput = GetInputCamera();
return cameraForInput != null ? cameraForInput.transform.position : PanTransform.position;
}
}
public void SetIsActive(bool value)
{
isActive = value;
}
public void SetIsLock(bool value)
{
isLocked = value;
}
public void SetInteractionActive(bool value)
{
_isInteractionActive = value;
}
public void SetInteractionAvailable(bool value)
{
_isInteractionAvailable = value;
}
public void ResetPanOrigin()
{
focusTween?.Kill();
currentOffset = Vector3.zero;
initialPosition = PanTransform.position;
}
public Tween MoveFocusTo(Transform target, float duration, Vector2 minBounds, Vector2 maxBounds, Vector2 viewportSize)
{
if (target == null)
{
return null;
}
Vector3 focusPosition = ClampFocusPosition(target.position, minBounds, maxBounds, viewportSize);
Vector3 targetPanPosition;
if (UsesCinemachinePan)
{
targetPanPosition = ClampFocusPosition(
new Vector3(focusPosition.x, focusPosition.y, PanTransform.position.z),
minBounds,
maxBounds,
viewportSize);
}
else
{
Vector3 viewPosition = ViewPosition;
viewPosition.z = focusPosition.z;
targetPanPosition = PanTransform.position + viewPosition - focusPosition;
targetPanPosition.z = PanTransform.position.z;
}
initialPosition = targetPanPosition;
focusTween?.Kill();
if (duration <= 0f)
{
PanTransform.position = initialPosition + currentOffset;
focusTween = null;
return null;
}
focusTween = PanTransform.DOMove(initialPosition + currentOffset, duration).SetEase(Ease.InOutSine);
return focusTween;
}
public IEnumerator WaitForFocusMove(float fallbackDuration = 0f)
{
if (focusTween != null && focusTween.IsActive())
{
yield return focusTween.WaitForCompletion();
yield break;
}
if (fallbackDuration > 0f)
{
yield return new WaitForSeconds(fallbackDuration);
}
}
private void Start()
{
initialPosition = PanTransform.position;
targetIndicator.SetActive(false);
eyemanager = FindObjectOfType<EyeSystem>();
if (eyemanager == null)
{
Debug.LogError("[MouseFollowAndZoom] EyeSystem 未找到。", this);
}
RegisterDialogEvents();
}
private void Update()
{
if (!isActive)
{
targetIndicator.SetActive(false);
return;
}
UpdateDialogCooldown();
HandleTargetIndicator();
if (isLocked)
{
return;
}
HandleMouseMovement();
UpdateCurrentTarget();
}
private void HandleMouseMovement()
{
Camera cameraForInput = GetInputCamera();
if (cameraForInput == null)
{
return;
}
Vector3 mousePosition = GetMouseScreenPosition();
Vector3 worldMousePosition = cameraForInput.ScreenToWorldPoint(mousePosition);
worldMousePosition.z = 0;
// 视差锚点用 initialPosition,避免相机跟随平移后 delta 被抵消
Vector3 parallaxAnchor = UsesCinemachinePan ? initialPosition : ViewPosition;
parallaxAnchor.z = 0;
Vector3 delta = UsesCinemachinePan
? worldMousePosition - parallaxAnchor
: parallaxAnchor - worldMousePosition;
Vector3 targetOffset = delta * parallaxEffectMultiplier;
targetOffset.x = Mathf.Clamp(targetOffset.x, -maxOffset.x, maxOffset.x);
targetOffset.y = Mathf.Clamp(targetOffset.y, -maxOffset.y, maxOffset.y);
currentOffset = Vector3.Lerp(currentOffset, targetOffset, 0.01f);
Vector3 targetPosition = initialPosition + currentOffset;
if (UsesCinemachinePan)
{
Vector2 parallaxSlack = maxOffset * parallaxEffectMultiplier;
targetPosition = ClampFocusPositionWithSlack(
targetPosition,
worldRegionMin,
worldRegionMax,
GetViewportSize(),
parallaxSlack);
}
else
{
targetPosition.x = Mathf.Clamp(targetPosition.x, sceneBoundsMin.x, sceneBoundsMax.x);
targetPosition.y = Mathf.Clamp(targetPosition.y, sceneBoundsMin.y, sceneBoundsMax.y);
}
targetPosition.z = PanTransform.position.z;
PanTransform.position = Vector3.Lerp(PanTransform.position, targetPosition, 0.1f);
}
private void UpdateCurrentTarget()
{
Camera cameraForInput = GetInputCamera();
if (cameraForInput == null)
{
currentTarget = null;
return;
}
Vector3 worldPosition = cameraForInput.ScreenToWorldPoint(GetMouseScreenPosition());
worldPosition.z = 0;
RaycastHit2D hit = Physics2D.Raycast(worldPosition, Vector2.zero);
if (hit.collider != null && hit.collider.CompareTag("EyeTarget"))
{
currentTarget = hit.collider.GetComponent<EyeTarget>();
if (Input.GetMouseButtonDown(0) && _isInteractionActive && _isInteractionAvailable && !_isDialogCooldownActive)
{
if (currentTarget != null)
{
eyemanager.SetTarget(currentTarget);
}
else
{
Debug.LogWarning("[MouseFollowAndZoom] Collider 缺少 EyeTarget 组件。", hit.collider);
}
}
}
else
{
currentTarget = null;
}
}
private Vector3 GetMouseScreenPosition()
{
Vector3 mousePosition = Input.mousePosition;
mousePosition.x -= mouseScreenOffsetX;
mousePosition.z = GetInputCamera() != null
? GetInputCamera().nearClipPlane
: mousePosition.z;
return mousePosition;
}
private void HandleTargetIndicator()
{
if (currentTarget == null || !_isInteractionActive)
{
targetIndicator.SetActive(false);
return;
}
BoxCollider2D boxCollider = currentTarget.GetComponent<BoxCollider2D>();
if (boxCollider == null)
{
return;
}
Vector3 boxCenter = boxCollider.bounds.center;
Vector3 boxSize = boxCollider.bounds.size;
RectTransform indicatorRectTransform = targetIndicator.GetComponent<RectTransform>();
indicatorRectTransform.position = boxCenter;
indicatorRectTransform.sizeDelta = new Vector2(boxSize.x, boxSize.y);
Color indicatorColor = currentTarget.GetIndicatorColor();
targetNameText.text = currentTarget.name + " " +
"R" + indicatorColor.r.ToString("F1") +
" G" + indicatorColor.g.ToString("F1") +
" B" + indicatorColor.b.ToString("F1");
targetIndicator.SetActive(true);
}
public void SetIndicatorTarget(EyeTarget target)
{
currentTarget = target;
}
private Camera GetInputCamera()
{
if (UsesCinemachinePan && CameraKit.Instance != null)
{
return CameraKit.Instance.GetCurrentCamera();
}
if (legacyInputCamera != null)
{
return legacyInputCamera;
}
return Camera.main;
}
private Vector2 GetViewportSize()
{
Camera cam = GetInputCamera();
if (cam == null || !cam.orthographic)
{
return Vector2.zero;
}
float height = cam.orthographicSize * 2f;
return new Vector2(height * cam.aspect, height);
}
private static Vector3 ClampFocusPositionWithSlack(
Vector3 targetPosition,
Vector2 minBounds,
Vector2 maxBounds,
Vector2 viewportSize,
Vector2 slack)
{
if (maxBounds.x > minBounds.x)
{
float halfWidth = Mathf.Max(0f, viewportSize.x * 0.5f);
targetPosition.x = Mathf.Clamp(
targetPosition.x,
minBounds.x + halfWidth - slack.x,
maxBounds.x - halfWidth + slack.x);
}
if (maxBounds.y > minBounds.y)
{
float halfHeight = Mathf.Max(0f, viewportSize.y * 0.5f);
targetPosition.y = Mathf.Clamp(
targetPosition.y,
minBounds.y + halfHeight - slack.y,
maxBounds.y - halfHeight + slack.y);
}
return targetPosition;
}
private static Vector3 ClampFocusPosition(Vector3 targetPosition, Vector2 minBounds, Vector2 maxBounds, Vector2 viewportSize)
{
if (maxBounds.x > minBounds.x)
{
float halfWidth = Mathf.Max(0f, viewportSize.x * 0.5f);
targetPosition.x = Mathf.Clamp(targetPosition.x, minBounds.x + halfWidth, maxBounds.x - halfWidth);
}
if (maxBounds.y > minBounds.y)
{
float halfHeight = Mathf.Max(0f, viewportSize.y * 0.5f);
targetPosition.y = Mathf.Clamp(targetPosition.y, minBounds.y + halfHeight, maxBounds.y - halfHeight);
}
return targetPosition;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Vector2 min = UsesCinemachinePan ? worldRegionMin : sceneBoundsMin;
Vector2 max = UsesCinemachinePan ? worldRegionMax : sceneBoundsMax;
Vector3 bottomLeft = new Vector3(min.x, min.y, 0);
Vector3 topRight = new Vector3(max.x, max.y, 0);
Gizmos.DrawLine(bottomLeft, new Vector3(bottomLeft.x, topRight.y, 0));
Gizmos.DrawLine(bottomLeft, new Vector3(topRight.x, bottomLeft.y, 0));
Gizmos.DrawLine(topRight, new Vector3(bottomLeft.x, topRight.y, 0));
Gizmos.DrawLine(topRight, new Vector3(topRight.x, bottomLeft.y, 0));
}
private void RegisterDialogEvents()
{
EnumEventSystem.Global.Register(InteractionEventEnum.DialogEnd, OnDialogueComplete);
}
private void OnDialogueComplete()
{
_lastDialogEndTime = Time.time;
_isDialogCooldownActive = true;
}
private void UpdateDialogCooldown()
{
if (_isDialogCooldownActive && Time.time - _lastDialogEndTime >= _dialogEndCooldown)
{
_isDialogCooldownActive = false;
}
}
private void OnDestroy()
{
focusTween?.Kill();
EnumEventSystem.Global.UnRegister(InteractionEventEnum.DialogEnd, OnDialogueComplete);
}
public bool IsActive => _isInteractionActive && !_isDialogCooldownActive;
public bool IsAvailable => _isInteractionAvailable && !_isDialogCooldownActive;
public GameObject GetGameObject()
{
return gameObject;
}
}
}