Unityアセット「Turn Based Strategy Framework」Ver2.2 ドキュメント・FAQの要約

# はじめに

本記事は「Turn Based Strategy Framework」というUnityアセットのドキュメントとFAQの要約です。ドキュメントとFAQは、以下のUnityアセットストアページの説明欄のリンクからダウンロードできます。

https://assetstore.unity.com/packages/templates/systems/turn-based-strategy-framework-50282

「Turn Based Strategy Framework」は、高度なカスタマイズ性を持った、ターン制の戦略ゲームを作るためのフレームワークのようです。2015年に初版が公開されて、以後ずっと開発・改良が続けられていて、2022年5月に最新のVersion 2.2が公開されているようです。

UnityアセットストアのLike数も3000を超えていて結構人気のフレームワークのようです。このフレームワークを使用して作られてSteamなどで販売されているゲームもあるようです（Unityアセットストアでいくつか紹介されています）。

四角形セルと六角形セルに標準対応していて、タイル編集ツール、ユニット配置ツール、AIプレーヤー、経路探索、ターンの管理などなど色々な機能が含まれていて、ファミコンウォーズや大戦略やファイアーエムブレムのような戦略ゲームが簡単に作れそうです。

本記事はVersion2.2の公式ドキュメントとFAQの要約です。なお、記事中の画像やコードのほとんどは公式ドキュメントから抜粋しました。

以下、本題です。

# Turn Based Strategy Framework Documentation Version 2.2

## 1. Introduction | 導入

「Turn Based Strategy Framework」は、高度なカスタマイズ性を持った、ターン制の戦略ゲームを作るためのフレームワークであり、カスタム形状を持ったマップを作ったり、ユニットや障害物を配置したり、人間とAI両方のタイプのプレーヤーが遊ぶことのできるゲームを作ることができるとのこと。

（その他、このドキュメントにどういうことが書かれているかの説明があるけど、見出しを見ればだいたい分かるので省略）

## 2. Project structure | プロジェクト構成

以下のような構成になっている。マップ作成用のエディター拡張やサンプルや、フレームワーク本体などからなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/f064a54f-1b51-f766-8036-29f6d0c0dd4c.png)

サンプルゲームで使用しているアセット情報 
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/6e24c0ba-f595-e452-d6f5-768fad3bbc22.png)


## 3. Scene structure | シーン構成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/bb642d05-cbf1-9238-387f-3eee4e36a0b5.png)

サンプルシーンで重要なオブジェクトはCellGrid、Players、Unitsの3つ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/d1a2691a-c0f8-f063-ff46-0afd65a9adf8.png)

### 3.1. CellGrid

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/49f8442b-2902-4507-17ed-a6b31926242c.png)

以下のコンポーネントがつけられている。
- **CellGrid**: セルやユニットの管理や、ゲームスタートやターン遷移などを担当する
- **CustomUnitGenerator**: ユニットを読み込んでゲームに追加するのを担当する
- **SubsequentTurnResolver**: どのプレーヤーやユニットが次に行動するかを決めるのを担当する（置き換え可能）
- **DominationCondition**: ゲームオーバーになったか、誰が勝者かの判断を担当する（置き換え可能）

### 3.2 Players

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/b699911e-7f00-979b-2b99-1e11e1bc7475.png)

- Playerは、Playerコンポーネントがつけられているオブジェクトのことを指す。Playerの数は無制限（最低1つ以上は必要）
- HumanPlayerコンポーネントかAIPlayerコンポーネントがつけられている
- AIプレーヤーについては後の章で説明がある。
- （NaiveAiPlayerというVer2.1までで使われていた非推奨のコンポーネントもあるので注意）

### 3.3. Units

- UnitsオブジェクトにゲームのUnitを追加していく。ここ以外にUnitがいると上手く動かずエラーになるので注意
- 各UnitのPlayer NumberフィールドをそのUnitの持ち主のPlayerのPlayer Numberと一致させる必要がある
- Player Numberが正しくセットされていないUnitを追加することもできるけど、そのUnitは制御することはできない

シーンの構成の説明は以上。

## 4. Creating a scene | シーンの作成

上の構成を自分で作っていくのは大変なので、GridHelperを使ってセットアップしていく。GridHelperは、シーンの初期状態をセットアップしてくれる強力なカスタムエディター。この章でシーンの準備に必要なプレハブとシーン生成プロセスを学ぶ。 

### 4.1. Cell prefab | Cellプレハブ

