Files
zeling_v2/Assets/Scripts/World/DirectionalInteractable.cs
2026-05-12 15:34:08 +08:00

120 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using BaseGames.Core.Events;
using MoreMountains.Feedbacks;
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 MMF_Player _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 ──────────────────────────────────────────────────
private void OnTriggerEnter2D(Collider2D other)
{
if (_triggerCondition != TriggerCondition.PlayerBody) return;
if (!other.CompareTag("Player")) return;
if (!CheckSide(other.transform.position)) return;
TryActivate();
}
private void OnTriggerExit2D(Collider2D other)
{
if (_triggerCondition != TriggerCondition.PlayerBody) return;
if (!other.CompareTag("Player") || _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?.PlayFeedbacks();
_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()
{
// 读档恢复:若机关已激活则静默还原
if (_isOneShot && !string.IsNullOrEmpty(_interactableId)
&& _worldState != null
&& _worldState.HasFlag("mechanism_" + _interactableId))
{
_activated = true;
_activationChannel?.Raise();
}
}
}
}