using BaseGames.Core.Events; using BaseGames.Feedback; using UnityEngine; namespace BaseGames.World { /// /// 单向触发机关。支持玩家按键/玩家接触/攻击三种触发方式。 /// 通过 VoidEventChannelSO 零耦合激活/停用目标(门/升降台/灯光等)。 /// _isOneShot = true 时激活状态持久化到 WorldStateRegistry。 /// [RequireComponent(typeof(Collider2D))] public class DirectionalInteractable : MonoBehaviour, IInteractable { public enum TriggerSide { Left, Right, Top, Any } public enum TriggerCondition { PlayerAttack, PlayerBody, InteractKey } [Header("触发条件")] [SerializeField] private TriggerSide _triggerSide = TriggerSide.Any; [SerializeField] private TriggerCondition _triggerCondition = TriggerCondition.InteractKey; [Header("行为")] [SerializeField] private bool _isOneShot; [SerializeField] private string _interactableId; [Header("事件频道")] [SerializeField] private VoidEventChannelSO _activationChannel; [SerializeField] private VoidEventChannelSO _deactivationChannel; [Header("反馈")] [SerializeField] private SceneFeedback _activateFeedback; [Header("持久化")] [SerializeField] private WorldStateRegistry _worldState; private bool _activated; // ── IInteractable ───────────────────────────────────────────────────── public bool CanInteract => !(_isOneShot && _activated); public string InteractPrompt => _activated ? "已激活" : "交互"; public void Interact(Transform player) { if (_triggerCondition != TriggerCondition.InteractKey) return; if (!CheckSide(player.position)) return; TryActivate(); } public void OnPlayerEnterRange(Transform player) { } public void OnPlayerExitRange() { } // ── Physics Triggers ────────────────────────────────────────────────── /// /// 判断碰撞体是否为有效触发来源。子类可覆写以扩展触发主体(如幻影身体)。 /// protected virtual bool IsValidTriggerBody(Collider2D col) => col.CompareTag("Player"); private void OnTriggerEnter2D(Collider2D other) { if (_triggerCondition != TriggerCondition.PlayerBody) return; if (!IsValidTriggerBody(other)) return; if (!CheckSide(other.transform.position)) return; TryActivate(); } private void OnTriggerExit2D(Collider2D other) { if (_triggerCondition != TriggerCondition.PlayerBody) return; if (!IsValidTriggerBody(other) || _isOneShot) return; _activated = false; _deactivationChannel?.Raise(); } // ── Damage-based Trigger(由 HurtBox/TileDamageReceiver 调用)──────── public void TryInteractFromDamage(BaseGames.Combat.DamageInfo info) { if (_triggerCondition != TriggerCondition.PlayerAttack) return; if (!CheckSide(info.SourcePosition)) return; TryActivate(); } // ── Core ────────────────────────────────────────────────────────────── protected void TryActivate() { if (_isOneShot && _activated) return; _activated = true; _activateFeedback?.Play(); _activationChannel?.Raise(); if (_isOneShot && !string.IsNullOrEmpty(_interactableId)) _worldState?.SetFlag("mechanism_" + _interactableId); } private bool CheckSide(Vector2 sourcePos) { if (_triggerSide == TriggerSide.Any) return true; var dir = (sourcePos - (Vector2)transform.position).normalized; return _triggerSide switch { TriggerSide.Left => dir.x < -0.4f, TriggerSide.Right => dir.x > 0.4f, TriggerSide.Top => dir.y > 0.4f, _ => true }; } private void Start() { // 读档恢复:仅标记 _activated = true,不广播激活事件。 // 下游组件(PuzzleReceiver / 动画门等)已通过各自的 WorldStateRegistry 检查在 Start 中自行恢复, // 重复广播会导致它们再次播放开门动画等副作用。 if (_isOneShot && !string.IsNullOrEmpty(_interactableId) && _worldState != null && _worldState.HasFlag("mechanism_" + _interactableId)) { _activated = true; } } } }