// Assets/Scripts/World/Puzzle/PuzzleSwitch.cs using System; using Animancer; using BaseGames.Feedback; using BaseGames.Input; using BaseGames.World; using UnityEngine; namespace BaseGames.Puzzle { public enum SwitchTriggerMode { InteractOnce, // 玩家交互一次,永久激活 InteractToggle, // 玩家交互切换开关 Pressure, // 踩上激活,离开停用 Hold, // 按住交互键持续激活 } /// /// 通用谜题开关,支持三种触发模式。 /// 实现 ISwitchable + IInteractable(玩家手动触发)。 /// [RequireComponent(typeof(Collider2D))] public class PuzzleSwitch : MonoBehaviour, ISwitchable, IInteractable { [Header("触发模式")] [SerializeField] private SwitchTriggerMode _mode = SwitchTriggerMode.InteractOnce; [Header("状态")] [SerializeField] private bool _startsActive = false; [SerializeField] private string _switchId; // 持久化唯一 ID(空串则不持久化) [Header("视觉")] [SerializeField] private AnimancerComponent _animancer; [SerializeField] private AnimationClip _activeClip; [SerializeField] private AnimationClip _inactiveClip; [SerializeField] private SceneFeedback _activateFeedback; [Header("持久化(SO 注入,非 Instance 单例)")] [SerializeField] private WorldStateRegistry _worldState; [Header("Hold 模式输入(SwitchTriggerMode.Hold 时必填)")] [SerializeField] private InputReaderSO _inputReader; private bool _isActive; public bool IsActive => _isActive; public event Action OnStateChanged; private void Awake() { bool savedState = !string.IsNullOrEmpty(_switchId) && _worldState != null && _worldState.HasFlag("switch_" + _switchId); _isActive = savedState || _startsActive; } private void Start() { if (_isActive && _activeClip != null) _animancer?.Play(_activeClip); else if (_inactiveClip != null) _animancer?.Play(_inactiveClip); } // ── IInteractable ──────────────────────────────────────────────────── public string InteractPrompt => _mode == SwitchTriggerMode.Hold ? "按住交互" : "交互"; /// /// 压板模式不需要交互提示(物理触发);InteractOnce 已激活后隐藏提示。 /// public bool CanInteract => _mode switch { SwitchTriggerMode.InteractOnce => !_isActive, SwitchTriggerMode.InteractToggle => true, SwitchTriggerMode.Hold => true, SwitchTriggerMode.Pressure => false, _ => false, }; public void Interact(Transform player) { // Hold 模式通过 OnPlayerEnterRange 订阅输入事件处理,此处不响应 if (_mode == SwitchTriggerMode.Hold) return; if (_mode == SwitchTriggerMode.InteractOnce && _isActive) return; SetState(!_isActive); } public void OnPlayerEnterRange(Transform player) { if (_mode != SwitchTriggerMode.Hold || _inputReader == null) return; _inputReader.InteractEvent += OnHoldStarted; _inputReader.InteractCancelledEvent += OnHoldCancelled; } public void OnPlayerExitRange() { UnsubscribeHold(); // 玩家离开范围时停用 Hold 开关 if (_mode == SwitchTriggerMode.Hold && _isActive) SetState(false); } // ── ISwitchable ────────────────────────────────────────────────────── public void ForceState(bool active) => SetState(active); // ── 压板模式 ────────────────────────────────────────────────────────── private void OnTriggerEnter2D(Collider2D col) { if (_mode != SwitchTriggerMode.Pressure) return; if (col.CompareTag("Player") || col.CompareTag("PushBox")) SetState(true); } private void OnTriggerExit2D(Collider2D col) { if (_mode != SwitchTriggerMode.Pressure) return; if (col.CompareTag("Player") || col.CompareTag("PushBox")) SetState(false); } private void SetState(bool active) { if (_isActive == active) return; _isActive = active; if (active) _animancer?.Play(_activeClip); else _animancer?.Play(_inactiveClip); _activateFeedback?.Play(); OnStateChanged?.Invoke(active); // 持久化到 WorldStateRegistry(激活添加标记;停用清除标记) if (!string.IsNullOrEmpty(_switchId)) { if (active) _worldState?.SetFlag("switch_" + _switchId); else _worldState?.ClearFlag("switch_" + _switchId); } } // ── Hold 模式辅助 ────────────────────────────────────────────────────── private void OnHoldStarted() => SetState(true); private void OnHoldCancelled() => SetState(false); private void UnsubscribeHold() { if (_inputReader == null) return; _inputReader.InteractEvent -= OnHoldStarted; _inputReader.InteractCancelledEvent -= OnHoldCancelled; } private void OnDestroy() => UnsubscribeHold(); } }