Hazel游戏引擎(108)开始、结束、复制场景

文中若有代码、术语等错误,欢迎指正

前言

  • 此节目的

    1. 为实现点击运行,复制当前场景成为运行场景,点击结束销毁当前场景。
    2. 在当前场景复制实体

    最重要的如何复制场景,复制场景需要复制当前场景的所有实体及其包含的组件,但是当前的entt库不包含复制组件的API,所以我们需要手动写复制实体

  • 如何实现复制场景

    entt的注册表起关键作用entt::registry

    1. 创建新场景,为新场景创建和旧场景同名和uuid的实体,并用map存入(旧实体的uuid对应新实体)的关系
    2. 遍历旧场景所有uuid组件的旧实体,用旧实体的uuid-map-对应新实体
    3. 获取旧实体的所有组件,然后用API,复制旧实体的所有组件给新实体,复制组件会包括复制组件的属性值
  • 如何实现复制实体

    1. 创建新的同名实体
    2. 分别检测旧实体是否有对应组件,有就添加或修改对应新实体组件
  • 实现细节

    运行场景时,注意设置面板的上下文为场景,停止后要设回编辑场景(场景)。

关键代码+代码流程

  • 注册表添加或者替换组件

    T& component = m_Scene->m_Registry.emplace_or_replace<T>(m_EntityHandle, std::forward<Args>(args)...);
    
  • 复制场景

    • 在editorlayer中点击运行,执行Scene的Copy函数复制当前的场景,并设置面板的上下文为新场景

      void EditorLayer::OnScenePlay()
      {
          if (!m_EditorScene) {
              HZ_CORE_WARN("editorscene is null");
              return;
          }
          m_SceneState = SceneState::Play;
      
          // 复制新场景给活动场景
          m_ActiveScene = Scene::Copy(m_EditorScene);
          m_ActiveScene->OnRuntimeStart(); // 复制完新场景就开始运行
          // 当前上下文是新场景----------------------------
          m_SceneHierarchyPanel.SetContext(m_ActiveScene);
      }
      
    • 执行Scene的Copy函数复制当前的场景

      Ref<Scene> Scene::Copy(Ref<Scene> other)
      {
          // 1.1创建新场景
          Ref<Scene> newScene = CreateRef<Scene>();
      
          newScene->m_ViewportWidth = other->m_ViewportWidth;
          newScene->m_ViewportHeight = other->m_ViewportHeight;
      
          auto& srcSceneRegistry = other->m_Registry;
          auto& dstSceneRegistry = newScene->m_Registry;
          std::unordered_map<UUID, entt::entity> enttMap;
      
          auto idView = srcSceneRegistry.view<IDComponent>();
          for (auto e : idView) {
              UUID uuid = srcSceneRegistry.get<IDComponent>(e).ID;
              const auto& name = srcSceneRegistry.get<TagComponent>(e).Tag;
              // 1.2为新场景创建和旧场景同名和uuid的实体
              Entity newEntity = newScene->CreateEntityWithUUID(uuid, name);
              // 1.3并用**map存入(旧实体的uuid对应新实体)的关系**
              enttMap[uuid] = (entt::entity)newEntity;// UUID类需要哈希
          }
      
          // 拷贝组件,除了IDcomponent与tagcomponent,因CreateEntityWithUUID创建了
          CopyComponent<TransformComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
          CopyComponent<SpriteRendererComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
          CopyComponent<CameraComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
          CopyComponent<NativeScriptComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
          CopyComponent<Rigidbody2DComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
          CopyComponent<BoxCollider2DComponent>(dstSceneRegistry, srcSceneRegistry, enttMap);
      
          return newScene;
      }
      // 为复制场景的实体的组件的辅助方法
      template<typename Component>
      static void CopyComponent(entt::registry& dst, entt::registry& src, const std::unordered_map<UUID, entt::entity>& enttMap) {
          auto view = src.view<Component>();
          // 2.1遍历旧场景所有uuid组件的旧实体
          for (auto e : view) {
              UUID uuid = src.get<IDComponent>(e).ID;
              HZ_CORE_ASSERT(enttMap.find(uuid) != enttMap.end());
              // 2.2用** 旧实体的uuid - map - 对应新实体 * *
              entt::entity dstEnttID = enttMap.at(uuid);
              // 3.1获取旧实体的组件
              auto& component = src.get<Component>(e);
              // 3.2然后用API,** 复制旧实体的组件给新实体**
              dst.emplace_or_replace<Component>(dstEnttID, component);// 添加或替换,保险
          }
      }
      
  • 复制实体

    • 在editorlayer中按下快捷键ctrl+d,执行OnDuplicateEntity函数,复制当前选择的实体

      void EditorLayer::OnDuplicateEntity(){
          // 编辑场景时 可以复制实体
          if (m_SceneState != SceneState::Edit) {
              return;
          }
          Entity selectedEntity = m_SceneHierarchyPanel.GetSelectedEntity();
          if (selectedEntity) {
              m_EditorScene->DuplicateEntity(selectedEntity);
          }
      }
      
    • Scene的DuplicateEntity函数执行具体操作

      1. 创建旧实体同名的新实体
      2. 复制组件
      void Scene::DuplicateEntity(Entity entity){
          // 1.创建旧实体同名的新实体
          std::string name = entity.GetName();
          Entity newEntity = CreateEntity(name);
          // 2.复制组件
          CopyComponentIfExists<TransformComponent>(newEntity, entity);
          CopyComponentIfExists<SpriteRendererComponent>(newEntity, entity);
          CopyComponentIfExists<CameraComponent>(newEntity, entity);
          CopyComponentIfExists<NativeScriptComponent>(newEntity, entity);
          CopyComponentIfExists<Rigidbody2DComponent>(newEntity, entity);
          CopyComponentIfExists<BoxCollider2DComponent>(newEntity, entity);
      }
      // 为复制实体的辅助方法
      template<typename Component>
      static void CopyComponentIfExists(Entity dst, Entity src) {
          if (src.HasComponent<Component>()) {
              dst.AddOrReplaceComponent<Component>(src.GetComponent<Component>());
          }
      }
      

