Files
XCEngine/docs/api/XCEngine/Editor/Commands/EntityCommands/EntityCommands.md

7.2 KiB
Raw Blame History

EntityCommands

命名空间: XCEngine::Editor::Commands

类型: header-helper

源文件: editor/src/Commands/EntityCommands.h

描述: 场景实体编辑命令集合,封装实体创建、重命名、删除、复制、粘贴、重复、重挂接和从父节点分离等标准编辑动作。

概述

EntityCommands 是当前 editor 场景编辑链路里的核心命令层之一。
Hierarchy、Edit 菜单、快捷键和其他交互路由,不应该直接调用 SceneManager 做数据修改,而应该把“用户想做什么”翻译成稳定的命令语义。

当前这组 helper 覆盖的核心操作包括:

  • 创建空实体、相机、灯光和内置 primitive。
  • 重命名与删除实体。
  • 复制、粘贴、重复实体。
  • 改变父子关系,并在可能时保持世界空间变换。
  • 将实体从父节点分离到根层级。

前置知识

理解这组 API最重要的是分清两层

  • ISceneManager 负责真正的数据结构修改。
  • EntityCommands 负责把这些修改包装成编辑器可复用的“命令语义”。

这么做的原因很直接:

  • 统一接入 UndoUtils
  • 统一处理“创建后选中”“粘贴后选中”这类编辑器行为。
  • 让 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

重挂接与保持世界变换

CanReparentEntity(...) 负责做最核心的合法性校验:

  • source 不能为空。
  • 不能把对象挂到自己的后代节点下。

ReparentEntityPreserveWorldTransform(...) 的实现重点在于:

  1. 读取当前父节点 id。
  2. 若目标父节点未变化,直接返回 false
  3. 若对象没有 transform直接 MoveEntity(...)
  4. 若对象有 transform则先缓存世界空间位置、旋转和缩放。
  5. 执行 MoveEntity(...)
  6. 再把缓存下来的世界变换写回 transform。

这样做的设计目标非常明确:
用户在 Hierarchy 里换 parent 时,默认期望对象的世界空间姿态不要突然跳变。

DetachEntity(...) 本质上只是调用:

  • ReparentEntityPreserveWorldTransform(context, entity, 0, ...)

也就是把对象挂回根节点。

设计说明

把实体修改动作收敛到命令层,是编辑器工程质量的分水岭之一。

如果直接在面板或 router 中散落调用:

  • CreateEntity
  • DeleteEntity
  • MoveEntity
  • RenameEntity

会立刻遇到三个问题:

  • 撤销历史接不上。
  • 菜单栏、右键菜单、快捷键会复制同样的逻辑。
  • “UI 如何触发”与“场景如何修改”混在一起,后续难以扩展。

EntityCommands 的价值就在于把“商业编辑器里的标准实体操作”稳定成一层可复用协议。

测试与验证

当前至少有以下测试为这些命令提供行为锚点:

  • HierarchyRouteExecutesCopyPasteDuplicateDeleteAndRename
  • ReparentPreserveWorldTransformKeepsWorldPose
  • HierarchyRouterRenameHelpersPublishAndCommit
  • PlayModeAllowsRuntimeSceneUndoRedoButKeepsSceneDocumentCommandsBlocked

这些测试表明当前命令层不仅在 Edit 模式使用,也参与 Play 模式下的运行时场景编辑与 undo/redo。

当前限制

  • 仍然是单实体命令集,没有批量操作协议。
  • 粘贴与重复的深拷贝语义完全依赖 ISceneManager 当前实现。
  • “保持世界变换”当前只覆盖 transform 的位置、旋转、缩放,不涵盖其他可能依赖父层级的自定义状态。
  • 是否允许在某个 editor runtime mode 下执行,通常由更高层调用点决定,不是每个 helper 自己都做模式拦截。

相关文档