超级爆改
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# Editor Command Undo/Redo System Structure (撤销/重做架构指南)
|
||||
|
||||
该系统用于拦截并在运行时 (Runtime) 保证各种编辑器界面的参数变动能够提供健壮的无限撤销防线。该系统由下述几部分组成并具备可延展的极高宽容性:
|
||||
|
||||
### 核心机制组成
|
||||
* **`CommandManager`**: 包含两个运行时的堆栈 (`undoStack`, `redoStack`),所有可逆操作通过调用单例入口 `CommandManager.ExecuteCommand(ICommand)` 发起。
|
||||
* **`CommandShortcutListener`**: 一种不需要依附游戏内真实物体,通过 `[RuntimeInitializeOnLoadMethod]` 发起隐身对象和依靠 `OnGUI()` 发起底层的引擎按键截获。利用了 `Event.current.Use()` 防止了测试/游玩期因为摁下 `Ctrl+Z` 引发外部真实 Unity Editor 所引发的方法连退灾难。
|
||||
* **`ICommand`**: 对操作的抽象接口,其重点是除了具有常规的 `Execute() / Undo() / Redo()`,为了彻底切断 UI 系统的高频垃圾事件,多提供了一个 `TryMerge()` 功能!
|
||||
|
||||
### 未来扩展指南 (Future Implementation Patterns)
|
||||
|
||||
当需要设计 **跨对象 / 增删对象 / 脚本等复杂控制行为** 时,遵循以下架构范本设计 `ICommand`:
|
||||
|
||||
#### 1. 控制台复杂群组指令 (CommandGroup / Macro)
|
||||
已经实现的 `CommandGroup.cs` 是复合动作。若控制台一次性推平了 10 条轨道并在那里放了 10 个颜色框!只用新建一个 `CommandGroup`,并将操作分解成小碎片 `.Add(new ChangeValueCommand(...))` 等等。当它入栈:
|
||||
* **Undo 会自动进行“倒序循环”**:这意味着即使它干了相互依赖的事情,撤退必然从最后一片瓦砖往里拆。
|
||||
|
||||
#### 2. 创建对象的隔离指令 (CreateElementCommand)
|
||||
由于撤回往往会引发 NullReference 的雪崩,**所有可逆过程应当规避真正的 Object.Destroy**!
|
||||
设计创建指令时:
|
||||
* **Execute / Redo**: `LeanPool.Spawn(Prefab)` 生成并在对应列表增加结构体数据。保存好生成的对象唯一标识(如 UUID 或其直接引用)。
|
||||
* **Undo**: 绝不可使用 `Destroy()`。应将其强行从数据层的表单中 `List<T>.Remove(it)` 隐匿,并且使其视觉表现层 `gameObject.SetActive(false)` 作为标记丢在内存。因为如果 Destroy,后续队列如果还绑了某个 `ChangeValueCommand` 更改着该物体的颜色,栈里的指针将报错。
|
||||
|
||||
#### 3. 销毁对象的隔离指令 (DestroyElementCommand)
|
||||
同理推得:
|
||||
* **Execute**: 将其作为“临时尸体”封存(通过脱离逻辑层)。
|
||||
* **Undo**: 重新填表入列并在场景中还原激活态。彻底逃避因为生命周期消失引起的数据坍塌。
|
||||
@@ -0,0 +1,22 @@
|
||||
# DynamicUI Event Listeners Bug & Fix
|
||||
|
||||
## 症状 (Symptom)
|
||||
在 DynamicUI 系统接入对象池 (LeanPool) 后,发现两类典型的异常现象:
|
||||
1. 异常触发:部分需要反射映射的变量,由于反射目标名不匹配或数据确实为 `null`,引发向预设类型强转时的 `NullReferenceException`,从而直接打断整个 Inspector 界面的生成。
|
||||
2. 交互断层:绝大部分可交互 UI(如 InputField、ColorPicker、Toggle 等)虽然通过对象池复用弹出了,但要么无法编辑,要么虽数值生效却无法触发界面上理应附带的点击与过渡表现动效(如 Toggle 的状态图形切换失效)。
|
||||
|
||||
## 根本原因 (Root Cause)
|
||||
1. **缺失非空防护**:早期的反射生成未校验 `GetDeepValue` 提取对象的空引用情况,直接强制拆装箱。
|
||||
2. **过度保护导致的原生监听被清空**:为防范组件被不断回收复用导致“绑定业务动作成倍堆积”,粗暴使用了 `RemoveAllListeners()`。这同时**无差别清除了 UGUI 在预制件内部自行配置、为了播放渐隐/翻转等动效的持久监听器 (Persistent Listeners)**。此外,如果在追加绑定的 `AddListenerFunction` 等扩展 API 中进行了不当的 `Remove...`,更会直接截断其初始化的核心数值回写功能。
|
||||
|
||||
## 解决方案规范与原则 (Solution Pattern)
|
||||
**绝对禁止在复用的 UI 组件身上使用 `RemoveAllListeners()`。** 我们必须采用**代理路由 (Delegate Proxy / Mediator)** 模式以保全底层视效。
|
||||
|
||||
1. **设定私有托管委托**:在派生的 `DynamicUIElement` 子类中引入 `UnityAction<T> customAction` 专门存储动态赋予的逻辑。
|
||||
2. **中心化受控方法**:在组件内使用诸如 `OnToggleValueChanged` / `OnEndEditNode` 等唯一方法负责响应 UGUI 原生回调,并在这个方法内分别去执行参数刷新 `ApplyParameters()` 以及调用 `customAction?.Invoke()`。
|
||||
3. **精准回收防残留**:每次因为对象池 Spawn 触发 `Initialize` 时,仅仅执行:
|
||||
```csharp
|
||||
toggle.onValueChanged.RemoveListener(OnToggleValueChanged); // 只解绑我们自定义的明确方法
|
||||
customAction = null; // 清空代理容器
|
||||
```
|
||||
如此便完美实现了“上游逻辑随时挂载与拔插”,同时绝不伤及任何预制体原生的动效监听链路。
|
||||
Reference in New Issue
Block a user