- Gridを初期化していくにはCellのプレハブが必要
- Cellは、Cellクラスを継承したコンポーネントを持ったGameObjectのプレハブのことを指す
- Cellは、マウスイベントに対応するためにコライダーが必要
- Cell、Square、Hexagonという3つの抽象クラスがある。CellはSquare、Hexagon以外の形のセル（三角形など）を使いたい場合に使う
- Cellを使う場合は以下の抽象メソッドを実装する必要がある。Square、Hexagonを使う場合は実装済み
    - `int GetDistance(Cell other)`
    - `List<Cell> GetNeighbours(List<Cell> cells)`
    - `void CopyFields(Cell newCell)`
- Cell、Square、Hexabonは以下の仮想メソッドを持っていて、こいつをオーバーライドして色々動きをカスタマイズできる。次の章で詳しく説明される
    - `void MarkAsReachable()`
    - `void MarkAsPath()`
    - `void MarkAsHighlighted()`
    - `void UnMark()`
- 以下の仮想メソッドは必ずカスタマイズする必要がある
    - `Vector3 GetCellDimensions()`
    - GetCellDimensionsは、Cellの寸法を返すメソッド。Pixels per Unitを10にセットしていて、スプライトが16pixelsなら、Vector3(1.6f, 1.6f, 0)を返す感じで良い（=普通にUnity上の大きさでいい）

### 4.2. Grid generation | グリッドの生成
- Cellのプレハブが作れたら、グリッドを作れる
- グリッドはGridHelperを使って作る
- メニューのWindow -> Grid Helperから開ける
- ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/9bd84dec-7b26-6bc4-b13f-ec7dc9334ba6.png)
- - GridHelperは、シーンの初期構造をセットアップするのが仕事
- 各設定の意味は以下の通り
    - **Use movable camera (bool)**: 移動可能カメラスクリプトを追加する必要があればtrue。trueにしたら、画面端にカーソルを持っていったらスクロールする感じになる
    - **Human players no**: 人が操作するPlayerをいくつ生成するか 
    - **AI players no**: AIプレーヤーをいくつ生成するか
    - **Plane**: どういう平面にGridを生成するか。XY or XZ
    - **Generator**: 生成するGridの形（セルの形とグリッド全体の形）を指定する。以下の種類がある
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/542f0984-f67a-4073-9a38-2c59d8ab8baf.png)
- 上記の設定が終わったら「Generate Scene」でシーンの生成ができる。上で説明したゲームに不可欠なCellGridやPlayersやUnitsを生成し、カメラやライトや、ターン遷移できる単純なUIが作成される

↓空のシーンでGridHelperを開いてGenerate Sceneしたあとの様子 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/ca43702a-b0b7-4444-2e39-81e1ebbfad34.png)

### 4.3. Tile painting | タイルの配置

ペイント感覚でタイルを配置できるGridHelperの機能。プレハブをセットしてEnter Edit Modeしてシーンビューから配置する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/e660341f-73f9-e96f-6c1b-6e954c609d20.png)


### 4.4. Unit prefab | Unitプレハブ

- Cell以外にUnitのプレハブも作る必要がある。Unitクラスを継承したコンポーネントをつけたゲームオブジェクト
- 以下の仮想メソッドをオーバーライドして見た目をカスタマイズする。詳しくは次の章で説明する
    - `void MarkAsDefending(Unit aggressor)`
    - `void MarkAsAttacking(Unit target)`
    - `void MarkAsDestroyed()`
    - `void MarkAsFriendly()`
    - `void MarkAsReachableEnemy()`
    - `void MarkAsSelected()`
    - `void MarkAsFinished()`
    - `void UnMark()`

### 4.5. Unit painting | ユニットの配置

Tile paintingと似たようなもの。お絵描き感覚でUnitプレハブを配置できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/3e339a1a-2789-3eba-c078-bfe83c564eb1.png)

### 4.6. Prefab Helper | プレハブ化ヘルパー

GridHelperの機能。シーンビューでプレハブにしたいオブジェクトを複数選択して実行すると同時に複数のオブジェクトをプレハブ化できる。ただそれだけ。（Unityでオブジェクトをプレハブ化するとき一つ一つオブジェクトをプロジェクトへドラッグアンドドロップするのが面倒なのに対処するために用意された機能）


## 5. Customization | カスタマイズ

TBS Frameworkの強みの一つがこのカスタマイズ性の高さとのこと。

### 5.1. Cell customization | セルのカスタマイズ

以下のメソッドでカスタマイズする。

- `void MarkAsReachable()`
    - ユニットの移動可能範囲のセル用の見た目を設定する
- `void MarkAsPath()`
    - ユニットの移動経路のセル用の見た目を設定する
- `void MarkAsHighlighted()`
    - マウスオーバーしているセル用の見た目を設定する
- `void UnMark()`
    - 通常状態のセル用の見た目を設定する

以下の画像は各状態のときの例。左から通常状態、選択状態、移動経路状態、移動可能状態、みたいな感じ。色を変えるだけでなく、画像を変えたりパーティクルエフェクトを使ったりということも可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/52a31491-34f0-3d2e-0baa-d3dfbc0cdc5e.png)

以下の2つのメソッドでセルの動作を変えられる。

- `int GetDistance(Cell other)`
    - 引数のセルからの距離を返す。デフォルトはマンハッタン距離。距離は経路探索や攻撃範囲の計算に使われる。
- `List<Cell> GetNeighbours(List<Cell> cells)`
    - 隣接するセルを返す。隣接するセルは経路探索の計算に使われる。

### 5.2. Unit customization | ユニットのカスタマイズ

セルと同様にユニットの見た目もカスタマイズできる。

- `void MarkAsFriendly()`・・・味方
- `void MarkAsSelected()`・・・選択中
- `void MarkAsReachableEnemy()`・・・攻撃範囲の敵
- `void MarkAsFinished()`・・・行動済み
- `void MarkAsDefending()`・・・防御中
- `void MarkAsAttacking()`・・・攻撃中
- `void MarkAsDestroyed()`・・・破壊された
- `void UnMark()`・・・通常状態

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/01092f19-1e85-6c71-80fa-15be75c2ae36.png)

以下のカスタマイズポイントもある。（マウスオーバーしているユニットの情報を表示するなどに使う？）

- `void OnMouseDown()`
- `void OnMouseEnter()`
- `void OnMouseExit()`

上記のメソッドからUnitClicked、UnitHighlighted、UnitDehighlightedというイベントも発生する（？）

以下のような仮想メソッドもある。

- `void OnUnitSelected()`・・・選択時
- `void OnUnitDeselected()`・・・選択解除時
- `void OnTurnStart()`・・・ターン開始時
- `void OnTurnEnd()`・・・ターン終了時
- `void OnDestroyed()`・・・破壊時
- `bool IsUnitAttackable(Unit other, Cell sourceCell)`・・・攻撃可能ならtrue
- `AttackAction DealDamage(Unit unitToAttack)`・・・ダメージ計算（攻撃側）
- `void AttackActionPerformed()`・・・攻撃実行時
- `int Defend(Unit aggressor, int damage)`・・・ダメージ計算（防御側。地形効果とか用に攻撃側と防御側に分割されている）
- `void DefenceActionPerformed()`・・・防御実行時
- `bool IsCellMovableTo(Cell cell)`・・・移動可能ならtrue
- `bool IsCellTraversable(Cell cell)`・・・通過可能ならtrue
- `void Move(Cell destination, List<Cell> path)`・・・移動

上記はデフォルト実装もあるので、オーバーライドする場合は親クラスのメソッドを呼び出す必要がある。

### 5.3. Unit special abilities | ユニットの特殊アビリティ

ユニットはアビリティを持つ。移動や攻撃もアビリティの一種として実装される。

アビリティはUnitクラスにAbilityクラスを継承したコンポーネントをつけることで有効にできる。

Abilityを継承したクラスは以下のメソッドを定義する。

```
void Display(CellGrid cellGrid)
void CleanUp(CellGrid cellGrid)
```

上記はアビリティが選択・選択解除されたときに呼ばれる。移動範囲を表示したり、攻撃対象を表示したり、それらの表示を消したりするのに使われる。これらはhuman playerのみで呼ばれる表示用のメソッド。

```
void OnUnitClicked(Unit unit, CellGrid cellGrid)
void OnUnitHighlighted(Unit unit, CellGrid cellGrid)
void OnUnitDehighlighted(Unit unit, CellGrid cellGrid)
void OnCellClicked(Cell cell, CellGrid cellGrid)
void OnCellSelected(Cell cell, CellGrid cellGrid)
void OnCellDeselected(Cell cell, CellGrid cellGrid)
```

上記もhuman playerでのみ呼ばれる。例えばOnUnitClickedは主に選択したユニットに対して実際にアビリティを実行するのに使われる。

```
void OnAbilitySelected(CellGrid cellGrid)
void OnAbilityDeselected(CellGrid cellGrid)
void OnTurnStart(CellGrid cellGrid)
void OnTurnEnd(CellGrid cellGrid)
void OnUnitDestroyed(CellGrid cellGrid)
```

上記は、playerの種類に関係なく呼ばれる。OnTurnStartやOnTurnEndは例えばパッシブアビリティの実装に用いられる。

```
bool CanPerform(CellGrid cellGrid)
```

上記は、実際にアビリティを実行できるかの判断に使われる。

```
IEnumerator Act(CellGrid cellGrid)
```

上記は実際にアビリティを実行するのに使われる。アニメーションなどを利用できるように、コルーチンとして実行する。

Actは直接呼び出すのではなく、AbilityクラスのExecuteから呼び出すのが良い（？）。Executeは、コルーチン実行前と実行完了後に呼び出される2つのコールバックを引数に取る。デフォルトでHumanExecuteという入力を全部無効にする便利実装や、AIExecuteという何もしない便利実装もある。 

アビリティの選択はCellGridStateAbilitySelectedというオブジェクトを通して行われる。他にも色々CellGridStateがあり、次の章で解説される。


### 5.4. Customization examples | カスタマイズの実例

セルのカスタマイズの実例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/0fb73cb4-ba71-a647-0521-18a4a44cd88a.png)


ユニットのカスタマイズの実例。水のセルは進入禁止にしたり、飛行ユニットは他の飛行ユニットがいるセルを横断できないようにしたり。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/8088552c-520a-061c-ea3d-26b4698ead8e.png)

三すくみのダメージ計算例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/e8eb6174-9f66-0daa-7e40-d3cdf95e2ee4.png)

GUIのカスタマイズ方法は、サンプルのGUIControllerのソースなどを参照する。CellGridなどからイベントを受け取って更新している。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/8ab54d85-1b44-8f78-e2db-6bc409e3abdd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/6c551a5a-d30d-8e02-aca9-c8909dfe4f11.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/5c3929ef-8c24-6a2d-a667-371705fca478.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/cc10f13a-0d2d-5957-026e-e75e455294de.png)


## 6. The AI Player | AIプレーヤー

この章ではルールベースのAIの利用方法について説明する。

### 6.1. Overview | 概要

AIの利用にはAIPlayerコンポーネントをアタッチしたゲームオブジェクトが必要。通常はGridHelperによって生成されるので手動で作る必要はない。AIPlayerにはUnitSelectionというコンポーネントが必要。デフォルトで基本的な実装が用意されている。

AIはルールベースで、AIActionという各ユニットにアタッチされたコンポーネントを使う。AIActionはユニットが取ることができる一つのアクションを表す。通常複数のAIActionが一つのユニットにアタッチされている。AIPlayerは各AIActionを順番に処理して実行が必要か評価し、行動する。

### 6.2. AI Actions overview | AIActionの概要
一つのAIActionは、通常はユニットのアビリティの一つと紐づいているが、複数と紐づいていることもある。逆に一つのアビリティが複数のAIActionと紐づいていることもある。

サンプルプロジェクトではMoveToPositionAIActionと、AttackAIActionといくつかの特定のAIActionがある。

AIActionは以下のようなインターフェイスを持つ。

- `bool ShouldExecute(Player player, Unit unit, CellGrid cellGrid)`
    - アクションを実行すべきならtrueを返す
- `Precalculate(Player player, Unit unit, CellGrid cellGrid)`
    - Executeの前に呼ばれる。例えばどのユニットを攻撃するかを決めるのに使う
- `IEnumerator Execute(Player player, Unit unit, CellGrid cellGrid)`
    - アクションを実行する。Ability.Actと同様にコルーチンになっている。理由も同様
- `ShowDebugInfo(Player player, Unit unit, CellGrid cellGrid)`
    - デバッグ情報を表示する。AIのデバッグ方法は次の節で解説される
- `CleanUp(Player player, Unit unit, CellGrid cellGrid)`
    - Executeの後に呼ばれる。Precalculateで変えたセルの表示を元に戻すのに使うなどする

### 6.3. Basic AI Actions | 基本的なAIAction

サンプルでは以下のようにコンポーネントがセットされている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/7263f459-286a-a6f3-e58a-4eab36d946cc.png)

#### 6.3.1. MoveToPositionAIAction

