#環境
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が値の変更を検知していない(本題)
ここからが本題ですがまず問題のコードを見てください。
#問題のコード
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されているはず。
適当なオブジェクトにアタッチして
値を変更。Ctrl+Sで保存
Unityを再起動。値は変更前に戻ってしまっています。
#原因
このようなことが起きる理由はEditorGUILayout.〇〇Fieldで変更したときにsceneに変更された情報が反映されておらず、保存できてないからです。
反映されたかどうかはHierarchy上のシーン名の隣に____がつくことでわかります。
例えば、値を9999にしたあとTransformのPositionのxをちょっとだけ動かすことでがつきました。
保存してUnityを再起動すると値も保存されてたことがわかります。
#なぜこういうことがおきるのか
EditorからもとのコンポーネントにアクセスするにはSerializedObjectを通す必要があり、EditorGUILayout.〇〇Fieldで取得した値を再代入する方法それを介さないはSceneに変更が通知されないということでしょう。
ただ、SerializedPropertyを使用したアクセスの方法は、クラスや配列を扱うのが大変なのでできればEditorGUILayout.〇〇Fieldで扱いたいわけです。
#解決法
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しましょう。