PlayerMovement:新增 _facingLocked 字段 + LockFacing(bool) 方法;UpdateFacing() 锁定时直接返回 WallSlideState:OnStateEnter 调用 LockFacing(true) + FlipFacing(_wallDir);OnStateExit 调用 LockFacing(false) 解锁 WallJumpState:OnStateEnter 保险性再调一次 LockFacing(false);WallJumpAway/Toward 同步写入 _inputVelocityX,确保解锁后 UpdateFacing 朝向正确(背墙跳 = 离墙方向,对墙跳 = 朝墙方向)
126 lines
5.0 KiB
C#
126 lines
5.0 KiB
C#
using BaseGames.Core.Events;
|
||
using BaseGames.Feedback;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.World
|
||
{
|
||
/// <summary>
|
||
/// 单向触发机关。支持玩家按键/玩家接触/攻击三种触发方式。
|
||
/// 通过 VoidEventChannelSO 零耦合激活/停用目标(门/升降台/灯光等)。
|
||
/// _isOneShot = true 时激活状态持久化到 WorldStateRegistry。
|
||
/// </summary>
|
||
[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 ──────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 判断碰撞体是否为有效触发来源。子类可覆写以扩展触发主体(如幻影身体)。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|