MoveToPositionAIActionはMoveAbilityに対応する。ShouldExecuteが一番大事な処理を行っている。以下のような感じ

1. 現在のセルより高い「スコア」を持つセルがないか確認する
2. 「スコア」はcell evaluatorsの合計値
3. 計算対象のセルは、移動範囲外のセルも含まれる
4. 移動範囲外のセルが選ばれた場合は、そのセルに最も近いセルか、セルまでの経路で最もスコアの高いセルを移動先にする。この動作はShouldMoveAllTheWayで制御できる

サンプルでは、DamageCellEvaluator、DistanceCellEvaluator、DamageReceivedCellEvaluator、AlliesNearbyEvaluator、PositionProximityCellEvaluator、UnitProximityCellEvaluator、RandomCellEvaluatorというcell evaluatorが用意されている。

cell evaluatorは共通してfloat Weightというプロパティを持つ。Weightはインスペクターからセットできる。デフォルトは1。

以下のメソッドを持つ。

- `void Precalculate(Unit evaluatingUnit, Player currentPlayer, CellGrid cellGrid)`
    - 色々なデータをキャッシュして連続するセルの評価で使い回せるようにする
- `float Evaluate(Cell cellToEvaluate, Unit evaluatingUnit, Player currentPlayer, CellGrid cellGrid)`
    - セルを評価する

各cell evaluatorの説明。

- **DamageCellEvaluator**: そのセルから与えられるダメージから評価値を計算する
- **DamageReceivedCellEvaluator**: そのセルで受けるダメージから評価値を計算する
- **DistanceCellEvaluator**: そのセルに到達するまでにかかるターン数から評価値を計算する
- **AlliesNearbyEvaluator**: そのセルにいくことで隣接する味方の数から評価値を計算する
- **PositionProximityCellEvaluator**: そのセルと他のセルの距離から評価値を計算する（？）
- **UnitProximityCellEvaluator**: そのセルと他のユニットの距離から評価値を計算する
- **RandomCellEvaluator**: ランダムにスコアを加える。ゲームプレイに多様性をもたらす

#### 6.3.2. AttackAIAction

AttackAIActionはAttackAbilityに対応する。ShouldExecuteが以下の処理を行う。

1. 攻撃範囲にユニットがいるか確認する
2. Precalculateでevaluatorsの合計スコアが一番高いユニットを選択する
3. Executeで攻撃を行う

UnitEvaluatorもCellEvaluatorと似たような感じのもの。

サンプルでは以下の2つのevaluatorが用意されている。

- **DamageUnitEvaluator**: 与えられるダメージ量から評価値を計算する
- **HPUnitEvaluator**: 攻撃による残りHP量から評価値を計算する 

### 6.4. Debugging AI Actions | AIActionのデバッグ

AIPlayerのDebugModeフラグをONにすることでAIの動作をデバッグできる。

AIAction.ShowDebugInfoの出力がログに出るようになる。サンプルのAIActionはこの機能に対応済み。自前でAIActionを作る場合は自前で実装する必要がある。コンソールにテキストを出力したり、セルを評価値でカラーリングしたりという機能がある。

デバッグモードをONにすると、ユニットを選択してAIActionを選択して実行するように求められる。AIActionを実行する直前に、デバッグ情報が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/15e16e1a-f463-5059-9b65-522a7b065348.png)

### 6.5. Unit Selection order | ユニット選択の順番

AIがどういう順番でユニットを選択して行動させていくかも決められる。UnitSelectionというコンポーネントがこの役割を担っている。

```
IEnumerable<Unit> SelectNext(List<Unit> units, CellGrid
cellGrid)
```

というメソッドで順番を決める。

サンプルでは以下の二つの実装がある。

- **MovementFreedomUnitSelection**: 移動先候補の多いユニットから先に行動していく
- **SubsequentUnitSelection**: 特に順番の変動なく（Unitsの子要素リストの上から？）順番に行動していく


### 6.6. Conclusion | 締め

TBSの将来のバージョンではUnity ML Agentsを使って強化学習によるAIの導入もできないか検討している。 


## 7. Game state management | ゲームの状態管理

この章ではユーザーの操作、ターン遷移、ゲームオーバー条件などのメカニズムについて説明する。

### 7.1. User interaction | ユーザー操作

ユニットの選択や移動、攻撃は、CellGridのCellGridStateプロパティにセットされているCellGridStateのサブクラスが処理する。Stateデザインパターンを知っているならこのメカニズムが簡単に把握できるはず。

