This commit is contained in:
2026-07-07 03:34:56 +08:00
parent e7891d7e00
commit ecb20cf370
81 changed files with 2108 additions and 293 deletions
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DXhN53n7Wn6X4mq4BzmVGcYyf4qgGr+dz9mS8D/RSs2VcTccKveruRE=
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 0}
m_Name: EchoReleased
m_EditorClassIdentifier: Assembly-CSharp:Architecture.Core:Vector3Event
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DCwXsH6lBn1xFo36tyRsjs+h4O+VaxEW/j5EUKPXE9wXY5c7QNmWpTw=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 0}
m_Name: Enemies
m_EditorClassIdentifier: Assembly-CSharp:Architecture.Core:TransformRuntimeSet
Items: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: WXNNtH6sVHk/AtKjh3pZHW8Y0Ud+sV/2q1BsD/usbR4CLijRwhOQiIk=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0d892c58b6fddaa4cb129dc82a1599bf, type: 3}
m_Name: OnGameOver
m_EditorClassIdentifier:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: CChMtHz5UioVBUTAdiP6msXTWBHbXvrlL/lrziGncpCasO91pyWEhyk=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0d892c58b6fddaa4cb129dc82a1599bf, type: 3}
m_Name: OnGameWin
m_EditorClassIdentifier:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: XH5L5HukAH7cHD+m8JFQuv4+Mj+RudGvKdJlEfvVp4JP/PFYo2XNvkY=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0d892c58b6fddaa4cb129dc82a1599bf, type: 3}
m_Name: OnPlayerDamaged
m_EditorClassIdentifier:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DnwWtS6sUHnS++rBiFqf1RewyFloSSTeXLbE2N2AYG05n2Ydap4Fwj0=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0d892c58b6fddaa4cb129dc82a1599bf, type: 3}
m_Name: OnPlayerDied
m_EditorClassIdentifier:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: By9K4Cr+VHyn1VhN1u6wwvxVZTtp7QakSbea38aO5PM+1kQ2/WvqpTk=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d031a592f871059438eecfab967281c5, type: 3}
m_Name: PlayerHealth
m_EditorClassIdentifier:
_value: 5
_defaultValue: 5
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: CXMat3n8Uine4euE0m+/PP+65iSUYO/kdbR4DpS1kOnmtBMCoGxpDXU=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 0}
m_Name: Players
m_EditorClassIdentifier: Assembly-CSharp:Architecture.Core:TransformRuntimeSet
Items: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DXkfsH+oAC3hF+c5rKlzwRKUa/umXiwsRPwwZnPWvvBN2Q0hWBBPanE=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d031a592f871059438eecfab967281c5, type: 3}
m_Name: Score
m_EditorClassIdentifier:
_value: 0
_defaultValue: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: WnIXtyivW3to970CZlNapENiq9hQEjUh+OqHFQyk+JfklXrmuCSE5fk=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 0}
m_Name: ScoreChanged
m_EditorClassIdentifier: Assembly-CSharp:Architecture.Core:IntEvent
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: WXNNvCuoVXP90sjsVOaNxagClFTPQl2e6lNPzXvzsn7Deww9UFDHgQ4=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 0}
m_Name: ScoreSettled
m_EditorClassIdentifier: Assembly-CSharp:Architecture.Core:IntEvent
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: Wi9KtSqvUiiCMcmSH6Ragy/X4YLegcpMnbruBTQrei7BTu3lyGrSIoc=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: CHwdtCmvUy1YMyYqJ0W+W9h32R9zeWPvveDocc+XzRh7umlTkIVFsPU=
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Architecture.Core
{
/// <summary>
/// 无参事件通道(ScriptableObject 资产)。
/// 系统 / UI 在 Inspector 中拖入同一个资产实例即可实现解耦通信,
/// 彻底替代 static Action 事件与单例方法调用。
/// 注意:资产实例是项目级资源,跨场景天然存活,无需 DontDestroyOnLoad。
/// </summary>
[CreateAssetMenu(menuName = "Architecture/Events/Game Event", fileName = "NewGameEvent")]
public class GameEvent : ScriptableObject
{
private readonly List<GameEventListener> _listeners = new List<GameEventListener>();
private event System.Action _codeListeners;
public void Raise()
{
// Inspector 监听器(设计师配置的 UnityEvent 响应)优先于代码监听器触发,
// 保证表现层(UI 动画/音效)在逻辑层之前响应事件。
for (int i = _listeners.Count - 1; i >= 0; i--)
_listeners[i].OnEventRaised();
_codeListeners?.Invoke();
}
/// <summary>Inspector 监听器(GameEventListener 组件)注册。</summary>
public void RegisterListener(GameEventListener listener)
{
if (!_listeners.Contains(listener)) _listeners.Add(listener);
}
public void UnregisterListener(GameEventListener listener)
{
if (_listeners.Contains(listener)) _listeners.Remove(listener);
}
/// <summary>代码监听器(Action)注册,便于 MonoBehaviour 在 OnEnable 中订阅,
/// 是替换 static Action 事件的主要入口。</summary>
public void Register(System.Action listener)
{
if (listener != null) _codeListeners += listener;
}
public void Unregister(System.Action listener)
{
if (listener != null) _codeListeners -= listener;
}
}
/// <summary>
/// 无参事件监听器(设计师友好,纯 Inspector 配置)。
/// 监听某个 GameEvent,触发时调用绑定的 UnityEvent 响应。
/// </summary>
public class GameEventListener : MonoBehaviour
{
[SerializeField] private GameEvent _event;
[SerializeField] private UnityEvent _response;
private void OnEnable() => _event?.RegisterListener(this);
private void OnDisable() => _event?.UnregisterListener(this);
public void OnEventRaised() => _response?.Invoke();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: Dy4XvCj+VnPPG8II9z1gbnWGNyb39F6pQKS/CfIEjQntIaaEOm2XcP4=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace Architecture.Core
{
/// <summary>
/// 带载荷的泛型事件通道。用于在调用方之间传递数据,而无需 static 事件或单例。
/// 具体类型(IntEvent / FloatEvent 等)可在 Inspector 右键 Create。
/// 系统内部订阅用 event.Register(Action&lt;T&gt;);设计师驱动用对应 *EventListener 组件。
/// </summary>
public abstract class GameEvent<T> : ScriptableObject
{
private readonly List<Action<T>> _listeners = new List<Action<T>>();
public void Raise(T payload)
{
for (int i = _listeners.Count - 1; i >= 0; i--)
_listeners[i]?.Invoke(payload);
}
public void Register(Action<T> listener)
{
if (!_listeners.Contains(listener)) _listeners.Add(listener);
}
public void Unregister(Action<T> listener)
{
if (_listeners.Contains(listener)) _listeners.Remove(listener);
}
}
[CreateAssetMenu(menuName = "Architecture/Events/Int Event", fileName = "NewIntEvent")]
public class IntEvent : GameEvent<int> { }
[CreateAssetMenu(menuName = "Architecture/Events/Float Event", fileName = "NewFloatEvent")]
public class FloatEvent : GameEvent<float> { }
[CreateAssetMenu(menuName = "Architecture/Events/Vector3 Event", fileName = "NewVector3Event")]
public class Vector3Event : GameEvent<Vector3> { }
[CreateAssetMenu(menuName = "Architecture/Events/String Event", fileName = "NewStringEvent")]
public class StringEvent : GameEvent<string> { }
[CreateAssetMenu(menuName = "Architecture/Events/GameObject Event", fileName = "NewGameObjectEvent")]
public class GameObjectEvent : GameEvent<GameObject> { }
// ===== 带载荷的监听器组件(Inspector 驱动)=====
public class IntEventListener : MonoBehaviour
{
[SerializeField] private IntEvent _event;
[SerializeField] private UnityEvent<int> _response;
private void OnEnable() => _event?.Register(Respond);
private void OnDisable() => _event?.Unregister(Respond);
private void Respond(int v) => _response?.Invoke(v);
}
public class FloatEventListener : MonoBehaviour
{
[SerializeField] private FloatEvent _event;
[SerializeField] private UnityEvent<float> _response;
private void OnEnable() => _event?.Register(Respond);
private void OnDisable() => _event?.Unregister(Respond);
private void Respond(float v) => _response?.Invoke(v);
}
public class Vector3EventListener : MonoBehaviour
{
[SerializeField] private Vector3Event _event;
[SerializeField] private UnityEvent<Vector3> _response;
private void OnEnable() => _event?.Register(Respond);
private void OnDisable() => _event?.Unregister(Respond);
private void Respond(Vector3 v) => _response?.Invoke(v);
}
public class GameObjectEventListener : MonoBehaviour
{
[SerializeField] private GameObjectEvent _event;
[SerializeField] private UnityEvent<GameObject> _response;
private void OnEnable() => _event?.Register(Respond);
private void OnDisable() => _event?.Unregister(Respond);
private void Respond(GameObject v) => _response?.Invoke(v);
}
/// <summary>
/// 字符串事件监听器组件。补齐 Int/Float/Vector3/GameObject 之后遗漏的 String 通道监听器,
/// 使策划能在 Inspector 里为 StringEvent 连线响应(例如传递场景名、提示文本)。
/// </summary>
public class StringEventListener : MonoBehaviour
{
[SerializeField] private StringEvent _event;
[SerializeField] private UnityEvent<string> _response;
private void OnEnable() => _event?.Register(Respond);
private void OnDisable() => _event?.Unregister(Respond);
private void Respond(string v) => _response?.Invoke(v);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: Wn0Y43n4BSqQmCBSQfcVumlArfA4ONJNN2iHuAFNyRze987Ocx2MGyY=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: B31O5CulAS1WrdnBvd8elA4AXddHMwLYIMjEJbxVqMOjBnaYzy4S8LU=
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,72 @@
using UnityEngine;
using UnityEditor;
using Architecture.Core;
using Architecture.Variables;
namespace Architecture.Editor
{
/// <summary>
/// 一键生成 P1/P2 所需的 SO 通信资产(变量 / 事件 / 运行时集合)。
/// 运行方式:Unity 顶部菜单 Architecture > Bootstrap Core Assets。
/// 生成的资产位于 Assets/Architecture/Assets/,可安全删改、重新生成(已存在则跳过)。
///
/// 注意:本脚本仅生成"空壳资产",具体系统需在 Inspector 中将对应字段拖入这些资产实例,
/// 才能建立解耦链路(详见 Docs/解耦架构重构方案.md)。
/// </summary>
public static class AssetBootstrap
{
private const string Root = "Assets/Architecture/Assets";
[MenuItem("Architecture/Bootstrap Core Assets")]
public static void Bootstrap()
{
if (!AssetDatabase.IsValidFolder(Root))
AssetDatabase.CreateFolder("Assets/Architecture", "Assets");
// 共享变量(同时设置 _value 与 _defaultValue,避免跨 Play Mode 状态泄漏)
CreateInt("PlayerHealth", 5);
CreateInt("Score", 0);
// 无参事件通道
Create<GameEvent>("OnPlayerDied");
Create<GameEvent>("OnGameOver");
Create<GameEvent>("OnGameWin");
Create<GameEvent>("OnPlayerDamaged");
// 带载荷事件通道
Create<Vector3Event>("EchoReleased");
Create<IntEvent>("ScoreChanged");
Create<IntEvent>("ScoreSettled");
// 运行时集合(当前场景实体注册表)
Create<TransformRuntimeSet>("Players");
Create<TransformRuntimeSet>("Enemies");
AssetDatabase.SaveAssets();
Debug.Log($"[AssetBootstrap] 已生成核心 SO 资产到 {Root}");
}
private static T Create<T>(string name) where T : ScriptableObject
{
var path = $"{Root}/{name}.asset";
var existing = AssetDatabase.LoadAssetAtPath<T>(path);
if (existing != null)
{
Debug.Log($"[AssetBootstrap] 已存在,跳过:{path}");
return existing;
}
var asset = ScriptableObject.CreateInstance<T>();
AssetDatabase.CreateAsset(asset, path);
return asset;
}
private static void CreateInt(string name, int value)
{
var asset = Create<IntVariable>(name);
var so = new SerializedObject(asset);
so.FindProperty("_value").intValue = value;
so.FindProperty("_defaultValue").intValue = value;
so.ApplyModifiedProperties();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: Dn4fvSL+Bi5qpJQPKTiLAFU3fgR+KLt4w0Ql57XB14ROp4pNfqn9n84=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,43 @@
using UnityEditor;
using UnityEngine;
using Architecture.Variables;
namespace Architecture.Editor
{
/// <summary>
/// 变量资产的 Inspector 绘制器:在字段右侧实时显示当前值(含 Play 模式运行时值),
/// 设计师无需打开脚本即可看到数值,降低沟通成本。
/// </summary>
[CustomPropertyDrawer(typeof(FloatVariable))]
[CustomPropertyDrawer(typeof(IntVariable))]
[CustomPropertyDrawer(typeof(BoolVariable))]
[CustomPropertyDrawer(typeof(Vector3Variable))]
public class VariableDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
var target = property.objectReferenceValue as ScriptableObject;
if (target != null)
{
var so = new SerializedObject(target);
so.Update();
var valueProp = so.FindProperty("_value");
Rect objRect = new Rect(position.x, position.y, position.width * 0.58f, position.height);
Rect valRect = new Rect(position.x + position.width * 0.60f, position.y, position.width * 0.40f, position.height);
EditorGUI.ObjectField(objRect, property, GUIContent.none);
if (valueProp != null)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUI.PropertyField(valRect, valueProp, GUIContent.none);
EditorGUI.EndDisabledGroup();
}
}
else
{
EditorGUI.ObjectField(position, property, label);
}
EditorGUI.EndProperty();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: BnMa4CioUihgI9MvBm9J06Z6TNiVXXGlVT3vne3vk2CEiePGAFaObuc=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DXkasC+sU3ihXHypaAzLi0kYmkhZiiQpNsOF3Y4Ksr+Tt5M8BpbIw1w=
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,76 @@
using System.Collections.Generic;
using UnityEngine;
namespace Architecture.Core
{
/// <summary>
/// 运行时集合:替代 FindObjectsOfType / 单例列表,零单例开销地跟踪场景实体。
/// 实体在 OnEnable 注册、OnDisable 注销(见下方 RuntimeSetRegistrar)。
/// 资产实例是项目级资源,跨场景存活,可安全被任何系统读取。
/// </summary>
public abstract class RuntimeSet<T> : ScriptableObject
{
public List<T> Items = new List<T>();
public void Add(T item)
{
if (item != null && !Items.Contains(item)) Items.Add(item);
}
public void Remove(T item)
{
if (item != null && Items.Contains(item)) Items.Remove(item);
}
public int Count => Items.Count;
public T this[int i] => Items[i];
}
/// <summary>
/// 通用 Transform 集合,作为"当前场景实体"的轻量注册表(玩家、敌人等)。
/// 领域专用集合(如 EnemyRuntimeSet : RuntimeSet&lt;EnemyAI&gt;)放在对应领域文件夹,
/// 继承本类即可,无需改动核心。
/// </summary>
[CreateAssetMenu(menuName = "Architecture/Runtime Sets/Transform Set", fileName = "NewTransformSet")]
public class TransformRuntimeSet : RuntimeSet<Transform> { }
/// <summary>
/// 挂载到任意预制体,使其自动注册进指定 RuntimeSet。
/// 这样系统只需遍历集合,永不需要 GameObject.Find / FindObjectsOfType。
/// </summary>
public class RuntimeSetRegistrar : MonoBehaviour
{
[SerializeField] private TransformRuntimeSet _set;
private void OnEnable()
{
if (_set != null) _set.Add(transform);
}
private void OnDisable()
{
if (_set != null) _set.Remove(transform);
}
}
/// <summary>
/// 泛型注册器:领域专用强类型集合(如 <c>EnemyRuntimeSet : RuntimeSet&lt;EnemyAI&gt;</c>)使用。
/// 自动 <c>GetComponent&lt;T&gt;()</c> 并加入对应集合,避免用 Transform 集合再手动转型。
/// 因 Unity 对泛型 MonoBehaviour 需具体闭包类型才能挂载,领域文件夹内写一行具体子类即可:
/// <code>public class EnemySetRegistrar : RuntimeSetRegistrar&lt;EnemyAI&gt; { }</code>
/// </summary>
public class RuntimeSetRegistrar<T> : MonoBehaviour where T : Component
{
[SerializeField] private RuntimeSet<T> _set;
private void OnEnable()
{
if (_set != null) _set.Add(GetComponent<T>());
}
private void OnDisable()
{
if (_set != null) _set.Remove(GetComponent<T>());
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: By8XsSukWyonwLQZ/3RNaE8FAk+70HcGhhnyFRI8oY/tcLIntpkDvH4=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: CXMW4CP8BnnBCuaEto3WYyr2C3RLoOC5Jd5fjwZGKcsuUekgm8W5KFY=
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
using UnityEngine;
using System;
namespace Architecture.Variables
{
/// <summary>
/// 共享布尔变量(ScriptableObject 资产)。常用于暂停、开关等状态。
/// </summary>
[CreateAssetMenu(menuName = "Architecture/Variables/Bool", fileName = "NewBoolVariable")]
public class BoolVariable : ScriptableObject, Architecture.IVariable
{
[SerializeField] private bool _value;
[Tooltip("进入 Play Mode 时的初始值,由 VariableRegistry 在每次启动游戏时自动恢复,防止跨局状态泄漏")]
[SerializeField] private bool _defaultValue;
public bool Value
{
get => _value;
set
{
if (_value == value) return;
_value = value;
OnValueChanged?.Invoke(value);
}
}
public event Action<bool> OnValueChanged;
public void SetValue(bool value) => Value = value;
/// <summary>将运行时值重置为 _defaultValue(每次进入 Play Mode 时由 VariableRegistry 自动调用)。</summary>
public void ResetToDefault()
{
_value = _defaultValue;
OnValueChanged?.Invoke(_value);
}
[ContextMenu("Reset To Default")]
public void Reset() => ResetToDefault();
private void OnEnable() => VariableRegistry.Register(this);
private void OnDisable()
{
VariableRegistry.Unregister(this);
OnValueChanged = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: DXsdsCquB3geiEyhIkY2aFLJpbGuThlr6ky9pgS1Nnrno7aw3ZWk4Uc=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,52 @@
using UnityEngine;
using System;
namespace Architecture.Variables
{
/// <summary>
/// 共享浮点变量(ScriptableObject 资产)。
/// 任何系统可读取 / 写入,并由 OnValueChanged 事件驱动 UI 刷新,
/// 替代 MonoBehaviour 字段跨系统传递与逐帧反射读取。
/// </summary>
[CreateAssetMenu(menuName = "Architecture/Variables/Float", fileName = "NewFloatVariable")]
public class FloatVariable : ScriptableObject, Architecture.IVariable
{
[SerializeField] private float _value;
[Tooltip("进入 Play Mode 时的初始值,由 VariableRegistry 在每次启动游戏时自动恢复,防止跨局状态泄漏")]
[SerializeField] private float _defaultValue;
public float Value
{
get => _value;
set
{
if (Mathf.Approximately(_value, value)) return;
_value = value;
OnValueChanged?.Invoke(value);
}
}
public event Action<float> OnValueChanged;
public void SetValue(float value) => Value = value;
public void ApplyChange(float amount) => Value += amount;
/// <summary>将运行时值重置为 _defaultValue(每次进入 Play Mode 时由 VariableRegistry 自动调用)。</summary>
public void ResetToDefault()
{
_value = _defaultValue;
OnValueChanged?.Invoke(_value);
}
[ContextMenu("Reset To Default")]
public void Reset() => ResetToDefault();
private void OnEnable() => VariableRegistry.Register(this);
private void OnDisable()
{
VariableRegistry.Unregister(this);
OnValueChanged = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: DntJsS6uBnP85uXqLRZji/q5FaD10/2h7b+TePWWB+tWhBvOfF7TELg=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
using UnityEngine;
using System;
namespace Architecture.Variables
{
/// <summary>
/// 共享整数变量(ScriptableObject 资产)。常用于血量、得分、魂灵数等。
/// </summary>
[CreateAssetMenu(menuName = "Architecture/Variables/Int", fileName = "NewIntVariable")]
public class IntVariable : ScriptableObject, Architecture.IVariable
{
[SerializeField] private int _value;
[Tooltip("进入 Play Mode 时的初始值,由 VariableRegistry 在每次启动游戏时自动恢复,防止跨局状态泄漏")]
[SerializeField] private int _defaultValue;
public int Value
{
get => _value;
set
{
if (_value == value) return;
_value = value;
OnValueChanged?.Invoke(value);
}
}
public event Action<int> OnValueChanged;
public void SetValue(int value) => Value = value;
public void ApplyChange(int amount) => Value += amount;
/// <summary>将运行时值重置为 _defaultValue(每次进入 Play Mode 时由 VariableRegistry 自动调用)。</summary>
public void ResetToDefault()
{
_value = _defaultValue;
OnValueChanged?.Invoke(_value);
}
[ContextMenu("Reset To Default")]
public void Reset() => ResetToDefault();
private void OnEnable() => VariableRegistry.Register(this);
private void OnDisable()
{
VariableRegistry.Unregister(this);
OnValueChanged = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: W3octHuoWnnZAVrTuCOazcbeHxKYqo5abD3dZ/KJEBmkydHqGDyV9qE=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using UnityEngine;
namespace Architecture
{
/// <summary>
/// 共享变量统一接口:所有 Variable 资产实现它,注册表才能统一重置。
/// </summary>
public interface IVariable
{
void ResetToDefault();
}
/// <summary>
/// 共享变量注册表 —— 运行时状态防泄漏的核心。
///
/// <b>问题</b>ScriptableObject 资产在 Editor 下跨 Play Mode 域重载会持久化其序列化值。
/// 例如 PlayerHealth.Value 在游玩中变为 0 后退出 Play Mode,若残留为 0,
/// 下次进入 Play Mode 可能直接触发死亡(或分数残留、开关状态错乱)。
///
/// <b>解决</b>:每个 Variable 在 OnEnable 时自动注册;每次进入 Play Mode(域加载前)
/// 由 [RuntimeInitializeOnLoadMethod] 统一调用 ResetToDefault()
/// 将运行时值恢复到设计器设置的 _defaultValue,杜绝跨局状态泄漏。
///
/// 注意:RuntimeInitializeLoadType.BeforeSceneLoad 在整个 Play Mode 生命周期内只触发一次,
/// 因此同一局内跨场景加载(SceneManager.LoadScene)不会重置变量,分数等跨场景共享状态可正常保留。
/// </summary>
public static class VariableRegistry
{
private static readonly HashSet<IVariable> _variables = new HashSet<IVariable>();
public static void Register(IVariable variable)
{
_variables.Add(variable);
// 注册即重置:解决 [RuntimeInitializeOnLoadMethod(BeforeSceneLoad)] 在 SO 资产
// 尚未 OnEnable 注册前就执行 ResetAll(此时 _variables 为空)导致重置无效的问题。
// 无论 ResetAll 何时触发,变量在「上线」瞬间都会被恢复到 _defaultValue
// 杜绝退出 Play Mode 后 PlayerHealth 残留 0、再次进入直接触发死亡的跨局泄漏。
variable.ResetToDefault();
}
public static void Unregister(IVariable variable) => _variables.Remove(variable);
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void ResetAll()
{
foreach (var v in _variables)
v.ResetToDefault();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: DHMdty+tWygUNpW3ZTnsgECvyUsf7TZS/Bowuhd8nTjV1gGiSvKO6z4=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
using UnityEngine;
using System;
namespace Architecture.Variables
{
/// <summary>
/// 共享 Vector3 变量(ScriptableObject 资产)。常用于位置、回声中心等。
/// </summary>
[CreateAssetMenu(menuName = "Architecture/Variables/Vector3", fileName = "NewVector3Variable")]
public class Vector3Variable : ScriptableObject, Architecture.IVariable
{
[SerializeField] private Vector3 _value;
[Tooltip("进入 Play Mode 时的初始值,由 VariableRegistry 在每次启动游戏时自动恢复,防止跨局状态泄漏")]
[SerializeField] private Vector3 _defaultValue;
public Vector3 Value
{
get => _value;
set
{
_value = value;
OnValueChanged?.Invoke(value);
}
}
public event Action<Vector3> OnValueChanged;
public void SetValue(Vector3 value) => Value = value;
/// <summary>将运行时值重置为 _defaultValue(每次进入 Play Mode 时由 VariableRegistry 自动调用)。</summary>
public void ResetToDefault()
{
_value = _defaultValue;
OnValueChanged?.Invoke(_value);
}
[ContextMenu("Reset To Default")]
public void Reset() => ResetToDefault();
private void OnEnable() => VariableRegistry.Register(this);
private void OnDisable()
{
VariableRegistry.Unregister(this);
OnValueChanged = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: BnIa4SyuVyiKWphMPOJ0zNHa5e8eoX06FNossjB+7+Ma8SaSRkVd14E=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: