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
6
7
8
9
Entity Scene::CreateEntityWithUUID(UUID uuid, const std::string& name)
{
    Entity entity = { m_Registry.create(), this };
    entity.AddComponent<IDComponent>().ID = uuid;	// UUID
    entity.AddComponent<RelationshipComponent>();	// Relationship
    // ...
    m_EntityIDMap[uuid] = entity.m_EntityHandle;    // uuid 与组件映射 
    return entity;
}

定义常用组件接口:

  • 查找:

    1
    2
    3
    4
    5
    6
    
    Entity 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
    25
    
    void 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
    11
    
    void 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
    26
    
    void 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下窗口拖拽方案