CellGridStateは抽象クラスで、セル選択時、選択解除時、クリック時、ユニット選択時などのコールバック用メソッドを持っている。

また、OnStateEnterとOnStateExitを持っていて、初期化やクリーンアップが行える。

これらのメソッドはCellGridから呼ばれる。

サンプルプロジェクトでは以下のCellGridStateがある。

- **CellGridStateUnitSelected**: ユニット選択時の状態
- **CellGridStateAbilitySelected**: アビリティ選択時の状態（実際にアビリティを実行するのに用いられる）
- **CellGridStateWaitingForInput**: OnUnitClickedのみを実装していて、もし自軍ユニットがクリックされたらCellGridStateAbilitySelected状態に遷移する
- **CellGridStateBlockInput**: 何もメソッドを実装していなくて、ユーザーの入力を無効にしたいときに使う。AIのターンやゲーム終了後など

### 7.2. Turn transitioning | ターン遷移

CellGrid.EndTurnが呼ばれるとターンが終わる。サンプルでは、プレーヤーの場合はGUIボタンをクリックするとEndTurnが呼ばれる。AIプレーヤーの場合は直接EndTurnメソッドを呼ぶ。

Ver2.1からターン遷移のカスタマイズの仕組みが導入された。これを使うことでプレーヤの交代順序（やユニットの行動順）をカスタマイズできる。TurnResolverを継承したコンポーネントをCellGridにアタッチすることでカスタマイズできる。

サンプルでは以下のTurnResolverがある。

- **SubsequentTurnResolver**: デフォルトのTurnReslver。player numberに従って順番にプレーヤーが切り替わる
- **SpeedTurnResolver**: ユニットのスピードによってプレーヤーが切り替わる。各ターンでは一つのユニットのみしか移動できない

ターンの切り替わりでは以下のようなことが起きる。

1. そのターンで行動可能だった全てのunitに対してOnTurnEndが呼ばれる
2. 次のターンのプレーヤーが選ばれる
3. 次のターンで行動可能な全てのunitに対してOnTurnStartが呼ばれる
4. 最後に次のターンのプレーヤーオブジェクトに対してPlayメソッドが呼ばれる
5. プレーヤーが人間なら、CellGridStateWaitingForInputがCellGridのStateにセットされ、操作可能になる。AIプレーヤーなら操作がブロックされ、AIが処理を実行していく

### 7.3. Ending the game | ゲーム終了

Ver2.1からゲーム終了条件をカスタマイズできるようになった。

GameEndConditionを実装するか既存の以下のコンポーネントを使う。

- **DominationCondition**: 全ての敵を倒せば勝ち。GridHelperを使った場合デフォルトでCellGridにアタッチされている
- **PositionCondition**: 特定のセルに到達したら勝ち
- **ObjectiveDestroyCondition**: 特定のUnitを倒したら勝ち
- **TurnLimitCondition**: 特定のターン数が経過したら勝ち

複数のGameEndConditionをCellGridにアタッチしても問題ない。各プレーヤーに違う条件を適用することもできる。例えば一方のプレーヤーは一定ターンまでにヒーローを特定のセルまで移動させると勝ちで、もう一方のプレーヤーはそのヒーローを倒すか一定ターンまで足止めできれば勝ち、みたいに。

条件のチェックは以下のタイミングでのみ行われる。

- ユニットの移動後
- ユニットの破壊後
- ターン終了後

もし上記以外のタイミングでチェックしたい場合は、CellGrid.CheckGameFinishedを手動で呼び出せば良い。

ゲーム終了になったら、CellGrid.GameOverイベントが呼ばれる。その引数には勝者と敗者のユーザーのリストが渡される。これをGUIに表示すれば良い。

## 8. Tutorial | チュートリアル

このセクションでは最低限の機能を持ったシーンを一から作っていく。このシーンはキューブ状のセル、キューブ状のユニット、キューブ状の障害物からなる。最終的なシーンの完成形はExamples/Tutorialフォルダーにある。

手順1. Unityエディターで新しいシーンを作成して開く。

手順2. Cubeゲームオブジェクトを作る。これをセルのプレハブとする。Box Colliderがついていなければ付ける（マウス操作検知のため）。

手順3. SampleSquareというスクリプトを作る

手順4. SampleSquareはSquareクラスを継承し、外見カスタマイズ用のメソッドをいくつかオーバーライドして以下のようにする。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/bf0faf97-0ce9-261a-701e-6f7a2bdb400e.png)

