多轮审查和修复
This commit is contained in:
18
Assets/Scripts/World/Puzzle/PuzzleDoor.cs
Normal file
18
Assets/Scripts/World/Puzzle/PuzzleDoor.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Assets/Scripts/World/Puzzle/PuzzleDoor.cs
|
||||
// 门型接收器——PuzzleReceiver 子类(Architecture 21_LiquidPuzzleModule §10)
|
||||
using Animancer;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Puzzle
|
||||
{
|
||||
/// <summary>谜题门:激活时播放开门动画,停用时播放关门动画。</summary>
|
||||
public class PuzzleDoor : PuzzleReceiver
|
||||
{
|
||||
[SerializeField] private AnimancerComponent _animancer;
|
||||
[SerializeField] private AnimationClip _openClip;
|
||||
[SerializeField] private AnimationClip _closeClip;
|
||||
|
||||
protected override void OnActivate() => _animancer?.Play(_openClip);
|
||||
protected override void OnDeactivate() => _animancer?.Play(_closeClip);
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/World/Puzzle/PuzzleDoor.cs.meta
Normal file
11
Assets/Scripts/World/Puzzle/PuzzleDoor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 872d675c9ad51954e89048ab1718ec2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Assets/Scripts/World/Puzzle/PuzzleInterfaces.cs
Normal file
33
Assets/Scripts/World/Puzzle/PuzzleInterfaces.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// Assets/Scripts/World/Puzzle/PuzzleInterfaces.cs
|
||||
// 谜题系统核心接口(Architecture 21_LiquidPuzzleModule §8)
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Puzzle
|
||||
{
|
||||
/// <summary>任何可被切换激活/停用状态的谜题元素。</summary>
|
||||
public interface ISwitchable
|
||||
{
|
||||
bool IsActive { get; }
|
||||
event Action<bool> OnStateChanged;
|
||||
|
||||
/// <summary>SaveData 恢复时调用,强制设置状态不触发副作用逻辑。</summary>
|
||||
void ForceState(bool active);
|
||||
}
|
||||
|
||||
/// <summary>可被玩家推动的物件(需 Rigidbody2D)。</summary>
|
||||
public interface IMovable
|
||||
{
|
||||
bool CanBePushed { get; }
|
||||
void OnPushStart(Vector2 direction);
|
||||
void OnPushEnd();
|
||||
}
|
||||
|
||||
/// <summary>接受激活信号后改变自身状态的物件。</summary>
|
||||
public interface IActivatable
|
||||
{
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
bool IsActivated { get; }
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/World/Puzzle/PuzzleInterfaces.cs.meta
Normal file
11
Assets/Scripts/World/Puzzle/PuzzleInterfaces.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60e23ba6f1db851418d4bc83031b7aa3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
57
Assets/Scripts/World/Puzzle/PuzzleReceiver.cs
Normal file
57
Assets/Scripts/World/Puzzle/PuzzleReceiver.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// Assets/Scripts/World/Puzzle/PuzzleReceiver.cs
|
||||
using BaseGames.World;
|
||||
using MoreMountains.Feedbacks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Puzzle
|
||||
{
|
||||
/// <summary>
|
||||
/// 谜题接收器,由 PuzzleWire 驱动。
|
||||
/// 挂在谜题目标物件上(门/平台等),实现 IActivatable。
|
||||
/// 子类覆写 OnActivate / OnDeactivate 实现具体行为。
|
||||
/// </summary>
|
||||
public class PuzzleReceiver : MonoBehaviour, IActivatable
|
||||
{
|
||||
[SerializeField] private bool _startsActivated = false;
|
||||
[SerializeField] private string _receiverId; // 持久化唯一 ID(空串则不持久化)
|
||||
[SerializeField] private MMFeedbacks _activateFeedback;
|
||||
[SerializeField] private MMFeedbacks _deactivateFeedback;
|
||||
|
||||
[Header("持久化(SO 注入,非 Instance 单例)")]
|
||||
[SerializeField] private WorldStateRegistry _worldState;
|
||||
|
||||
private bool _isActivated;
|
||||
public bool IsActivated => _isActivated;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
_isActivated = _startsActivated;
|
||||
if (_isActivated) OnActivate();
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
if (_isActivated) return;
|
||||
_isActivated = true;
|
||||
_activateFeedback?.PlayFeedbacks();
|
||||
OnActivate();
|
||||
|
||||
if (!string.IsNullOrEmpty(_receiverId))
|
||||
_worldState?.SetFlag("receiver_" + _receiverId);
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
if (!_isActivated) return;
|
||||
_isActivated = false;
|
||||
_deactivateFeedback?.PlayFeedbacks();
|
||||
OnDeactivate();
|
||||
|
||||
if (!string.IsNullOrEmpty(_receiverId))
|
||||
_worldState?.ClearFlag("receiver_" + _receiverId);
|
||||
}
|
||||
|
||||
protected virtual void OnActivate() { }
|
||||
protected virtual void OnDeactivate() { }
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/World/Puzzle/PuzzleReceiver.cs.meta
Normal file
11
Assets/Scripts/World/Puzzle/PuzzleReceiver.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 900ec271407dc2c4782ba8e474c1400d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
98
Assets/Scripts/World/Puzzle/PuzzleSwitch.cs
Normal file
98
Assets/Scripts/World/Puzzle/PuzzleSwitch.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
// Assets/Scripts/World/Puzzle/PuzzleSwitch.cs
|
||||
using System;
|
||||
using Animancer;
|
||||
using BaseGames.World;
|
||||
using MoreMountains.Feedbacks;
|
||||
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 MMFeedbacks _activateFeedback;
|
||||
|
||||
[Header("持久化(SO 注入,非 Instance 单例)")]
|
||||
[SerializeField] private WorldStateRegistry _worldState;
|
||||
|
||||
private bool _isActive;
|
||||
|
||||
public bool IsActive => _isActive;
|
||||
public event Action<bool> OnStateChanged;
|
||||
|
||||
private void Start() => _isActive = _startsActive;
|
||||
|
||||
// ── IInteractable ────────────────────────────────────────────────────
|
||||
public string InteractPrompt => _mode == SwitchTriggerMode.Hold ? "按住交互" : "交互";
|
||||
public bool CanInteract => true;
|
||||
|
||||
public void Interact(Transform player)
|
||||
{
|
||||
if (_mode == SwitchTriggerMode.InteractOnce && _isActive) return;
|
||||
SetState(!_isActive);
|
||||
}
|
||||
|
||||
public void OnPlayerEnterRange(Transform player) { }
|
||||
public void OnPlayerExitRange() { }
|
||||
|
||||
// ── 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?.PlayFeedbacks();
|
||||
OnStateChanged?.Invoke(active);
|
||||
|
||||
// 持久化到 WorldStateRegistry(激活添加标记;停用清除标记)
|
||||
if (!string.IsNullOrEmpty(_switchId))
|
||||
{
|
||||
if (active) _worldState?.SetFlag("switch_" + _switchId);
|
||||
else _worldState?.ClearFlag("switch_" + _switchId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/World/Puzzle/PuzzleSwitch.cs.meta
Normal file
11
Assets/Scripts/World/Puzzle/PuzzleSwitch.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49b0e5504832e054888d1568aa970a7b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Assets/Scripts/World/Puzzle/PuzzleWire.cs
Normal file
51
Assets/Scripts/World/Puzzle/PuzzleWire.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// Assets/Scripts/World/Puzzle/PuzzleWire.cs
|
||||
// 逻辑连接器:将一组 PuzzleSwitch 连接到 PuzzleReceiver(Architecture 21_LiquidPuzzleModule §11)
|
||||
// 支持 AND / OR / XOR 激活逻辑,Inspector 中配置,无需代码
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Puzzle
|
||||
{
|
||||
public enum LogicType { AND, OR, XOR }
|
||||
|
||||
/// <summary>
|
||||
/// 连接一个或多个 PuzzleSwitch 到 PuzzleReceiver。
|
||||
/// 支持 AND / OR / XOR 激活逻辑。
|
||||
/// 关卡设计师在 Inspector 中配置,无需编写代码。
|
||||
/// </summary>
|
||||
public class PuzzleWire : MonoBehaviour
|
||||
{
|
||||
[Header("输入开关")]
|
||||
[SerializeField] private PuzzleSwitch[] _switches;
|
||||
|
||||
[Header("激活逻辑")]
|
||||
[SerializeField] private LogicType _logic = LogicType.AND;
|
||||
|
||||
[Header("目标接收器")]
|
||||
[SerializeField] private PuzzleReceiver _receiver;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (_switches != null)
|
||||
foreach (var sw in _switches)
|
||||
if (sw != null) sw.OnStateChanged += _ => Evaluate();
|
||||
Evaluate(); // 初始求值
|
||||
}
|
||||
|
||||
private void Evaluate()
|
||||
{
|
||||
if (_switches == null || _receiver == null) return;
|
||||
|
||||
bool shouldActivate = _logic switch
|
||||
{
|
||||
LogicType.AND => System.Array.TrueForAll(_switches, s => s != null && s.IsActive),
|
||||
LogicType.OR => System.Array.Exists(_switches, s => s != null && s.IsActive),
|
||||
LogicType.XOR => _switches.Count(s => s != null && s.IsActive) % 2 == 1,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if (shouldActivate) _receiver.Activate();
|
||||
else _receiver.Deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/World/Puzzle/PuzzleWire.cs.meta
Normal file
11
Assets/Scripts/World/Puzzle/PuzzleWire.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 371d40df91ac7314c96eaea30272441f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user