Hazel学习笔记:编辑器
经过一段时间的挣扎,终于决定要向完整的3D编辑器出发了,还是Hazel游戏引擎的框架进行完善,对于游戏脚本仍然沿用之前的 Lua 脚本,对于后端仍然保留OpenGL作为后端图形API,以此两点作为与原Hazel引擎的不同之处。
本篇内容不涉及ImGUI使用教程。
新增功能如下:
- UUID生成器、UUID组件
- 新增组件之间父子关系,子只保存对父的相对坐标
- 对每个组件而言,当父子关系发生变化时,坐标也要相应的进行变化
- 编辑器窗口
- 视图窗口(Viewport)、属性窗口(Property)、组件树、窗口支持拖拽
- 独立的Camera(EditorCamera)
- 鼠标拾取
- 引入 ImGui 的 ImGuizmo,使得实体支持手柄拖动修改(QWER:关闭平移旋转缩放)
- 引入 Line、Circle 绘制,增强编辑器 Debug 能力
- 添加Scene拷贝能力,用来支持播放、暂停功能。
- 添加 Project,定制项目文件格式,统一管理脚本、资源、场景文件
父子关系
以场景为单位,在场景中新增实体时,为每个实体附加一个UUID组件和关系组件:
| |
定义常用组件接口:
查找:
1 2 3 4 5 6Entity Scene::FindEntityByUUID(UUID uuid) { if (auto it = m_EntityIDMap.find(uuid); it != m_EntityIDMap.end()) return Entity{ it->second, this }; return Entity{}; }实体销毁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25void Scene::DestroyEntity(Entity entity) { // 递归销毁子实体 if (entity.HasComponent<RelationshipComponent>()) { auto children = entity.Children(); for (auto childId : children) { Entity child = FindEntityByUUID(childId); if (child) DestroyEntity(child); } // 移除自己 Entity parent = entity.GetParent(); if (parent) parent.RemoveChild(entity); } // 从 UUID 映射中移除 if (entity.HasComponent<IDComponent>()) m_EntityIDMap.erase(entity.GetComponent<IDComponent>().ID); m_Registry.destroy(entity.m_EntityHandle); }取消实体的父:
1 2 3 4 5 6 7 8 9 10 11void Scene::UnparentEntity(Entity entity, bool convertToWorldSpace) { Entity parent = FindEntityByUUID(entity.GetParentUUID()); std::erase(parent.Children(), entity.GetUUID()); if (convertToWorldSpace) ConvertToWorldSpace(entity); entity.SetParentUUID(0); }设置父子关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26void Scene::ParentEntity(Entity entity, Entity parent) { // 防止关系成环 if (parent.IsDescendantOf(entity)) { UnparentEntity(parent); Entity newParent = FindEntityByUUID(entity.GetParentUUID()); if (newParent) { UnparentEntity(entity); ParentEntity(parent, newParent); } } else { Entity previousParent = FindEntityByUUID(entity.GetParentUUID()); if (previousParent) UnparentEntity(entity); } entity.SetParentUUID(parent.GetUUID()); parent.Children().push_back(entity.GetUUID()); ConvertToLocalSpace(entity); }坐标转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37// 对于:Parent Translation(100, 200) Child Translation(130, 200) // 最后Child Translation(30, 0) void Scene::ConvertToLocalSpace(Entity entity) { Entity parent = FindEntityByUUID(entity.GetParentUUID()); if (!parent) return; auto& transform = entity.Transform(); glm::mat4 parentTransform = GetWorldSpaceTransformMatrix(parent); glm::mat4 localTransform = glm::inverse(parentTransform) * transform.GetTransform(); transform.SetTransform(localTransform); } // 世界矩阵 * 偏移量:转换坐标为世界坐标 void Scene::ConvertToWorldSpace(Entity entity) { Entity parent = FindEntityByUUID(entity.GetParentUUID()); if (!parent) return; glm::mat4 worldTransform = GetWorldSpaceTransformMatrix(entity); auto& entityTransform = entity.Transform(); entityTransform.SetTransform(worldTransform); } // 递归获取位置矩阵 glm::mat4 Scene::GetWorldSpaceTransformMatrix(Entity entity) { glm::mat4 transform(1.0f); Entity parent = FindEntityByUUID(entity.GetParentUUID()); if (parent) transform = GetWorldSpaceTransformMatrix(parent); return transform * entity.Transform().GetTransform(); }最终渲染时通过
GetWorldSpaceTransformMatrix获取实体在世界中的实际位置。Windows下窗口拖拽方案