手順5. SampleSquareをCubeにアタッチする。movement costを1にする。プロジェクトエクスプローラーにドラッグしてプレハブ化する

手順6. GridHelperを開く（Windpw -> Grid Helper）

手順7. GirdHelperを以下のように設定する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/e9dda980-2a3c-566a-0c79-6bc0775be2dc.png)

手順8. 「Generate scene」でシーンを初期化する。以下のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/980c61e6-4f16-01a2-9a0e-c098c8460218.png)

手順9. ユニットを作っていく。SampleUnitというスクリプトを作りUnitを継承させる。味方ならライトグリーン、選択は緑、攻撃可能なら赤、行動完了後はグレーという感じにする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/5b3c6089-a166-5f64-8efd-b3ea774a39b6.png)

手順10. 二つの色違いのマテリアルを作る。

手順11. 二つの空のゲームオブジェクトを作り、SampleUnitをアタッチする。ゲームオブジェクトにキューブを追加し、マテリアルを付ける。プレハブ化する。

手順12. キューブをユニットの子要素にしたのは、ユニットがセルの真ん中に配置されたあと、ユニットの位置を微調整したいため。キューブの位置をY方向に0.9動かす。

手順13. キューブの親オブジェクトにBoxコライダーをつけて、キューブの位置に合わせる。

手順14. SampleUnitのパラメーターを埋めていく。以下が例。色々実験して値を決めていくと良い。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/1bf8f0a5-7e8f-f4c6-175d-c9c7f123af82.png)

手順15. GridHelperのUnit Painterを使ってユニットを配置していく。

手順16. Player Numberを変えて色々配置していく。

手順17. 障害物セルを作る。新しいキューブを作り、上に黒いマテリアルをセットする。既存のセルプレハブをコピーして乗せる。IsTakenフィールドをtrueにする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/991cf247-8593-f639-d68f-a9d7bf55df9e.png)

手順18. GridHelperのcell painterを使って障害物セルを配置していく。

手順19. デフォルトのGUIControllerは、mキーを押すとターンが終了するようになっていることを確認する。

以上でチュートリアルは終わり。特に面白みはないけど普通に遊ぶことのできるシーンが完成した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2869807/716905b3-32e4-5c8a-bdd3-b8a00e57cca9.png)

## 9. License

省略。一般的なStandard Unity Asset Store EULA。

## 10. Support

省略。unityフォーラムやDiscordやメールで問い合わせしてOKとのこと。

## 11. Conclusion

省略。もっと詳しく知りたい場合はソースやソースコメントも読んでください、提案やフィードバックも歓迎ですという感じ。

## 12. References

サンプルで使用したアセット類への参照がある。

## APPENDIX A - Upgrade from v1.1.2 to v2.0x

省略。

## APPENDIX B - Upgrade from v2.0x to v2.1

省略。


# Turn Based Strategy Framework FAQ Version 2.2

全22問。Asset Store公開後数年間を通してよく聞かれた質問に対する回答がまとまっている。

## 質問1. 移動コストはサポートされていますか？

→はい、サポートされています。本フレームワークではダイクストラ法による経路探索を行います。セルのMovementCostフィールドに値をセットすると、経路の計算時に参照されます。

## 質問2. 遠距離攻撃はサポートされていますか？

→はい、サポートされています。ユニットのAttackRangeパラメーターに攻撃範囲をセットしてください。

## 質問3. ユニットが特殊アビリティを持つことはできますか？

→Ver2.1から全く新しいアビリティシステムが導入されました。ドキュメントとサンプルシーンをご確認ください。

## 質問4. 範囲攻撃はサポートされていますか？

→範囲攻撃はVer2.1のアビリティシステムの完璧なユースケースです。Example 5のシーンのファイアーボールの呪文が実例です。

## 質問5. 複数のセルにまたがって存在するユニットを作成することはできますか？

→いいえ、現在の実装ではできません。もし実装に興味があれば、クリアランスベースの経路探索アルゴリズム（clearance based pathfinding algorithm）を調べてみてください。（例えば細い経路は移動不可、みたいなことを考えないといけない）

## 質問6. 複数のユニットを一つのセルに配置することは可能ですか？

→デフォルトだとIsCellMovableToは、すでに他にユニットがいるセルは移動不可になっています。この制約取り除いた自前の実装を作れば可能です。

## 質問7. 戦場の霧メカニズムはサポートされていますか？

