UnityでCloneがCloneを生成し続けヒエラルキーがカオスになった話+解決

# 記載者の経験
C 大学の講義で触れる。約2年程。
Java 実際に何かを作ったのは半年と少し、その他は遊びレベル
C# ほぼ初見。ChatGPTとかで調べながらやっている。
→訳：多分熟達した方々から見るとツッコミどころ豊富だと思いますが、暖かい目で、余裕があればご指摘いただけるとありがたいです。

# やろうとしたこと
[ゲーム作り方ガイド](https://gamedev.kurokumasoft.com/2022/08/09/akuro-ou-04-obstacles/)さんの[【Unity】動く障害物の作り方（ゲームの作り方チュートリアル「悪路王」その4）](https://gamedev.kurokumasoft.com/2022/08/09/akuro-ou-04-obstacles/)を参考に、動く障害物を生成するWallSpawnerを作成しようとした
同サイトの動く障害物のCスクリプトまでは作れている状況だった(必要であれば上記URLから確認してください)

# 起きた問題
ヒエラルキーがヒエっとなった。(..はい。)
![Screenshot 2025-02-25 at 1.40.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768702/b911dbc7-7231-4d89-9c89-e39f02f5ba8c.png)
なぜこんなにクローン生成されているのか？

# エラーコード
```:Consoleに表示された内容
NullReferenceException: Object reference not set to an instance of an object
WallSpawner+<SpawnTimer>d__24.MoveNext () (at Assets/Sprites/WallSpawner.cs:97)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at /Users/bokken/build/output/unity/unity/Runtime/Export/Scripting/Coroutines.cs:17)
UnityEngine.MonoBehaviour:StartCoroutine(String)
WallSpawner:Update() (at Assets/Sprites/WallSpawner.cs:73)
```
WallSpawner:Update()(Assets/Sprites/WallSpawner.csの73行目)を呼んだところ、さまざまな関数呼び出しを経て、NullReferenceExceptionが出ているよう？(どこかでポインタの中身が変になったのか？)

# コードと簡単な状況
## 環境
* Unityバージョン：2022.3.43f1
* 開発環境: Mac Sonoma 14.5
## 状況説明
何も手は加えていない状況、Unity表示のInspectorのwallPrefabには、その前に作成したwallのプレハブを指定した。
## コード
```cs:WallSpawner.cs
using System.Collections;
using UnityEngine;

//This class plays a role of generating walls in the game Akuroou.
public class WallSpawner : MonoBehaviour
{
    [SerializeField]
    GameObject wallPrefab = null;
    [SerializeField, Min(0.1f)]
    float defaultMinWaitTime = 1;
    [SerializeField, Min(0.1f)]
    float defaultMaxWaitTime = 1;
    [SerializeField]
    Vector2 defaultMinSize = Vector2.one;
    [SerializeField]
    Vector2 defaultMaxSize = Vector2.one;

    bool isSpawning = false;
    float minWaitTime;
    float maxWaitTime;
    Vector2 minSize;
    Vector2 maxSize; 
    Coroutine timer; 

    public float MinWaitTime{
        set
        {
            minWaitTime = Mathf.Max(value, 0.1f); //So as not to be under 0.1f
        }
        get
        {
            return minWaitTime;
        }
    }

    public float MaxWaitTime{
        set
        {
            maxWaitTime = Mathf.Max(value, 0.1f);
        }
        get
        {
            return maxWaitTime;
        }

    }
    
    public bool IsActive{
        get;
        set;
    } = true;
    // Start is called before the first frame update
    void Start()
    {
        InitSpawner();
    }

    // Update is called once per frame
    void Update()
    {
        if(!IsActive)
        {
            //if generating some object, stop do it
            if(timer != null)
            {
                StopCoroutine(timer);
                isSpawning = false;
            }
            return;
        }
        if(!isSpawning)
        {
            timer = StartCoroutine(nameof(SpawnTimer));
        }

    }

    public void InitSpawner()
    {
        minWaitTime = defaultMinWaitTime;
        maxWaitTime = defaultMaxWaitTime;
        minSize = defaultMinSize;
        maxSize = defaultMaxSize;

    }

    IEnumerator SpawnTimer()
    {
        isSpawning = true;

        GameObject wallObj = Instantiate(wallPrefab,
                            transform.position, Quaternion.identity
                            );
        Wall wall = wallObj.GetComponent<Wall>();
        float sizeX = Random.Range(minSize.x, maxSize.x);
        float sizeY = Random.Range(minSize.y, maxSize.y);
        wall.SetWall(new Vector2(sizeX, sizeY));
        float waitTime = Random.Range(minWaitTime, maxWaitTime);
        yield return new WaitForSeconds(waitTime);

        isSpawning = false;
    }

}
```
## Sceneの状況
![Screenshot 2025-02-25 at 2.10.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768702/5bdad585-c5a6-4dfc-9937-9053adec24ca.png)


# やったことを思い出すためにいじってみる

おそらく、指示にないことをやってしまったことが原因なのだろう。

ヒエラルキーから、大量に生成されてしまったものたちをInspectorから見てみる。


wall(Clone)(Clone)...(Clone)のコンポーネントを選ぶ。

コンポーネントを
見てみると、
* Transform
* Sprite Renderer
* Additional Settings
* Box Collider 2D
* Wall Spawner(Script)

がある状態。

そして、Wall Spawner(Script)のWall Prefabには、wall(Clone)(Clone)...(Clone)が指定されている。さらに、この指定されているPrefabは、クリックして黄色くなるものを見る感じ、自分自身が指定されているようだ。
...こんなことしたか？
# 仮説立て

望ましくは、ヒエラルキーには
* Main Camera
* Global Light 2D
* Player
* floor
* wall
* wall spawner

が表示されるべきであると推測される。

wallとwall spawnerの2つが最低限あるべきなのが、
* Main Camera
* Global Light 2D
* Player
* floor
* wall

というように、wallをwall spawnerとして用いたことが原因なのではないか？

# 対策
wallとwall spawnerの二つを、spriteの中のfloor画像を使って作成してみる。

ここで気づいたが、どうやら一つだけしか見えていなかったようで、大量に一つのオブジェクト上に重なっていた。
![Screenshot 2025-02-25 at 2.12.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768702/c3b69bc3-8e75-4092-a08f-881ac1c40641.png)

とりあえず仕方がないので、ヒエラルキーでcommand + Shiftを駆使して重なっているクローンたちを削除。ごめんよクローンたち。
最終的に整理を行い、ヒエラルキーはこのようになった。(追記：おそらくゲームのテストをするために再生ボタンを押している状態で作業をしている)
![Screenshot 2025-02-25 at 2.17.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768702/fe8ea96c-8780-4216-9bd5-15920172fbdb.png)
ヒエラルキー上からオブジェクト？をクリックし、Inspector操作した。wallにはWall.csをコンポーネントとして追加、Wall spawnerにはWallSpawner.csをコンポーネントとして追加した。(今見たけれど、大文字小文字がごっちゃごちゃになってますね、失礼しました)
すると、SerializeFieldとして設定したため、Wall spawnerにはwallPrefabが指定できる。ここには、wallを設定する。(問題が発生した理由は、おそらくここで自分自身を指定したことで、オリジナルがクローンを生成、クローンがクローンのクローンを生成、クローンのクローンがクローンのクローンの...と続いたのだろう)
![Screenshot 2025-02-25 at 2.21.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768702/b2e18681-20f5-4103-81d7-9db72a4fef80.png)


# 結果
クローンが大量に生成される状態は解決した。(MissingReferenceExceptionが発生したが、これについてはこの記事では触れないことにする)

# まとめ
クローンが大量生成された原因は、wallSpawnerの属性を持つ自分自身を生成するように誤って設定していたためであった。そのため、wallSpawnerとwallとで役割を切り分けたことで、問題は解決した。[記事](https://gamedev.kurokumasoft.com/2022/08/09/akuro-ou-04-obstacles/)にあった、
> 書けたらこれを適当な空のゲームオブジェクトにアタッチし、「Wall Prefab」欄に先ほど作った障害物のプレハブを登録しておきましょう。

というところで、「適当な空のゲームオブジェクトにアタッチし、」を誤って解釈したのが要因であった。

# メモ
エラーチェックや、コードを書いたときに意図していなかったことが原因でエラーが出てしまっている。単純に、自分自身の構造把握が甘かったのと、読解力が足らなかったことも一つの要因となった。また、編集した内容を保存せず、再生してしまい、変更内容が全て破棄されてしまい、今が再生中なのか、それとも再生していないのか、その把握もないまま作業を行なったことで、若干問題の特定時間が遅くなった。