2026-03-27 14:40:29 +08:00
|
|
|
|
# EntityCommands
|
|
|
|
|
|
|
|
|
|
|
|
**命名空间**: `XCEngine::Editor::Commands`
|
|
|
|
|
|
|
|
|
|
|
|
**类型**: `header-helper`
|
|
|
|
|
|
|
|
|
|
|
|
**源文件**: `editor/src/Commands/EntityCommands.h`
|
|
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
**描述**: 场景实体编辑命令集合,封装实体创建、重命名、删除、复制、粘贴、重复、重挂接和从父节点分离等标准编辑动作。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
`EntityCommands` 是当前 editor 场景编辑链路里的核心命令层之一。
|
|
|
|
|
|
Hierarchy、Edit 菜单、快捷键和其他交互路由,不应该直接调用 `SceneManager` 做数据修改,而应该把“用户想做什么”翻译成稳定的命令语义。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
当前这组 helper 覆盖的核心操作包括:
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
- 创建空实体、相机、灯光和内置 primitive。
|
|
|
|
|
|
- 重命名与删除实体。
|
|
|
|
|
|
- 复制、粘贴、重复实体。
|
|
|
|
|
|
- 改变父子关系,并在可能时保持世界空间变换。
|
|
|
|
|
|
- 将实体从父节点分离到根层级。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
## 前置知识
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
理解这组 API,最重要的是分清两层:
|
|
|
|
|
|
|
|
|
|
|
|
- [ISceneManager](../../Core/ISceneManager/ISceneManager.md) 负责真正的数据结构修改。
|
|
|
|
|
|
- `EntityCommands` 负责把这些修改包装成编辑器可复用的“命令语义”。
|
|
|
|
|
|
|
|
|
|
|
|
这么做的原因很直接:
|
|
|
|
|
|
|
|
|
|
|
|
- 统一接入 [UndoUtils](../../Utils/UndoUtils/UndoUtils.md)。
|
|
|
|
|
|
- 统一处理“创建后选中”“粘贴后选中”这类编辑器行为。
|
|
|
|
|
|
- 让 Hierarchy、菜单栏、快捷键共享同一套实现。
|
|
|
|
|
|
|
|
|
|
|
|
## 主要接口
|
|
|
|
|
|
|
|
|
|
|
|
| 成员 | 说明 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| `CreateEntity(...)` | 通用模板创建入口,允许传入自定义 setup 回调。 |
|
|
|
|
|
|
| `CreateEmptyEntity(...)` | 创建空实体。 |
|
|
|
|
|
|
| `CreateCameraEntity(...)` | 创建实体并附加 `CameraComponent`。 |
|
|
|
|
|
|
| `CreateLightEntity(...)` | 创建实体并附加 `LightComponent`。 |
|
|
|
|
|
|
| `CreatePrimitiveEntity(...)` | 创建实体并附加 primitive 所需组件与资源引用。 |
|
|
|
|
|
|
| `RenameEntity(...)` | 重命名实体。 |
|
|
|
|
|
|
| `DeleteEntity(...)` | 删除实体。 |
|
|
|
|
|
|
| `CopyEntity(...)` | 把实体复制到 scene clipboard。 |
|
|
|
|
|
|
| `PasteEntity(...)` | 从 clipboard 粘贴实体,可指定父节点。 |
|
|
|
|
|
|
| `DuplicateEntity(...)` | 复制当前实体并插入场景。 |
|
|
|
|
|
|
| `CanReparentEntity(...)` | 判断目标父节点是否合法。 |
|
|
|
|
|
|
| `ReparentEntityPreserveWorldTransform(...)` | 重挂接到新父节点,并尽量保持世界变换。 |
|
|
|
|
|
|
| `DetachEntity(...)` | 从父节点分离到根节点。 |
|
|
|
|
|
|
|
|
|
|
|
|
## 当前实现行为
|
|
|
|
|
|
|
|
|
|
|
|
### 命令包装与撤销
|
|
|
|
|
|
|
|
|
|
|
|
大多数会改动场景结构的操作,都会走:
|
|
|
|
|
|
|
|
|
|
|
|
- `UndoUtils::ExecuteSceneCommand(context, commandLabel, ...)`
|
|
|
|
|
|
|
|
|
|
|
|
这包括:
|
|
|
|
|
|
|
|
|
|
|
|
- 创建
|
|
|
|
|
|
- 重命名
|
|
|
|
|
|
- 删除
|
|
|
|
|
|
- 粘贴
|
|
|
|
|
|
- 重复
|
|
|
|
|
|
- 重挂接
|
|
|
|
|
|
|
|
|
|
|
|
这意味着 `EntityCommands` 的职责不只是帮你少写几行代码,而是确保场景级编辑动作可以用统一方式进入撤销系统。
|
|
|
|
|
|
|
|
|
|
|
|
一个重要例外是 `CopyEntity(...)`:
|
|
|
|
|
|
|
|
|
|
|
|
- 它只写 scene clipboard,不改场景结构。
|
|
|
|
|
|
- 因此当前没有包进 undo command。
|
|
|
|
|
|
|
|
|
|
|
|
### 创建语义
|
|
|
|
|
|
|
|
|
|
|
|
`CreateEntity(...)` 是所有创建 helper 的底层模板。
|
|
|
|
|
|
|
|
|
|
|
|
它会:
|
|
|
|
|
|
|
|
|
|
|
|
1. 通过 `SceneManager::CreateEntity(...)` 创建实体。
|
|
|
|
|
|
2. 执行可选的 `setup(created, sceneManager)` 回调。
|
|
|
|
|
|
3. 把新实体设为当前选择。
|
|
|
|
|
|
|
|
|
|
|
|
这使得更高层的创建 helper 可以只关注“创建后要附加什么”。
|
|
|
|
|
|
|
|
|
|
|
|
### Camera / Light / Primitive
|
|
|
|
|
|
|
|
|
|
|
|
当前具体 helper 的行为是:
|
|
|
|
|
|
|
|
|
|
|
|
- `CreateCameraEntity(...)`
|
|
|
|
|
|
- 创建实体
|
|
|
|
|
|
- 附加 `CameraComponent`
|
|
|
|
|
|
- `CreateLightEntity(...)`
|
|
|
|
|
|
- 创建实体
|
|
|
|
|
|
- 附加 `LightComponent`
|
|
|
|
|
|
- `CreatePrimitiveEntity(...)`
|
|
|
|
|
|
- 创建实体
|
|
|
|
|
|
- 确保存在 `MeshFilterComponent`
|
|
|
|
|
|
- 把 mesh 路径设为内置 primitive mesh
|
|
|
|
|
|
- 确保存在 `MeshRendererComponent`
|
|
|
|
|
|
- 给第 `0` 个材质槽绑定默认 primitive material
|
|
|
|
|
|
|
|
|
|
|
|
这意味着当前 primitive 创建已经具备“立刻可见”的基础语义,而不是旧版本那种只生成命名空对象。
|
|
|
|
|
|
|
|
|
|
|
|
## 重命名、删除、复制、粘贴、重复
|
|
|
|
|
|
|
|
|
|
|
|
### 重命名与删除
|
|
|
|
|
|
|
|
|
|
|
|
- `RenameEntity(...)` 会先检查新名称非空且实体存在。
|
|
|
|
|
|
- `DeleteEntity(...)` 会先检查实体是否存在。
|
|
|
|
|
|
|
|
|
|
|
|
这里要注意一点:
|
|
|
|
|
|
|
|
|
|
|
|
- 这两个 helper 不自己维护 UI 选择状态。
|
|
|
|
|
|
- 删除后选择如何清理,依赖更高层路由与 manager 的整体行为。
|
|
|
|
|
|
|
|
|
|
|
|
因此文档里不应把“删除后自动取消选择”错误归因到 `EntityCommands` 自身。
|
|
|
|
|
|
|
|
|
|
|
|
### 复制、粘贴、重复
|
|
|
|
|
|
|
|
|
|
|
|
- `CopyEntity(...)` 只是把实体写入 scene clipboard。
|
|
|
|
|
|
- `PasteEntity(...)` 可以指定 `parentId`,成功后会把新实体设为选中对象。
|
|
|
|
|
|
- `DuplicateEntity(...)` 依赖 `SceneManager::DuplicateEntity(...)`,成功后同样会把复制出的实体设为选中对象。
|
|
|
|
|
|
|
|
|
|
|
|
复制 / 粘贴 / 重复的深拷贝细节,当前并不在本文件里实现,而是委托给 [ISceneManager](../../Core/ISceneManager/ISceneManager.md)。
|
|
|
|
|
|
|
|
|
|
|
|
## 重挂接与保持世界变换
|
|
|
|
|
|
|
|
|
|
|
|
`CanReparentEntity(...)` 负责做最核心的合法性校验:
|
|
|
|
|
|
|
|
|
|
|
|
- `source` 不能为空。
|
|
|
|
|
|
- 不能把对象挂到自己的后代节点下。
|
|
|
|
|
|
|
|
|
|
|
|
`ReparentEntityPreserveWorldTransform(...)` 的实现重点在于:
|
|
|
|
|
|
|
|
|
|
|
|
1. 读取当前父节点 id。
|
|
|
|
|
|
2. 若目标父节点未变化,直接返回 `false`。
|
|
|
|
|
|
3. 若对象没有 transform,直接 `MoveEntity(...)`。
|
|
|
|
|
|
4. 若对象有 transform,则先缓存世界空间位置、旋转和缩放。
|
|
|
|
|
|
5. 执行 `MoveEntity(...)`。
|
|
|
|
|
|
6. 再把缓存下来的世界变换写回 transform。
|
|
|
|
|
|
|
|
|
|
|
|
这样做的设计目标非常明确:
|
|
|
|
|
|
用户在 Hierarchy 里换 parent 时,默认期望对象的世界空间姿态不要突然跳变。
|
|
|
|
|
|
|
|
|
|
|
|
`DetachEntity(...)` 本质上只是调用:
|
|
|
|
|
|
|
|
|
|
|
|
- `ReparentEntityPreserveWorldTransform(context, entity, 0, ...)`
|
|
|
|
|
|
|
|
|
|
|
|
也就是把对象挂回根节点。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
|
|
|
|
|
## 设计说明
|
|
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
把实体修改动作收敛到命令层,是编辑器工程质量的分水岭之一。
|
|
|
|
|
|
|
|
|
|
|
|
如果直接在面板或 router 中散落调用:
|
|
|
|
|
|
|
|
|
|
|
|
- `CreateEntity`
|
|
|
|
|
|
- `DeleteEntity`
|
|
|
|
|
|
- `MoveEntity`
|
|
|
|
|
|
- `RenameEntity`
|
|
|
|
|
|
|
|
|
|
|
|
会立刻遇到三个问题:
|
|
|
|
|
|
|
|
|
|
|
|
- 撤销历史接不上。
|
|
|
|
|
|
- 菜单栏、右键菜单、快捷键会复制同样的逻辑。
|
|
|
|
|
|
- “UI 如何触发”与“场景如何修改”混在一起,后续难以扩展。
|
|
|
|
|
|
|
|
|
|
|
|
`EntityCommands` 的价值就在于把“商业编辑器里的标准实体操作”稳定成一层可复用协议。
|
|
|
|
|
|
|
|
|
|
|
|
## 测试与验证
|
|
|
|
|
|
|
|
|
|
|
|
当前至少有以下测试为这些命令提供行为锚点:
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
- `HierarchyRouteExecutesCopyPasteDuplicateDeleteAndRename`
|
|
|
|
|
|
- `ReparentPreserveWorldTransformKeepsWorldPose`
|
|
|
|
|
|
- `HierarchyRouterRenameHelpersPublishAndCommit`
|
|
|
|
|
|
- `PlayModeAllowsRuntimeSceneUndoRedoButKeepsSceneDocumentCommandsBlocked`
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
这些测试表明当前命令层不仅在 Edit 模式使用,也参与 Play 模式下的运行时场景编辑与 undo/redo。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
|
|
|
|
|
## 当前限制
|
|
|
|
|
|
|
2026-04-08 16:07:03 +08:00
|
|
|
|
- 仍然是单实体命令集,没有批量操作协议。
|
|
|
|
|
|
- 粘贴与重复的深拷贝语义完全依赖 [ISceneManager](../../Core/ISceneManager/ISceneManager.md) 当前实现。
|
|
|
|
|
|
- “保持世界变换”当前只覆盖 transform 的位置、旋转、缩放,不涵盖其他可能依赖父层级的自定义状态。
|
|
|
|
|
|
- 是否允许在某个 editor runtime mode 下执行,通常由更高层调用点决定,不是每个 helper 自己都做模式拦截。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
|
|
|
|
|
## 相关文档
|
|
|
|
|
|
|
|
|
|
|
|
- [HierarchyActionRouter](../../Actions/HierarchyActionRouter/HierarchyActionRouter.md)
|
2026-04-08 16:07:03 +08:00
|
|
|
|
- [HierarchyPanel](../../panels/HierarchyPanel/HierarchyPanel.md)
|
|
|
|
|
|
- [ISceneManager](../../Core/ISceneManager/ISceneManager.md)
|
2026-03-27 14:40:29 +08:00
|
|
|
|
- [UndoUtils](../../Utils/UndoUtils/UndoUtils.md)
|