ノートの端の書き残し

UnityやらC#やら。設計が得意かもしれない。

Unityにおける、非MonoBehaviourなC#の依存関係と、Hierarchyの木構造は分離されるべきだった説

C#のオブジェクト依存関係図

私たちがUnityで開発している際に書くスクリプト。例えば以下のような2つのクラスがあるとします。

public class ClassA
{
    private ClassB _instanceB = new ClassB();
}

public class ClassB
{
    ......
}

このとき、クラスA(または、クラスAのインスタンス)はクラスB(またはクラスBのインスタンス)に依存していると言えます。
f:id:u_osusi:20220214001905p:plain:w100
とか書きます。多分。すみません点線とか実線かは特に関係無いです。(クラス図あまり書き慣れてない)

上手くアーキテクチャが意識されている場合、エントリポイントが1つだけ存在し、そこから一方向にのみ依存の矢印が伸びることになります。 このとき、クラスの依存構造は綺麗な木構造となります。
f:id:u_osusi:20220213232915p:plain:w200
ClassAがエントリポイントで、リモートサーバとのやり取りするモジュールだとかシーン内の演出ロジックだとかに依存しているわけですね。

Hierarchyの木構造

Transformには親子関係があり、全てのシーン上に存在するGameObjectはそれを使って他のGameObjectと親子関係となっています。 GameObjectだけでなく、GameObjectにはComponentをアタッチすることができ、これも、GameObjectを親とする木構造と見ることができます。
f:id:u_osusi:20220213233445p:plain:w300

MonoBehaviourと非MonoBehaviour

私たちがUnityで開発する際、MonoBehaviourを使用することで、シーンにコンポーネントとして配置できるクラスを作成することができます。何を当たり前のことを言ってるんだという感じですが、これってなかなか恐ろしいことだと思います。 だって、Unity開発中には2つの木構造が存在し、実はお互いに共通する部分が存在するんですよ。例えば、C#クラスの依存関係がこうなってて
f:id:u_osusi:20220213234621p:plain:w100
Hierarchyの木構造がこうなってる場合
f:id:u_osusi:20220213234651p:plain:w300
それぞれは凄くシンプルでわかりやすいのに、実際には「Classあいう」は両方に存在してるから
f:id:u_osusi:20220213234732p:plain:w300
こうなってるんですよ。
恐らく皆、SerializeFieldでコンポーネントを参照する場合、自分がアタッチされているGameObjectの子に当たるコンポーネントを参照するようにしていると思われますが、それは大正解で、そうしないと実際の依存関係がループして地獄が生まれるためですね。

MonoBehaviourは正しかったのか

C#という共通言語でシーン上のオブジェクトの振る舞いと、シーンに現れないロジックの両方が記述できるのは良いです。必要な知識は少ない方が良いに決まっています。が、その両方の世界を簡単に横断できてしまうことはあまり良くなかったのではないかと思います。 GameObject.FindやらGetComponentInParentやら、GameObjectの木構造は無差別に参照を取れすぎます。MonoBehaviourやGameObjectはpublicメンバが多すぎです。
私たちにできることは、ロジックの木構造にHierarchyの木構造を持ち込まない、または、持ち込んでいるということを忘れずに、頼りすぎないことです。
可能ならば、DIを使うなどして、またインターフェースを介して、相手がMonoBehaviourであることなど知らずにロジックを記述できるのが良いでしょう。

なんでこんなことを考えたのか

GetComponentInParentを使ってる人がいてキレたからです。(せめてGetComponentか、10000歩譲ってInChildrenにしろ)