その1:https://qiita.com/mas-yo/items/43101d2e5ef073a1f3f5
その2:https://qiita.com/mas-yo/items/f802b5a6a6b3f47eed71
オブジェクト指向はゲームづくりに向かないのその3です。
これまで、オブジェクト指向の「継承」の仕組みと、「オブジェクトをクラスで表す」仕組みが、ゲームづくりにとっては柔軟性が低く、複雑なコードになりがちで、バグを生みやすいことを示して来ました。
では、この問題を解決するためにはどうしたら良いでしょうか?
Entity Component System
そこで筆者が提案したいのが、Entity Component System (以下ECS)による設計です。
ECSというと、UnityのECSを想起する人が多いかも知れませんが、ここでは、一般的なECSによる設計の話をします。
また、UnityのECSでは、どちらかというと実行速度にフォーカスが当たった説明がなされがちですが、ここではECSの設計の柔軟さ、メンテナンス性の良さについて、主に話題にします。
ECSについての予備知識として、Wikipediaや、"Entities, components and systems” by Mark Jordanを軽く読んでおいた方がいいかもしれません。
では、その1で示した、見下ろし型のアクションゲームをECSで実装するとどうなるか、見ていきたいと思います。
同じ流れで、設計を進めてみます。
開発初期
まず開発の初期段階では、プレイヤーと敵がいて、動かして攻撃ができる、程度を作ることになったとしましょう。
エンティティ、コンポーネント、システムの構成を図にしてみました。(クラス設計図だとイメージがつきにくいので、動作時のメモリの状態を描いています)
水色は各種コンポーネントのインスタンス、黄緑は、システムを表す関数です。
各コンポーネントにはエンティティIDが割り振られています(図の紫色の◯)。エンティティは、プレイヤー、敵などのキャラ(オブジェクト指向で言うオブジェクト)を表し、生成される度にユニークなIDが割り当てられます。
ECSでは、プログラム上にPlayer,Enemyといったクラスはありません。
この図で言えば、InputとPositionを持つエンティティは、プレイヤーとして振る舞う、AIとPositionを持つエンティティは、敵として振る舞う、という事になります。
コンポーネントは単なるデータで、機能を持ちません。様々なゲームロジックはシステムの方で記述します。
各システムの動作を見てみましょう。
- Inputシステム
Inputシステムは、Inputコンポーネントを更新するためのシステムです。
OS等の環境からキー入力を取得し、Inputコンポーネントの「進みたい方向」のデータをセットします。
- AI システム
AIシステムは、AIコンポーネントを更新するためのシステムです。
ここでは、AIは単に「進みたい方向」だけをデータとして持っています。
AIシステムは、Positionコンポーネント(のリスト)から、敵の位置を見つけ出し、自分の位置との差分を計算することで、「進みたい方向」をセットします。
※本当は、MoveIntent
コンポーネント(進みたい方向を表すコンポーネント)を1つ作った方が良いと思いますが、ここでは簡単のために、Input,AIがそれぞれ進みたい方向を持つことにしています。
- Velocityシステム
InputまたはAIの情報を元に、そのフレームでの速度を決定します。
将来的に、コリジョンの計算もここで行うことを想定しています。
- Positionシステム
Velocityの情報を元に、Positionを更新します。
一応ここまでで、プレイヤーと敵キャラが動く仕組みまでできました。
上の図を、システムとコンポーネントの関係に着目して図に描いてみると、こうなります。
システムは図の上から順番に呼ばれます。こうして見ると、各コンポーネントが何に依存して変化しているか、データが上から順番に変化しながら伝搬している様子がわかると思います。
さて、ここまでで移動の処理はできたのですが、攻撃してそれがヒットする様にするのは、さらに実装が必要になります。
新たに Animator
コンポーネント、AttackCollider
コンポーネント、DefenseCollider
コンポーネントを追加します。
攻撃の当たり判定はアニメーションに依存するので、Animatorコンポーネントでアニメーションのフレーム値を管理し、それに連動してコライダが変化するようになっています。
ここでは省略しますが、DefenseColliderの「ヒットしているフラグ」が立っていたら、別のシステム(Health
システム的なもの)で、ダメージ計算とHPの減算などを行います。
ここまでで、土台の実装は完了です。
1ヶ月後
さて、開発が始まって1ヶ月が経ちました。ここで企画から「空を飛ぶ敵を作りたい」という要望が来たとします。
空を飛ぶ敵の要件は
- 障害物の上を通れる
- プレイヤーの物理攻撃は当たらない
- アイテムをドロップする際は、周囲の通れる場所にドロップする。
だったとします。
次の実装に移りたいところですが、長くなってしまったので、その4で・・・