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 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; } } }