// 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();
}
}