Files
2026-07-08 20:42:51 +08:00

237 lines
6.7 KiB
C#

using System;
using UnityEngine;
using UnityEngine.Events;
using DG.Tweening;
namespace Interaction
{
public class ToggleMoveInteractable : MonoBehaviour, IInteractable
{
public enum ToggleState
{
Off = 0,
On = 1
}
public enum InitialStateMode
{
Auto = 0,
ForceOff = 1,
ForceOn = 2
}
[Serializable]
public class MoveTarget
{
public Transform target;
[Header("轴选择")]
public bool useX = true;
public bool useY = true;
public bool useZ = true;
[Header("关闭状态 (Off)")]
public bool captureInitialAsOff = true;
public Vector3 offLocalPosition;
[Header("开启状态 (On)")]
public bool useOffsetForOn = true;
public Vector3 onOffset;
public Vector3 onLocalPosition;
public Vector3 GetOffLocalPosition()
{
return offLocalPosition;
}
public Vector3 GetOnLocalPosition()
{
return useOffsetForOn ? offLocalPosition + onOffset : onLocalPosition;
}
}
[Header("目标设置")]
[SerializeField] private MoveTarget[] targets;
[Header("初始化")]
[SerializeField] private InitialStateMode initialState = InitialStateMode.Auto;
[SerializeField] private ToggleState state = ToggleState.Off;
[SerializeField] private bool applyInitialStateOnStart = true;
[Header("动画设置")]
[SerializeField] private float duration = 1.0f;
[SerializeField] private Ease animationEase = Ease.OutQuad;
[Header("交互提示")]
[SerializeField] private string promptWhenOff = "打开";
[SerializeField] private string promptWhenOn = "关闭";
[Header("事件")]
[SerializeField] private UnityEvent onTurnedOn;
[SerializeField] private UnityEvent onTurnedOff;
[SerializeField] private UnityEvent<bool> onStateChanged;
public bool IsOn => state == ToggleState.On;
private void Start()
{
if (targets == null || targets.Length == 0)
{
Debug.LogWarning($"[{nameof(ToggleMoveInteractable)}] 未绑定任何目标对象。");
return;
}
CaptureInitialOffIfNeeded();
switch (initialState)
{
case InitialStateMode.ForceOn:
state = ToggleState.On;
break;
case InitialStateMode.ForceOff:
state = ToggleState.Off;
break;
default:
state = DetectState();
break;
}
if (applyInitialStateOnStart)
{
ApplyStateToTargets(false);
}
}
public void Interact()
{
Toggle(true);
}
public void Toggle(bool animate = true)
{
SetState(IsOn ? ToggleState.Off : ToggleState.On, animate);
}
public void SetState(ToggleState newState, bool animate = true)
{
if (targets == null || targets.Length == 0) return;
if (state == newState) return;
state = newState;
ApplyStateToTargets(animate);
bool isOn = IsOn;
onStateChanged?.Invoke(isOn);
if (isOn) onTurnedOn?.Invoke();
else onTurnedOff?.Invoke();
}
public string GetInteractPrompt()
{
return IsOn ? promptWhenOn : promptWhenOff;
}
private void CaptureInitialOffIfNeeded()
{
foreach (var t in targets)
{
if (t == null || t.target == null) continue;
if (!t.captureInitialAsOff) continue;
t.offLocalPosition = t.target.localPosition;
}
}
private void ApplyStateToTargets(bool animate)
{
foreach (var t in targets)
{
if (t == null || t.target == null) continue;
Vector3 configured = IsOn ? t.GetOnLocalPosition() : t.GetOffLocalPosition();
Vector3 desired = BuildDesiredLocalPosition(t, configured);
t.target.DOKill();
if (!animate || duration <= 0f)
{
t.target.localPosition = desired;
continue;
}
t.target.DOLocalMove(desired, duration)
.SetEase(animationEase);
}
}
private ToggleState DetectState()
{
float distOn = 0f;
float distOff = 0f;
bool hasAny = false;
foreach (var t in targets)
{
if (t == null || t.target == null) continue;
hasAny = true;
Vector3 current = t.target.localPosition;
Vector3 onPos = t.GetOnLocalPosition();
Vector3 offPos = t.GetOffLocalPosition();
bool useX = t.useX;
bool useY = t.useY;
bool useZ = t.useZ;
if (!useX && !useY && !useZ)
{
useX = true;
useY = true;
useZ = true;
}
if (useX)
{
distOn += Mathf.Abs(current.x - onPos.x);
distOff += Mathf.Abs(current.x - offPos.x);
}
if (useY)
{
distOn += Mathf.Abs(current.y - onPos.y);
distOff += Mathf.Abs(current.y - offPos.y);
}
if (useZ)
{
distOn += Mathf.Abs(current.z - onPos.z);
distOff += Mathf.Abs(current.z - offPos.z);
}
}
if (!hasAny) return ToggleState.Off;
return distOn < distOff ? ToggleState.On : ToggleState.Off;
}
private Vector3 BuildDesiredLocalPosition(MoveTarget t, Vector3 configured)
{
Vector3 current = t.target.localPosition;
bool useX = t.useX;
bool useY = t.useY;
bool useZ = t.useZ;
if (!useX && !useY && !useZ)
{
useX = true;
useY = true;
useZ = true;
}
if (useX) current.x = configured.x;
if (useY) current.y = configured.y;
if (useZ) current.z = configured.z;
return current;
}
}
}