はじめに
今回はUnityを触っているとよく聞く「ECS」というものをC++で気軽に試してみようと思います。
ECSとは
ECSとはEntity Component Systemのことで設計パターンの1つです。Entity(オブジェクトID)にComponent(部品データなどの機能)を付け足し、System(振る舞い、Updateなど)で動かします。
このような設計はゲーム制作でよくある仕様変更に柔軟に対応できるようになります。
環境設定
- Visual Studio 2022
- C++20
実装開始
それでは実際に作ってみましょう。
今回は「EnTTを使ってみる」ということでEnTTのライブラリを使用します。
プロジェクトのプロパティ設定
まずはプロジェクトを作成してください。EnTTはC++17以上でしか動かないのでプロジェクト→プロパティ→全般からC++言語標準のC++バージョンを17以上にしてください。自分はC++20にしています。
設定できたら適用を押して今度はEnTTライブラリをインクルードしましょう。今回はヘッダーを一つインクルードするだけで使用できるhttps://github.com/skypjack/entt/blob/master/single_include/entt/entt.hppを使用します。どこに置いてもいいのでインクルードしてください。
EnTTを使ってサンプルを作成し実行してみる
できたらmain.cppを作成し、以下のソースコードをコピーしてください。
#include "entt/entt.hpp"
#include <iostream>
struct TransformComponent
{
float pos_x, pos_y, pos_z;
TransformComponent(float x = 0, float y = 0, float z = 0) : pos_x(x), pos_y(y), pos_z(z) {}
};
struct MeshComponent
{
int meshNo;
MeshComponent(int no = 0) : meshNo(no) {};
};
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: " << (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()
{
entt::registry registry;
for (size_t entityNo = 0; entityNo < 10; ++entityNo)
{
// エンティティ作成(Entity)
entt::entity entity = registry.create();
// コンポーネント追加(Component)
registry.emplace<TransformComponent>(entity, 1.0f, 2.0f, 3.0f);
registry.emplace<MeshComponent>(entity, entityNo);
}
// レジストリの中のエンティティを更新(System)
Update(registry);
return 0;
}
実際やっていることは
- エンティティを作成
- コンポーネントを追加
- Update関数で更新
を行っています。
1.エンティティを作成
entt::entity entity = registry.create();
↑これでEntityを作成しています。Entityはentt.hppの中身を見ると”enum class entity : id_type {};”となっています。普通に0,1で表現されるIDです。この段階ではIDを作成しただけですのでTransformやMeshなどのコンポーネント、nameやTagすらありません。
2.コンポーネントを追加
registry.emplace<TransformComponent>(entity, 1.0f, 2.0f, 3.0f);
Unityで例えるとAddComponentのようなものです。EntityにTransformを作成して紐づけています。
3.コンポーネントが紐づいているEntityの更新
for (auto [entity, transform, mesh] : registry.view<TransformComponent, MeshComponent>().each())
Viewを使用して特定のコンポーネントが紐づいているEntityを見つけて更新しています。今回はTransformとMeshコンポーネントの両方が紐づいているEntityを更新しています。なのでTransformのみ、Meshのみ紐づいているEntityは更新されません。
まとめ
いかがでしたか?うまく動いてECSがなんとなく理解できたでしょうか?次回はここからもう少し手を加えてUnityのような操作感(GameObjectにAddComponentする)ができるようにしてみたいと思います。