エンティティ&コンポーネント
PyxenはEntity-Component System(ECS)を採用しています。PlayerがCharacterを継承し、CharacterがGameObjectを継承するようなクラス階層を持つゲームエンジンを使ったことがある方には、これは異なるアプローチです。Pyxenには継承がありません。すべてはコンポジション(構成)です。
エンティティ
エンティティはゲームオブジェクトです。プレイヤー、壁、弾丸、パーティクル — 何でもなれます。内部的には、エンティティは単なるIDです。エンティティ自体はデータを持ちません。代わりに、コンポーネントをアタッチします。
player = world.spawn(name="player", x=100, y=50)
これは位置(100, 50)に名前付きのエンティティを作成します。エンティティはワールドに存在し、スプライトを持っていれば描画されます。
コンポーネント
コンポーネントはエンティティにアタッチされるデータの断片です。Pyxenには2種類あります:
組み込みコンポーネント
エンジンが提供するコンポーネント:
| コンポーネント | 用途 |
|---|---|
| Transform | 位置(x、y)、回転、スケール |
| Sprite | 見た目 — 画像、タイル、ピボット、カラー |
| Camera | ズーム付きビューポート |
| GridBody | グリッド上の衝突ボディ |
| GridMap | 行、列、タイルデータを持つタイルマップ |
| Sound | 効果音の再生 |
| Music | 音楽ストリームの再生 |
| Name | 検索用の文字列識別子 |
| Layer | 描画レイヤー(0〜255) |
| Parent / Children | シーン階層 |
組み込みコンポーネントはエンティティのプロパティまたは生成時に設定します:
e = world.spawn(
x=50, y=80,
sprite=Sprite("hero"),
name="player"
)
# later
e.x = 100
e.sprite = Sprite("hero", tile=(16, 0, 16, 16))
カスタムコンポーネント
任意のエンティティに独自のコンポーネントをアタッチできます。カスタムコンポーネントは型付きフィールドを持つ辞書です:
player.health = {"value": 100, "max": 100}
player.speed = {"value": 2.5}
アタッチ後は、フィールドに直接アクセスできます:
player.health.value -= 10
マーカーコンポーネント — データを持たずタグとして機能するコンポーネントも使えます:
player.alive = True
enemy.boss = True
これはクエリに便利です:特定のマーカーを持つすべてのエンティティを検索できます。
フィールドの型
コンポーネントのフィールドに使える型:
| 型 | 例 | 備考 |
|---|---|---|
int | 42 | 32ビット整数 |
float | 3.14 | 32ビット浮動小数点数 |
bool | True | ブーリアン |
str | "hello" | 最大31文字 |
| Entity | player | 別のエンティティへの参照 |
これらの制約は、コンポーネントがC++エンジン内でコンパクトでキャッシュに優しいレイアウトで格納されるために存在します。これがクエリを高速にしている仕組みです。
エンティティのクエリ
ワールドを通じて、コンポーネントでエンティティを検索できます:
# "health"コンポーネントを持つすべてのエンティティ
for e in world.all("health"):
if e.health.value <= 0:
world.destroy(e)
# 組み合わせ:"enemy"を持ち、"dead"を持たない
for e in world.all("enemy", without=("dead",)):
e.x += e.speed.value
"sprite"、"body"、"camera"、"sound"、"music"、"map"などの組み込みコンポーネントでもクエリできます:
# スプライトを持つすべてのエンティティ
for e in world.all("sprite"):
e.color = (1, 1, 1, 1)
# スプライトと"enemy"カスタムコンポーネントの両方を持つエンティティ
for e in world.all("sprite", "enemy"):
e.color = (1, 0, 0, 1)
クエリはPyxenでゲームロジックを書く主な方法です。各オブジェクトが自身を更新する代わりに、特定のコンポーネントを持つエンティティを反復処理するシステムを書きます。
完全なAPIはこちら:world.all()、Entity
なぜECSなのか?
オブジェクト指向のゲームコードに慣れている場合、ECSは最初は違和感があるかもしれません。メリットは以下の通り:
- クラス階層がない — PowerUpがCollectibleでCollectibleがEntityであるかどうかを事前に決める必要がありません。必要なコンポーネントをアタッチするだけです。
- 動作の組み合わせが簡単 — エンティティに
health、enemy、flying、bossコンポーネントをすべて同時に持たせることができ、多重継承は不要です。 - 高速なクエリ — エンジンはコンポーネントを連続メモリに格納するため、特定のコンポーネントを持つすべてのエンティティの反復処理が非常に効率的です。
- デバッグが簡単 — すべてのエンティティは単なるデータの集まりです。フレームインスペクタで各エンティティにアタッチされているものを正確に確認できます。