Hazel游戏引擎(107)UUID唯一标识

文章介绍了使用UUID作为全局唯一标识在游戏开发中的应用,详细描述了UUID类的实现,包括使用cpp的随机函数生成和哈希函数处理。同时提及了Cherno遇到的递归包含问题及其解决方案。
摘要由CSDN通过智能技术生成

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

前言

  • 什么是UUID

    可以理解为全球唯一标识

  • 为什么要使用UUID标识实体 = 此节目的

    为了点击运行场景,实体发生位置等变化复原而要实现的标识功能

  • 为什么不简单的使用increment递增

    假设从0开始,游戏分发到两个电脑,他们创建实体标识id,需要知道从多少开始递增,需要服务器提供权威多少开始递增,这样的设计不太好,不想多一个服务器功能,所以只需使用uuid。

    uuid发生的冲突很小,所以不用担心两个电脑创建实体的id一样

  • 如何实现

    定义UUID类,使用cpp的随机函数,随机ID

  • 实现细节

    • uuid一般是128位,16字节,但是Cherno只用了64位,8字节实现表示UUID

    • uuid作为key对应实体存储在map中时

      • 若是unordered_map<uint64_t, std::string> m_Map;

        这种结构,UUID类只需提供类型转换函数

        operator uint64_t() const {return m_UUID;}
        m_Map[(uint64_t)UUID()] = "Cherno"; 
        
      • 若是unordered_map<UUID, std::string> m_Map;

        需要为UUID类提供哈希函数

        namespace std {
        	template<>
        	struct hash<Hazel::UUID>{
        		std::size_t operator()(const Hazel::UUID& uuid) const{
        			return hash<uint64_t>()((uint64_t)uuid);
        		}
        	};
        }
        

        不然使用UUID类作为key会有bug

    • 在SceneSerializer中获取实体的UUID

      需要先获取实体的id组件再获取uuid,挺麻烦的,所以就直接在实体类中提供一个GetUUID的函数。

关键代码+代码流程

  • 定义好UUID类

    #pragma once
    namespace Hazel {
    	class UUID{
    	public:
    		UUID();
    		UUID(uint64_t uuid);
    		UUID(const UUID&) = default;
    
    		operator uint64_t() const { return m_UUID; }
    	private:
        	///
            // 类型转换函数
    		uint64_t m_UUID;
    	};
    }
    namespace std {
        ///
        // 哈希函数
    	template<>
    	struct hash<Hazel::UUID>{
    		std::size_t operator()(const Hazel::UUID& uuid) const{
    			return hash<uint64_t>()((uint64_t)uuid);
    		}
    	};
    }
    
    #include "hzpch.h"
    #include "UUID.h"
    #include <random>
    namespace Hazel {
    	static std::random_device s_RandomDevice;
    	static std::mt19937_64 s_Engine(s_RandomDevice());
    	static std::uniform_int_distribution<uint64_t> s_UniformDistribution;
    	UUID::UUID()
    		: m_UUID(s_UniformDistribution(s_Engine)){
    	}
    	UUID::UUID(uint64_t uuid)
    		: m_UUID(uuid){
    	}
    }
    
  • 开始创建实体时候(CreateEntity)创建新的UUID,真正创建实体时(CreateEntityWithUUID)使用实参UUID

        Entity Scene::CreateEntity(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;
        }
    
  • 序列化yaml-cpp文件时,读取实体的ID

    static void SerializeEntity(YAML::Emitter& out, Entity entity) {
        HZ_CORE_ASSERT(entity.HasComponent<IDComponent>());
        out << YAML::BeginMap;
        //out << YAML::Key << "Entity" << YAML::Value << "12837192831273";
        out << YAML::Key << "Entity" << YAML::Value << entity.GetUUID();
    
  • 解析yaml-cpp文件的时候,创建实体使用存储在yaml文件中的uuid

    bool SceneSerializer::DeSerialize(const std::string& filepath){
        ......
        auto entities = data["Entities"];
        if (entities) {
            for (auto entity : entities) {
                uint64_t uuid = entity["Entity"].as<uint64_t>();
    
                std::string name;
                auto tagComponent = entity["TagComponent"];
                if (tagComponent) {
                    name = tagComponent["Tag"].as<std::string>();
                }
                HZ_CORE_TRACE("Deserialized entity with ID = {0}, name = {1}", uuid, name);
    
                // 使用已存在的UUID
                Entity deserializedEntity = m_Scene->CreateEntityWithUUID(uuid, name);;
    

yaml-cpp的uuid

Scene: Untitled
Entities:
  - Entity: 17719385851905286962
    TagComponent:
      Tag: Red Square Entity
    TransformComponent:
	......
  - Entity: 734470491072089684
    TagComponent:
      Tag: Green Square Entity
    TransformComponent:
	......
  - Entity: 1740022399049692222
    TagComponent:
      Tag: Camera A
	......
  - Entity: 1432312044630952282
    TagComponent:
      Tag: Camera B
    TransformComponent:
	......

Cherno遇到的BUG

  • BUG1递归包含

    • Bug分析

      Entity.h

      #include "Hazel/Core/UUID.h"
      #include "Components.h"// 注意这个文件
      #include "Scene.h"
      #include "entt.hpp"
      

      Components.h

      // 脚本编辑组件要这个ScriptableEntity头文件
      #include "Hazel/Scene/ScriptableEntity.h"
      #include "Hazel/Renderer/Texture.h"
      #include "Hazel/Core/UUID.h"
      

      ScriptableEntity.h

      #include "Hazel/Core/Timestep.h"
      #include "Entity.h"
      

      可见:Entity包含Component,Component包含ScriptableEntity,ScriptableEntity包含Entity如此递归包含,所以会报以下错。

      请添加图片描述

    • 解决方法:

      组件Components应该不知道实体类Entity的存在,而ScriptableEntity类include包含实体,所以组件Components需改为不包含ScriptableEntity

      但是组件Components内部会使用ScriptableEntity,但这里正好是指针,可以前向声明ScriptableEntity,等要使用ScriptableEntity指针的时候再实际包含ScriptableEntity。

      class ScriptableEntity; // 前向声明
      struct NativeScriptComponent {
          ScriptableEntity* Instance = nullptr;
          /
          /
          // 函数指针名称:InstantiateScript、指向无参数、返回ScriptableEntity指针的函数
          // 1.函数指针指向的函数返回ScriptableEntity*
          // 2.*InstantiateScript 是函数指针,函数指针名字是InstantiateScript
          // 3.函数指针指向的函数无参数
          ScriptableEntity* (*InstantiateScript)();// 这函数返回ScriptableEntity指针,函数无参数(InstantiateScript的*代表为指针)
          void(*DestroyScript)(NativeScriptComponent*);
          template<typename T>
          void Bind() {
              // 这里绑定的函数功能是:根据T动态实例化Instanse
              InstantiateScript = []() {return static_cast<ScriptableEntity*>(new T()); };// 引用值捕获Instance
              DestroyScript = [](NativeScriptComponent* nsc) {delete nsc->Instance; nsc->Instance = nullptr; };// 感觉参数放不放NativeScriptComponent无所谓,反正有this
          }
      };
      

      注意:由于Scene使用了ScriptableEntity,且Scene包含了component.h(所以Scene之前可以不包含ScriptableEntity而使用ScriptableEntity)

      但现在component.h不包含ScriptableEntity了,所以需要在scene中包含"ScriptableEntity.h"

      #include "Components.h"
      #include "ScriptableEntity.h"
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘建杰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值