重构
This commit is contained in:
@@ -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:
|
||||
@@ -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<T>);设计师驱动用对应 *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:
|
||||
@@ -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<EnemyAI>)放在对应领域文件夹,
|
||||
/// 继承本类即可,无需改动核心。
|
||||
/// </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<EnemyAI></c>)使用。
|
||||
/// 自动 <c>GetComponent<T>()</c> 并加入对应集合,避免用 Transform 集合再手动转型。
|
||||
/// 因 Unity 对泛型 MonoBehaviour 需具体闭包类型才能挂载,领域文件夹内写一行具体子类即可:
|
||||
/// <code>public class EnemySetRegistrar : RuntimeSetRegistrar<EnemyAI> { }</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:
|
||||
@@ -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:
|
||||
Reference in New Issue
Block a user