はじめに
今回は前回の続きからです。Unityっぽい動きができるようにしてみたいと思います。
・前回の記事についてはこちら
Unityのような操作感でGameObjectの作成やAddComponentをするためにまずはGameObjectクラスとSceneクラスに分けます。
これをすることでScene→CreateGameObjectでシーンにオブジェクトを作成したり、GameObject→AddComponentでオブジェクトにコンポーネントをアタッチするというイメージで直感的に操作することができます。
実装
まずはGameObjectクラスからです。
#pragma once
#include "entt/entt.hpp"
#include <iostream>
#include "Scene.h"
class Scene;
class GameObject
{
public:
GameObject(entt::entity handle, Scene* scene)
: m_entityHandle(handle), m_Scene(scene) {};
GameObject(const GameObject& other) = default;
~GameObject() {};
template<typename ComponentType>
bool HasComponent()
{
return m_Scene->GetRegistry().all_of<ComponentType>(m_entityHandle);
}
template<typename ComponentType, typename... Args>
ComponentType& AddComponent(Args&&... args)
{
if (HasComponent<ComponentType>() == true)
{
std::cout << "このコンポーネントはすでに持っています" << std::endl;
// TODO Assert
}
return m_Scene->GetRegistry().emplace<ComponentType>(
m_entityHandle, std::forward<Args>(args)...);
}
template<typename ComponentType>
ComponentType& GetComponent()
{
if (HasComponent<ComponentType>() == false)
{
std::cout << "このコンポーネントは持ってません" << std::endl;
// TODO Assert
}
return m_Scene->GetRegistry().get<ComponentType>(m_entityHandle);
}
template<typename ComponentType>
void RemoveComponent()
{
if (HasComponent<ComponentType>() == false)
{
std::cout << "このコンポーネントは持ってません" << std::endl;
// TODO Assert
return;
}
m_Scene->GetRegistry().remove<ComponentType>(m_entityHandle);
}
operator bool() const { return (uint32_t)m_entityHandle != entt::null; }
private:
entt::entity m_entityHandle{ entt::null };
Scene* m_Scene;
};
「EntityID」と「どのシーンに存在しているか」という変数を持ち、このGameObjectに対してAddComponentやRemoveComponentが行えるようになっています。例外があった場合のAssert処理は入れていないので注意してください。
次にSceneクラスです。
#pragma once
#include "entt/entt.hpp"
class GameObject;
class Scene
{
public:
Scene() {};
~Scene() {};
entt::registry& GetRegistry() { return m_registry; }
GameObject CreateGameObject();
void DestroyGameObject(entt::entity id)
{
m_registry.destroy(id);
}
private:
entt::registry m_registry;
friend class GameObject;
};
#include "Scene.h"
#include "Component.h"
#include "GameObject.h"
GameObject Scene::CreateGameObject()
{
GameObject obj = { m_registry.create(), this };
obj.AddComponent<TransformComponent>(1, 1, 1);
return obj;
}
Entityを管理しているレジストリを持ち、GameObjectの作成と削除を行えるようにしています。
そして前回、main.cppで定義していたTransform,Meshのコンポーネントをどこでも使えるようにするために別のファイルに切り分けます。
#pragma once
struct TransformComponent
{
float pos_x, pos_y, pos_z;
TransformComponent() = default;
TransformComponent(float x, float y, float z) { pos_x = x; pos_y = y; pos_z = z; }
};
struct MeshComponent
{
int meshNo;
MeshComponent(int no = 0) : meshNo(no) {};
};
ここにGameObjectにアタッチするComponentを定義しています。これでGameObjectを作成する際に一緒にTransformをアタッチすることができるようになりました。
最後にこれらのクラスを使ってmain.cppで実際に動かしてみましょう。
#include "entt/entt.hpp"
#include <iostream>
#include "Scene.h"
#include "GameObject.h"
#include "Component.h"
Scene GameScene;
void Update(entt::registry& registry)
{
for (auto [entity, transform, mesh] : registry.view<TransformComponent, MeshComponent>().each())
{
transform.pos_x += 1;
transform.pos_y += 2;
transform.pos_z += 3;
std::cout << "entity: " << static_cast<uint32_t>(entity) << std::endl;
std::cout << "Transform: " << transform.pos_x << ", " << transform.pos_y << ", " << transform.pos_z << std::endl;
std::cout << "MeshNo : " << mesh.meshNo << std::endl;
}
}
int main()
{
for (size_t entityNo = 0; entityNo < 100; ++entityNo)
{
// エンティティ作成(Entity)
GameObject obj = GameScene.CreateGameObject();
// コンポーネント追加(Component)
obj.AddComponent<MeshComponent>(entityNo);
}
// レジストリの中のエンティティを更新(System)
Update(GameScene.GetRegistry());
return 0;
}
GameSceneを作成してシーンからオブジェクトを作成し、そのオブジェクトに対してコンポーネントを追加してGameSceneのEntityを管理しているレジストリを参照してUpdateをしています。
まとめ
うまく動いたでしょうか?これで気軽にUnityっぽい操作感で柔軟にゲームを作っていきましょう。