多轮审查和修复

This commit is contained in:
2026-05-12 15:34:08 +08:00
parent f55d2a57c3
commit ebbbb7332e
805 changed files with 838724 additions and 1905 deletions

View 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);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 872d675c9ad51954e89048ab1718ec2d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 60e23ba6f1db851418d4bc83031b7aa3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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() { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 900ec271407dc2c4782ba8e474c1400d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 49b0e5504832e054888d1568aa970a7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
// Assets/Scripts/World/Puzzle/PuzzleWire.cs
// 逻辑连接器:将一组 PuzzleSwitch 连接到 PuzzleReceiverArchitecture 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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 371d40df91ac7314c96eaea30272441f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: