跳转至

前言

  • 什么是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

UUID.h

#pragma once
#include <xhash>
namespace CryDust {
    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<CryDust::UUID>
    {
        std::size_t operator()(const CryDust::UUID& uuid) const
        {
            return hash<uint64_t>()((uint64_t)uuid);
        }
    };
}

UUID.cpp

#pragma once
#include "cdpch.h"
#include "UUID.h"
#include <random>
#include <unordered_map>
namespace CryDust {
    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)
    {
    }
}

Components

附加UUID的component以及可脚本化的组件的component(但是不实现,只声明)

    struct IDComponent
    {
        UUID ID;
        IDComponent() = default;
        IDComponent(const IDComponent&) = default;
    };
    // Forward declaration
    class ScriptableEntity;

Entity

UUID GetUUID() {
    return GetComponent<IDComponent>().ID; 
}

Scene

添加IDComponent的模板添加特化,

    template<>
    void Scene::OnComponentAdded<IDComponent>(Entity entity, IDComponent& component)
    {
    }

    Entity Scene::CreateEntity(const std::string& name)
    {
        return CreateEntityWithUUID(UUID(), name);
    }

    Entity Scene::CreateEntityWithUUID(UUID uuid, const std::string & name)
    {

        Entity entity = { m_Registry.create(), this };
        entity.AddComponent<IDComponent>(uuid);
        entity.AddComponent<TransformComponent>();
        auto& tag = entity.AddComponent<TagComponent>(); //创建tag组件
        tag.Tag = name.empty() ? "Entity" : name;   //设置tag组件为名字
        return entity;
    }

SceneSerialize

序列化场景的时候,需要拿到实体uiid

out << YAML::Key << "Entity" << YAML::Value << entity.GetUUID();

反序列化的时候,通过uuid创建实体

uint64_t uuid = entity["Entity"].as<uint64_t>();//这里通过运算符重载完成了uuid的创建
//
Entity deserializedEntity = m_Scene->CreateEntityWithUUID(uuid, name);

C++小知识

写一下巩固一下:可以通过运算符重载,将一个类转成其他类型的函数

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