Rust + Entity Component System で仕様変更に強いゲーム設計 その３−１ 〜 コンポーネントの設計


## 目次

[その１ 〜 序文](https://qiita.com/mas-yo/items/e7d1e247e5d4a9f47e92)
[その２ 〜 キャラの移動](https://qiita.com/mas-yo/items/207244f04dd442cb567c)
【イマココ】[その３−１ 〜 コンポーネントの設計](https://qiita.com/mas-yo/items/e1f89bea5f84b7041462)
[その３−２ 〜 システムの設計](https://qiita.com/mas-yo/items/ca97fe6ed961d77be715)
[その３−３ 〜 メイン部分](https://qiita.com/mas-yo/items/9f0459dd942a874c6273)
[その４−１ 〜 剣を表示](https://qiita.com/mas-yo/items/daae411935d138d85c95)
[その４−２ 〜 アニメーションコンポーネント](https://qiita.com/mas-yo/items/678d775e4caf5e14abe7)
[その４－３ 〜 アニメーションを動かす](https://qiita.com/mas-yo/items/3db0a920cb76c75bca51)
[その５－１～ あたり判定](https://qiita.com/mas-yo/items/598ed52f5c08a0375ead)
[その５－２～ やられアニメーション](https://qiita.com/mas-yo/items/cf017ac1ebdd7ce25d86)
[その６ 〜 これまでの振り返り](https://qiita.com/mas-yo/items/22ead1dc226da4df54e2)

## 前回まで

前回は、プレイヤーキャラを動かすためのコンポーネントと、それを扱うシステムを定義して、プレイヤーキャラを動かせるようにするところまでを実装しました。
ただ、扱うエンティティが１つしかなかったので、エンティティを管理する仕組みはありませんでした。

今回は、エンティティを管理する仕組みを実装し、敵キャラを表示して、動かすところまでをやりたいと思います。

## 今回つくったもの

![game.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227302/35fdeb64-f986-692e-4f55-d37ad2f853f9.gif)

緑がプレイヤーキャラ、赤が敵キャラです。
敵キャラは、一定距離までプレイヤーが近づくと追いかけてくるようにしました。

今回つくったソースは、こちらです。

https://github.com/mas-yo/rust-ecs-game/tree/step-3

gitpodでソースを見たり動かすことができるようになっています。
詳しくは [その２](https://qiita.com/mas-yo/items/207244f04dd442cb567c
) を参照してください。




## エンティティとコンポーネント

ECSにおけるエンティティとは、プレイヤーキャラや敵キャラなど、ゲーム内のオブジェクトを表す言葉です。
そしてコンポーネントは、細分化された１つ１つ機能を実現するためのデータの事でした。

例えば、前回作ったプレイヤーキャラのエンティティは、Input、Velocity、Position といったコンポーネントを持ちます。


今回作る敵キャラは、自動で動くため、Inputは必要ありません。その代わり今回は MoveTargetというコンポーネントを持つことにし、敵キャラはMoveTargetに向かって自動で移動する、という処理にしたいと思います。

ゲーム内にプレイヤーキャラ１体、敵キャラ１体がいた場合

- Input 1個
- MoveTarget 1個
- Velocity 2個
- Position 2個

となります。これらはそれぞれ `Vec` で持つことにしますが、例えばVelocityを更新しようとした場合、それがInputに紐付いている（プレイヤーキャラ）のか、MoveTargetに紐付いている（敵キャラ）のかを判別できなければなりません。

そこで、各コンポーネントには、エンティティIDを持つことにします。
IDが同じコンポーネントは、同じエンティティ、ということになります。

## コンポーネントの実装

コンポーネントをどう実装するか、いろいろ検討しましたが、結局下記のようになりました。

今回の設計では、できるだけランタイムコストは払わず、「コンパイル時に決められることはコンパイル時に決める」という方針で行きます。

抽象度や汎用性は下がってしまいますが、その方が、コンパイラによるチェックの恩恵を最大限に受けられますし、思わぬランタイムエラーに悩まされる可能性も減るからです。

アプリケーションのレイヤでは、その方が良い、というのが筆者の考えです。


- Component

```Rust
pub(crate) type EntityID = u32;

pub(crate) struct Component<T> {
    entity_id: EntityID,
    inner: T,
}

impl<T> Component<T> {
    pub fn new(entity_id: EntityID, inner: T) -> Self {
        Self {
            entity_id: entity_id,
            inner: inner,
        }
    }
    pub fn entity_id(&self) -> EntityID {
        self.entity_id
    }
    pub fn inner(&self) -> &T {
        &self.inner
    }
    pub fn inner_mut(&mut self) -> &mut T {
        &mut self.inner
    }
}
```

`Component` は、内部にエンティティIDと、実際に扱うデータが入ります。
内部データにアクセスするための、 `inner()` `inner_mut()` も作りました。

`Deref` を使いたくなるところですが、アンチパターンらしいのでやめました。
その代わり、後述するイテレータが、直接内部データへの参照を返してくれるようになっています。

つづいて、コンポーネントをまとめて保持する、 `ComponentContainer` です。
名前が長いので、 `CContainer` というエイリアス名も作りました。

- ComponentContainer

```Rust

pub(crate) type CContainer<T> = ComponentContainer<T>;

#[derive(Default)]
pub(crate) struct ComponentContainer<T> {
    map: HashMap<EntityID, usize>,
    vec: Vec<Component<T>>,
}

impl<T> ComponentContainer<T> {
    pub fn push(&mut self, entity_id: EntityID, item: T) {
        self.vec.push(Component::<T>::new(entity_id, item));
        self.map.insert(entity_id, self.vec.len() - 1);
    }
    pub fn get(&self, entity_id: EntityID) -> Option<&T> {
        let index = self.map.get(&entity_id)?;
        Some(self.vec[*index].inner())
    }
    pub fn get_mut(&mut self, entity_id: EntityID) -> Option<&mut T> {
        let index = self.map.get(&entity_id)?;
        Some(self.vec[*index].inner_mut())
    }
    pub fn iter(&self) -> ComponentIter<T> {
        ComponentIter {
            iter: self.vec.iter(),
        }
    }
    pub fn iter_mut(&mut self) -> ComponentIterMut<T> {
        ComponentIterMut {
            iter: self.vec.iter_mut(),
        }
    }
}

```

`ComponentContainer` 内部では、実際のコンポーネントを入れる `Vec` と、エンティティIDをキーとしてコンポーネントを参照するための `HashMap` をおいてあります。
（この設計だと、コンポーネントを削除するときに面倒・・・！　あとで考えます）

`iter()` `iter_mut()` は専用のイテレータ `ComponentIter` と　`ComponentIterMut` を返します。
エンティティIDとコンポーネントの中身をタプルで返してくれるイテレータです。


- ComponentIter, ComponentIterMut

```Rust

pub(crate) struct ComponentIter<'a, T>
where
    T: 'a,
{
    iter: std::slice::Iter<'a, Component<T>>,
}
impl<'a, T> Iterator for ComponentIter<'a, T> {
    type Item = (EntityID, &'a T);
    fn next(&mut self) -> Option<Self::Item> {
        let next = self.iter.next()?;
        Some((next.entity_id(), next.inner()))
    }
}
impl<'a, T> ComponentIter<'a, T> {
    pub fn zip_entity<U>(self, other: &'a CContainer<U>) -> ZipEntity<'a, T, U> {
        ZipEntity {
            base: self,
            other: other,
        }
    }
    pub fn zip_entity2<U, V>(
        self,
        other1: &'a CContainer<U>,
        other2: &'a CContainer<V>,
    ) -> ZipEntity2<'a, T, U, V> {
        ZipEntity2 {
            base: self,
            other1: other1,
            other2: other2,
        }
    }
}
pub(crate) struct ComponentIterMut<'a, T>
where
    T: 'a,
{
    iter: std::slice::IterMut<'a, Component<T>>,
}
impl<'a, T> Iterator for ComponentIterMut<'a, T> {
    type Item = (EntityID, &'a mut T);
    fn next(&mut self) -> Option<Self::Item> {
        let next = self.iter.next()?;
        Some((next.entity_id(), next.inner_mut()))
    }
}
impl<'a, T> ComponentIterMut<'a, T> {
    pub fn zip_entity<U>(self, other: &'a CContainer<U>) -> ZipEntityMut<'a, T, U> {
        ZipEntityMut {
            base: self,
            other: other,
        }
    }
    pub fn zip_entity2<U, V>(
        self,
        other1: &'a CContainer<U>,
        other2: &'a CContainer<V>,
    ) -> ZipEntity2Mut<'a, T, U, V> {
        ZipEntity2Mut {
            base: self,
            other1: other1,
            other2: other2,
        }
    }
}
```

`ComponentIter` `ComponentIterMut` に、 `zip_entity()` というメソッドを作りました。
これは、他の `ComponentContainer` を受け取って、 `ZipEntity` というイテレータを返します。
`ZipEntity` は、同じエンティティIDを持つコンポーネントを順に返してくれるイテレータです。

同種のものとして、 `ZipEntityMut`, `ZipEntity2`, `ZipEntity2Mut` があります。
`ZipEntity` については、前述のソースコードを参照してください。

長くなってしまったので、[次の投稿](https://qiita.com/mas-yo/items/ca97fe6ed961d77be715)で、システムの設計をしていきます。