237 lines
6.7 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
|