PlayMaker勉強会 第1回 ～多対多接続はやめよう編～

# PlayMaker勉強会 第1回 ～多対多接続はやめよう編～

社内のSTYLYシーンクリエイター向けに、PlayMaker勉強会というものを実施しています！
せっかくなので、資料を公開します！

シリーズになっているので、気になるところだけでも読んでみてください！

##### PlayMaker勉強会シリーズ

- PlayMaker勉強会 第1回 ～多対多接続はやめよう編～ ← 本記事
- [PlayMaker勉強会 第2回 ～大量ファイル処理はArrayでやろう編～](https://qiita.com/segur/items/10387b874cb4e28667af)
- [PlayMaker勉強会 第3回 ～ランタイムでマテリアルをいじろう編～](https://qiita.com/segur/items/fae40e8e5fbab10d739f)

# 本記事の対象

以下の方を対象としています。

- UnityのPlayMakerでなにか制作・開発したことがある人
- ただしPlayMakerの設計は、あまり自信がない人


# 気づいたら大量のTransitionでぐちゃぐちゃ

みなさんは以下のようなPlayMaker FSMを見たことがありませんか？


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/1378dec5-4fc0-fdfa-3687-aaaf49ac530e.png)


**大量のStateが** 縦横無尽に **大量のTransition**（ワイヤー）で接続されています！

規則性がありそうな雰囲気はあるので、がんばればどういう処理なのか読み取れそうですが、 **非常に複雑です！**

もしこれが先輩から引き継いだものだとしたら、先輩を恨むと同時に、 **「自分は絶対にこういうFSMを作らないぞ！」** と心に誓うことでしょう…

ただ、そうやって誓ったにもかかわらず、 **気づいたら自分も大量のTransitionを引いていたりします。**

そもそも、なぜこういう状態に陥るのでしょうか？

例題を交えて解説したいと思います！


# 例題

以下の `要件` を満たすPlayMaker FSMを設計したいとします。


- 3つのボタンがあります。
- 3つの展示物があります。
- `ボタン1` をクリックすると、 `展示物1` が表示され、他の展示物は非表示になります。
- `ボタン2` をクリックすると、 `展示物2` が表示され、他の展示物は非表示になります。
- `ボタン3` をクリックすると、 `展示物3` が表示され、他の展示物は非表示になります。
- 展示物が表示されるとき、出現エフェクトが表示されます。
- 展示物が非表示になるとき、消失エフェクトが表示されます。
- 同じボタンを連続でクリックしたとき、出現エフェクトは表示されません。

テレビのリモコンで、チャンネルを切り替えるようなイメージです。
よくあるシーンですね！


# 案A: 多対多接続（アンチパターン）


例題の `要件` を満たすPlayMaker FSMを試しに作ってました！
（以下は **アンチパターンなので真似しないでください！**）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/a9ece6ca-c46e-3210-0b53-101cd3b54a31.png)

同じボタンを連続してクリックしたときに、出現エフェクトが表示されてはいけないので、他のボタンをクリックしたときだけ遷移するようにしました！

丁寧にコメントも書いたし、これで文句ないでしょう！！

いや、 **文句おおありです！**

正直、ぐちゃぐちゃでわけがわかりません！

なんでこんなに複雑になってしまったんでしょうか？

一番の原因は、 **多対多接続** をしていることです！

多対多接続とは、以下のような状態です。


![多対多.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/2cf00382-91ee-72c0-515d-1a4fdafdb5db.jpeg)

この場合、矢印の数は `9本` です。

```math
3 \times 3 = 9本
```

もし仕様変更があって展示物の数が4個になったら、矢印の数は16本になります！
展示物の数が5個になったら矢印の数は25本です！

**矢印の数が多いほど、PlayMaker制作者の作業量は増えます。**

つまり、 **作業量が二次関数的に増えてしまいます！**



# 案B: 多対1接続と1対多接続の組み合わせ

多対多接続ではなく、多対1接続と1対多接続を活用しましょう！
以下のような状態を目指します。


![多対1＆1対多.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/06e53693-8311-d70a-b39e-c3bd2062f1c8.jpeg)


この場合、矢印の数は `6本` です。

```math
3 + 3 = 6本
```

