Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【Unity】エディタ拡張で設定した値が実行時に消える

Q&A

Closed

解決したいこと

エディタ拡張を使ってオフライン(非ゲーム実行時)でListに要素を設定し、それを永続化したいです。
手動で設定するには煩雑なためスクリプトで自動化したいというのがそもそもの目的です。

Unity2020.3.5f1 Windows環境です。

発生している問題・エラー

作成したスクリプトをエディタモードで実行すると、いったんは要素が入るのですが、ゲームを実行すると起動直後に要素が消えてListはEmptyになり、ゲーム停止後もEmptyのままとなります。
設定した要素を永続化するにはどうしたらよいでしょうか。

■作成したスクリプト

敵グループ(enUnit)の配置座標がが範囲(PopArea)矩形内に収まっていたらその敵グループのコンポネントであるEnemyArrayをPopAreaクラスのManualEnArraysというListに要素として追加するものです。

public class PlaceEnemyAreas : EditorWindow
{
    [MenuItem("Tools/PopArea/AttachManualEnemy")]
    static void AttachManualEnemy()
    {
        GameObject midashi = GameObject.Find("-----PopArea-------");
        var count = midashi.transform.childCount;

        //enUnitがPopAreaの範囲矩形内に収まっていたらManualEnArraysに追加する
        EnUnit[] enUnits = FindObjectsOfType<EnUnit>();
        for (int i = 0; i < enUnits.Length; i++)
        {
            //Debug.LogWarning(enUnits[i].gameObject);
            isMatch = false;

            for (int j = 0; j < count; j++)
            {
                var child = midashi.transform.GetChild(j);
                var pArea = child.GetComponent<PopArea>();
                if (pArea)
                {
                    var centerPos = (Vector2)pArea.transform.position + pArea.AreaOffset;
                    //Rectの基点(position)は、中心でなく左下コーナー
                    centerPos.x -= pArea.AreaSize.x / 2;
                    centerPos.y -= pArea.AreaSize.y / 2;
                    Rect rect = new Rect( centerPos, pArea.AreaSize );                    

                    if (rect.Contains(enUnits[i].transform.position))
                    {
                        Debug.LogWarning("Inside : " + enUnits[i] +" @" + pArea);
                       pArea.AddToManualEnemy(enUnits[i].transform.GetComponentInChildren<EnemyArray>());
                        break;
                    }
                    else
                    {
                        Debug.Log("Outside : " + enUnits[i] + " @" + pArea);
                    }
                }
            }
        }
    }

}

◇上記エディタ拡張スクリプト実行前
ManualEnArraysリストは空
SS000.png

◇スクリプト実行後
敵グループのコンポネント(EnemyArray)が正しくリストに載ります
SS001.png

◇ゲーム実行直後
リストが空になってしまいます(ゲーム終了後も空のまま)
SS002.png

自分で試したこと

◇手動でリストに登録してみた

EnAry_Manual03を手動でリストにD&Dして実行してみました。
すると本件の問題は起きず、要素は永続化しました。
つまりスクリプトから実行した場合と手動で実行した場合で、インスペクタ上の外見はまったく同じであるにも関わらず、挙動が違うということでした。

◇意図せず初期化されていないか確認した

ManualEnArraysリストを初期化でClearしていないか確認しました。
参照は2か所のみで、いずれも初期化はしていませんでした。
下に掲載してある実際のスクリプトをご覧いただくと、そもそもnewしている時点で初期化しているのでは?というのもありますがnewしなくても結果は同じでした。
(ちなみにインスペクタ上で値を設定した場合、スクリプト内での初期化は無視されるという認識です)
上記手動の場合はリストが空にならないことからも、この線はなさそうに思えました。

◇シリアライズの問題を疑った

シリアライズについて詳しくないのですがいろいろ調べて、関係する要素はすべてシリアライズしたつもりです。
調査目的で一時的にOdinというプラグインを導入してチェックもしました。

public class PopArea : MonoBehaviour
{
    [SerializeField]
    List<EnemyArray> manualEnArrays = new List<EnemyArray>();
    public List<EnemyArray> ManualEnArrays
    {
        get { return manualEnArrays; }
        //private set { manualEnArrays = value; Debug.LogWarning("enArray set : " + value); }
    }

    private void OnEnable()
    {
        InstantiateManualEnemyArray();
    }

  //リスト内の要素を初期化する処理
    void InstantiateManualEnemyArray()
    {
        foreach (var enArray in ManualEnArrays)
        {
            AddToEnemyArrayList(enArray);
            enArray.MyPopArea = this;
            enArray.ManualPlacementInit();
        }
    }

    //エディタ拡張スクリプトから呼んでいる
    public void AddToManualEnemy(EnemyArray enemyArray)
    {
        ManualEnArrays.Add(enemyArray);
    }
}

調査目的で入れたOdinのシリアライズデバッガーです。
リストはバッキングフィールドを使い、それをシリアライズしています。
(Odinのシリアライズ機能は使っていません。
 正確にはOdinの機能を使ってプロパティの方のリストもシリアライズしてみましたが、結果は同じでしたので戻しました)

image.png
また、リストの要素であるEnemyArrayクラスも念のためシリアライズ可能にしています(必要?)

[System.Serializable]
public class EnemyArray : CellArrayManager
{…}

その他

実はほぼ同じ構造にも関わらず、本件の問題が起きずに正しく機能しているリストがあります。
上に貼ったスクショに「Near Areas」というリストがあるかと思います。
これはグリッド上に敷き詰められたPopArea同士の隣接関係をリストで保持しているものなのですが、同様にエディタ拡張スクリプトで要素を設定しています。
要素のクラスくらいしか違いがないのですが、なぜかこちらは永続化されており問題なく使えています。

public class PopArea : MonoBehaviour
{
    //近傍エリアへの参照
    //シリアライズ化しないとエディットモードでの設定が揮発してしまう
    [SerializeField]
    List<PopArea> nearAreas = new List<PopArea>();
    public List<PopArea> NearAreas
    {
        get { return nearAreas; }        
    }
}

なおこちらは数か月前に実装したもので、当時の自分のコメントからシリアライズの必要性があったことがわかります。
ManualEnArraysリストをnewしたり、setをコメントアウトしているのは、なるべくこのnearAreasと同じにするためでした。
Odinでのシリアライズチェックもまったく同じです。

以上、長文で申し訳ありません。
解決方法、確認すべき点等ございましたらご教授いただけると助かります。

1

1Answer

Your answer might help someone💌