效果

1.3运行

1.4运行

  • 注意的小点

    这里相机也有box2d组件,注意相机的z是5,表示在绿色障碍物的前面,运行的时候也能与绿色的障碍物计算模拟。

    给白色的实体z调成任何值,也能和绿色障碍物交互!

    但摄像机是透视投影,所以白色实体z大小会影响白色实体的感官,若z太小,实体也会看起来缩小一样,若z太大超过摄像机的z,则实体将不会呈现。

    请添加图片描述

Cherno遇到的bug

  • hierarchy面板不显示实体

    没有设置面板的上下文场景,所以获取不到场景的实体

  • Ctrl+D复制实体出错

    问题概述:引用问题

    问题分析:

    在Editorlayer ctrl+d复制实体,调用scene的DuplicateEntity函数,创建一个和旧实体同名的新实体

    void Scene::DuplicateEntity(Entity entity)
    {
        Entity newEntity = CreateEntity(entity.GetName());
    

    在CreateEntity执行CreateEntityWithUUID函数

    Entity Scene::CreateEntity(const std::string& name)
    {
        // 创建新的uuid
        return CreateEntityWithUUID(UUID(), name);
    }
    Entity Scene::CreateEntityWithUUID(UUID uuid, const std::string& name)
    {
        // 添加默认组件
        Entity entity = { m_Registry.create(),this };
        entity.AddComponent<TransformComponent>();
        entity.AddComponent<IDComponent>(uuid); // 使用存在的uuid,不创建新的
        // 下面这一行!!!!!!!!!!!!!!!
        auto& tag = entity.AddComponent<TagComponent>();
        tag.Tag = name.empty() ? "Entity" : name;
        return entity;
    }
    

    当CreateEntityWithUUID函数执行到 auto& tag = entity.AddComponent();这段代码,会导致参数name获取字符串失败。

    问题原因:

    1. Entity的GetName是获取TagComponent的字符串

      const std::string& GetName() { return GetComponent<TagComponent>().Tag; }
      
    2. CreateEntity 、CreateEntityWithUUID的参数string都是引用类型

    3. 当执行auto& tag = entity.AddComponent();代码时候,添加新的TagComponent组件,entt的库会移动组件再内存的位置,所以会导致字符串引用的地址无效

    解决方法:

    在Scene的DuplicateEntity函数GetName()后赋给局部string变量

    void Scene::DuplicateEntity(Entity entity)
    {
        std::string name = entity.GetName();
        Entity newEntity = CreateEntity(name);
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘建杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值