LoginSignup
6
2

More than 3 years have passed since last update.

UnityのEditor拡張から値を更新したあとUnity再起動すると値がもとに戻る問題

Posted at

環境

Unity 2018.3.12f1

はじめに

Inspector上で値を変更した変数がUnityを再起動したときにもとに戻ってしまう原因はおもに2つあります。

  • 変数がSerializeされていないもしくはSerializeできない型を使っている
  • Sceneが値の変更を検知していない(本題)

変数がSerializeされていないもしくはSerializeできない型を使っている

これに関してはUnityのドキュメントを参照すると詳しく書いてありますが、Serialize属性をつけるかシリアライズ可能な型を使うことで回避できます。
https://docs.unity3d.com/ja/current/Manual/script-Serialization.html

  • Serializable 属性をもつカスタムの非抽象クラスと非ジェネリッククラス
  • Serializable 属性をもつカスタム構造体
  • UnityEngine.Object から派生するオブジェクトへの参照
  • プリミティブなデータ型 (int、float、double、bool、string など)
  • Enum 型
  • 特定の Unity ビルトイン型: Vector2、Vector3、Vector4、Rect、Quaternion、Matrix4x4、Color、Color32、LayerMask、AnimationCurve、Gradient、RectOffset、GUIStyle

Sceneが値の変更を検知していない(本題)

ここからが本題ですがまず問題のコードを見てください。

問題のコード

Testclass.cs
using UnityEngine;
using UnityEditor;
public class Testclass : MonoBehaviour
{
    [HideInInspector]
    public int hoge = 0;
}

[CustomEditor(typeof(Testclass))]
public class TestclassInspector : Editor {
    public override void OnInspectorGUI() {
        var obj = target as Testclass;
        base.OnInspectorGUI();
        obj.hoge = EditorGUILayout.IntField(obj.hoge);
    }
}

Testclassはint型の変数hogeを持っています。customEditorから値を操作するためにpublicにした上で[HideInInspector]でInspector上で非表示にしていますが、プリミティブ型なのでserializeされているはず。

適当なオブジェクトにアタッチして
image.png
値を変更。Ctrl+Sで保存
image.png
Unityを再起動。値は変更前に戻ってしまっています。:sweat_drops:
image.png

原因

このようなことが起きる理由はEditorGUILayout.〇〇Fieldで変更したときにsceneに変更された情報が反映されておらず、保存できてないからです。
反映されたかどうかはHierarchy上のシーン名の隣に*がつくことでわかります。
例えば、値を9999にしたあとTransformのPositionのxをちょっとだけ動かすことで*がつきました。
image.png

保存してUnityを再起動すると値も保存されてたことがわかります。
image.png

なぜこういうことがおきるのか

EditorからもとのコンポーネントにアクセスするにはSerializedObjectを通す必要があり、EditorGUILayout.〇〇Fieldで取得した値を再代入する方法それを介さないはSceneに変更が通知されないということでしょう。
ただ、SerializedPropertyを使用したアクセスの方法は、クラスや配列を扱うのが大変なのでできればEditorGUILayout.〇〇Fieldで扱いたいわけです。

解決法

Testclass.cs
using UnityEngine;
using UnityEditor;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;
public class Testclass : MonoBehaviour
{
    [HideInInspector]
    public int hoge = 0;
}

[CustomEditor(typeof(Testclass))]
public class TestclassInspector : Editor {
    public override void OnInspectorGUI() {
        var obj = target as Testclass;
        base.OnInspectorGUI();

        EditorGUI.BeginChangeCheck();

        obj.hoge = EditorGUILayout.IntField(obj.hoge);

        if (EditorGUI.EndChangeCheck())
        {
            var scene = SceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(scene);
        }
    }
}

解説

まず必要な名前空間を呼び出し

using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;

値を変更する部分をEditorGUI.BeginChangeCheck();とEditorGUI.EndChangeCheck()ではさみます。
変更時にEndChangeCheck()がtrueを返すので、そのたびにEditorScenemanager.MarkSceneDiryを使ってシーンが汚れたことを手動で書き込みます。

        EditorGUI.BeginChangeCheck();

        obj.hoge = EditorGUILayout.IntField(obj.hoge);

        if (EditorGUI.EndChangeCheck())
        {
            var scene = SceneManager.GetActiveScene();
            EditorSceneManager.MarkSceneDirty(scene);
        }

結論

EditorGUILayout.〇〇Fieldを使う場合は必ずMarkSceneDirtyしましょう。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2