PlayerMovement:新增 _facingLocked 字段 + LockFacing(bool) 方法;UpdateFacing() 锁定时直接返回 WallSlideState:OnStateEnter 调用 LockFacing(true) + FlipFacing(_wallDir);OnStateExit 调用 LockFacing(false) 解锁 WallJumpState:OnStateEnter 保险性再调一次 LockFacing(false);WallJumpAway/Toward 同步写入 _inputVelocityX,确保解锁后 UpdateFacing 朝向正确(背墙跳 = 离墙方向,对墙跳 = 朝墙方向)
154 lines
6.1 KiB
C#
154 lines
6.1 KiB
C#
// 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, // 按住交互键持续激活
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用谜题开关,支持三种触发模式。
|
||
/// 实现 ISwitchable + IInteractable(玩家手动触发)。
|
||
/// </summary>
|
||
[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<bool> 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 ? "按住交互" : "交互";
|
||
|
||
/// <summary>
|
||
/// 压板模式不需要交互提示(物理触发);InteractOnce 已激活后隐藏提示。
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|