→いいえ、戦場の霧はこのフレームワークではすぐに使える形で提供されていません。色々なユーザーといくつかの実装方法をディスカッションしました。基本的なアイデアは次のようなものです: まず、各ユニットから視認可能なセルのリストを保持しておきます。EndTurnメソッドで、それらのセルの内容を表示するようにします。

## 質問8. 四角形グリッドで、8方向（斜め方向）に移動することは可能ですか？

→その機能のサンプルはありませんが、その機能は簡単に実現することができます。GetNeighboursで8方向のセルのリストを返すだけです。詳しくはSquareのソースを見てみてください。

## 質問9. 複数の高さを持つフィールドを作ることは可能ですか？

→その機能のサンプルはありません。まずセルにheightフィールドを作ります。次にGetNeighboursをオーバーライドして、高さが大きく違わない隣接セルのみを返すようにします。最後にEqualsとGetHashCodeをオーバーライドします。現在の実装は座標しか使っていないので、高さも使うようにします。

## 質問10. 頂点が上に来る形にヘックスを配置できますか？

→サンプルのGrid生成スクリプトは、辺が上に来る形のみをサポートします。頂点が上に来る形で自前で実装したい場合の素晴らしいリソース: https://www.redblobgames.com/grids/hexagons/

## 質問11. 実行時にユニットや障害物の位置がずれます。なぜでしょう？

→ユニットの親のゲームオブジェクト、障害物、セルの位置が(0, 0, 0)であることを確認してください。

## 質問12. 実行時にユニットを生成するにはどうすればいいですか？

→CellGridのAddUnitを使ってください。ユニットを初期化し、適切にイベントハンドラーを設定してくれます。

## 質問13. 手動でグリッドからセルを削除・追加するにはどうすればいいですか？

→まずはmap generatorスクリプト（≒GridHelper？）を使ってCellGridを作ってください（スクリプトを自作することも可能です）。手動でセルを配置することでCellGridを作ったり、CellGridに後から手動でセルを追加することはできません。グリッドの形をカスタマイズしたい場合は、まず何かしらのグリッドをスクリプトで作って、不要なセルを削除することで実現します。セルのゲームオブジェクトのactive状態をゲーム中に変えることで、動的にグリッドの形を変えることも可能です。

## 質問14. ランダムマップを作ることは可能ですか？

→その機能のサンプルはありません。マップ生成スクリプトとランダムにセルを削除・非アクティブにすることで実現できます。

## 質問15. 移動経路を表示するもっと洗練された方法はありますか？

→いいえ、今の実装ではできません。今の方式（MarkAsPath）では経路を構成する他のセルの参照が取れないのが問題です。例えば経路を矢印で表現したい場合について考えてみます。これを実現するためには移動経路の前と次のセルの位置を知る必要があります。なので、解決法としては、全体の経路をMarkAsPathの引数に追加する必要があります。

## 質問16. 移動中にユニットを進行方向に向かせることは可能ですか？

→UnitのMovementAnimationメソッドの中にRotateTowardsメソッドを置いてください。

## 質問17. ステージにterrainを含めることは可能ですか？

→このフレームワークはterrainを念頭に設計されていません。その種類の機能のサポートは含まれていません。（terrainが何を指すのかよく分からなかった。継ぎ目のないグリッド？Unity標準のTerrain？）

## 質問18. インターネット経由のマルチプレイはサポートされていますか？

→いいえ、このフレームワークにはインターネット経由のマルチプレイの機能はありません。

## 質問19. このフレームワークはモバイルに対応していますか？

→このフレームワークで作成したシーンはモバイル版にビルド可能です。モバイル用のコントロール・ウィジェット・バーチャルパッドなどは特に用意されていません。

## 質問20. 他に追加の学習教材はありますか？例えば動画チュートリアルなど。

→動画チュートリアルはありません。ドキュメント、このFAQ、Unityフォーラムのスレッド、アセットのサンプルシーンが全てです。なにか質問があれば作者にメールしてください。

## 質問21. このフレームワークのコミュニティはありますか？

→Unityフォーラム: https://forum.unity.com/threads/released-turn-based-strategy-framework.704129/
→Discordサーバー: https://discord.gg/uBJNPJHFjB

## 質問22. 作者とゲームを共同開発することはできますか？

→私はいつでも他の人のプロジェクトについて聞けるのはうれしいですし、オファーをいただければそのチームに参加することを検討します。 

-- -- --

以上です。