もし仕様変更があって展示物の数が4個になっても、矢印の数は8本です！
展示物の数が5個になっても矢印の数は10本です！

つまり、 **作業量は一次関数的に増えます。**


## 案Bは案Aよりも `n/2` 倍効率的！

展示物の数を `n` 個とすると、

* 案Aの作業量は `n×n` になります。
* 案Bの作業量は `n×2` になります。

|展示物数|1|2|3|4|5|6|7|8|
|---|---|---|---|---|---|---|---|---|
|案Aの作業量(多対多接続)|1|4|9|16|25|36|49|64|
|案Bの作業量(多対1接続と1対多接続)|2|4|6|8|10|12|14|16|

グラフにするとこんな感じ！赤線が案A、青線が案Bです！


| <img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/c9fa1bc0-08d7-ff80-e2a4-885fcf335130.png" /> |
|---|
| グラフ作成: [desmos](https://www.desmos.com/calculator/6pl5rtcdqd) |


展示物数が増えるほど、作業量の差がどんどん大きくなります！

そして、プロジェクトを進めていくうちに展示物数が増えることはよくありますが、 **減ることは滅多にありません。**

**多対多を直接接続する設計は、いますぐやめましょう！**


# 案BをPlayMakerで実現する方法

それじゃどうやって案Bを実現するんだ？という話になります。
多対1接続と1対多接続の2段階に分けて考えましょう！


## Step1: 多対1接続

`多対1接続` とは以下の部分です。


![多対1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/f8be538b-ccb1-186e-a028-84c3a711c370.jpeg)


まず、 `ExhibitManager` という空のGameObjectを作って、そこに空のFSMを作成し、そこに `ExhibitId` という変数を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/ad31408c-a99e-08c3-4415-d445f2fe45bf.png)

`Exhibit` は `展示物` という意味の英単語です。
`ExhibitId` は、現在選択中の展示物のIDを保管する変数です！

次に、各ボタンにFSMを作成し、ボタンクリック時に `ExhibitId` を書き換える処理を追加します。
具体的には、 **[SetFsmInt](https://hutonggames.fogbugz.com/default.asp?W112)** アクションを使って、 `ExhibitManager` の `ExhibitId` を書き換えます。

以下のような感じです！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/9d8e6f45-f15d-879c-768f-26819f4f33c6.png)


これで `多対1接続` の部分は完了です！


## Step2: 1対多接続

次に、 `1対多接続` の部分を考えます。


![1対多.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/8891d724-04c0-55dc-1148-0a82ebc99c94.jpeg)


各展示物にFSMを作成し、 `ExhibitManager` の `ExhibitId` の変化を監視するようにします。

以下のような感じです！


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221845/eece8452-3c13-e8f6-e3e6-65b2cfe0afbb.png)


まず、 **[GetFsmInt](https://hutonggames.fogbugz.com/default.asp?W108)** で `ExhibitManager` の `ExhibitId` の値を取得します。
`EveryFrame` にチェックを入れることで、毎フレーム取得しています。

取得した値に変化があった場合、 **[IntChanged](https://hutonggames.fogbugz.com/default.asp?W514)** アクションがそれを検知して、 `Changed Event` が発火します！

これで、 **展示物IDが変わったときだけ処理を実行できるようになりました！**

同じボタンが連続でクリックされた場合、展示物IDは変化しないので、処理は実行されません！
そのため、出現エフェクトが連続で表示されるような心配もありません。


## 計算負荷は問題ないのか？

`EveryFrame` にチェックを入れたことで、計算負荷は上昇してしまうのでは？という疑問が浮かびます！

結論から言うと、計算負荷は多少上昇しますが、 **無視できるレベル** だと判断しています。

値の取得と比較をしているだけであり、これは重たい処理とは言えないです。

それよりも多対多接続のデメリットの方が大きいです！



# さいごに

今回はここまでです！おつかれさまでした！

次回はArray編です！引き続きこちらも実践していただけると嬉しいです！

https://qiita.com/segur/items/10387b874cb4e28667af

本記事作成にあたり、以下の記事を参考にさせていただきました！
ありがとうございました！

- [PlayMaker / Action Reference](https://hutonggames.fogbugz.com/default.asp?W2)
- [desmos](https://www.desmos.com/calculator?lang=ja)