知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityEditor拡張】SerializedObject #69

前回の成果

カメラを用いたエフェクトを制作した。

soramamenatan.hatenablog.com


今回やること

SerializedObjectについて学びます。


事前準備

Scene上に今回のScriptをアタッチする用の空のGameObjectを配置してください。

f:id:soramamenatan:20200906152242p:plain


SerializedObjectとは

シリアライズ化された値をUnityが扱いやすいように加工したものです。
主にEditor拡張で使用されます。
シリアライズ化は、

[SerializeField]
private int _hoge;

がよく使われる例となります。
具体的なものを挙げると、

  • publicであること
  • [SerializeField]属性があること

上記2つのどちらかがあればシリアライズ化されていると言えます。


メリット

メリットは主に3つ挙げられます。

データを扱うことが出来る

一番わかりやすい例だと思います。
inspector上から値を変更することができます。

f:id:soramamenatan:20200906153309p:plain

Undoのハンドリング

SerializedObjectで値を変更する際には、Undo処理は自動的に登録されます。
しかし、UnityEngine.Objectのインスタンスを直接変更した場合にはUndo処理を自前で実装する手間がかかってしまいます。

Selectionのハンドリング

複数のオブジェクトを選択した際に、シリアライズされたプロパティの同時編集を可能にする仕組みがあります。


ソースコード

SerializedObjectTest
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;

namespace SerializedObject {
    [CustomEditor(typeof(HogeParam))]
    public class SerializedObjectTest : Editor {
        public override void OnInspectorGUI() {
            serializedObject.Update();
            SerializedProperty hpProperty = serializedObject.FindProperty("_hp");
            EditorGUILayout.PropertyField(hpProperty);
            serializedObject.ApplyModifiedProperties();
        }
    }
}
#endif
HogeParam
using UnityEngine;

namespace SerializedObject {
    public class HogeParam : MonoBehaviour {
        [SerializeField]
        private int _hp;
        void Awake() {
            Debug.Log("HogeParam _hp : " + _hp);
        }
    }
}
FugaParam
using UnityEngine;

namespace SerializedObject {
    public class FugaParam : MonoBehaviour {
        [SerializeField]
        private int _hp;
        void Awake() {
            Debug.Log("FugaParam _hp : " + _hp);
        }
    }
}

HogeParamとFugaParamはどちらも同じような処理となります。
HogeParamのみ、カスタムエディタにしています。


実装

では、HogeParamとFugaParamを空のObjectにアタッチしてみます。

アタッチした見た目

どちらも同じような感じですが、HogeParamはScriptが出ていません。
f:id:soramamenatan:20200906154640p:plain

また、どちらも値を変更することができます。
f:id:soramamenatan:20200906154803p:plain そして、値が変わっていることも確認できます。
f:id:soramamenatan:20200906154646p:plain


解説

SerializedObjectTestに関して解説していこうと思います。
そこまで難しいものはないので、コメントで解説します。

// 任意のエディタをする属性
// UnityEditor.Editorを継承する必要があるので注意
[CustomEditor(typeof(HogeParam))]
public class SerializedObjectTest : Editor {
    /// <summary>
    /// inspectorのuGUIを制御する
    /// </summary>
    public override void OnInspectorGUI() {
        // 内部のキャッシュから最新のデータを取得する
        serializedObject.Update();
        // プロパティを取得する
        SerializedProperty hpProperty = serializedObject.FindProperty("_hp");
        // 取得したプロパティをinspectorから編集できるようにする
        EditorGUILayout.PropertyField(hpProperty);
        // 内部キャッシュに変更した値を適応させる
        serializedObject.ApplyModifiedProperties();
    }
}


応用例

ソースコード

using System.Collections.Generic;
using UnityEngine;
using System;

namespace SerializedObject {
    public class ListParam : MonoBehaviour {
        public List<Status> _list = new List<Status>();
    }
    [Serializable]
    public class Status {
        public string _name;
        public int _hp;
        public int _mp;

        public Status(string name, int hp, int mp) {
            _name = name;
            _hp = hp;
            _mp = mp;
        }
    }
}

RPG等でありがちなステータスClassです。


inspector上

このListParamをinspectorで編集したい場合、
普通のpublicや[SerializeField]でも編集は可能ですが少し味気ない気がします。

f:id:soramamenatan:20200912193211p:plain

ですので、以下のソースコードのようにエディタ拡張してみます。

エディタ拡張のソースコード
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;

namespace SerializedObject {

    [CustomEditor(typeof(ListParam))]
    public class SerializedObjectListTest : Editor {
        private bool _isInitialized;
        // Listの折りたたみ用
        private bool _isFold;
        // Listの要素の折りたたみ用
        private bool[] _isFoldings;

        /// <summary>
        /// inspectorのuGUIを制御する
        /// </summary>
        public override void OnInspectorGUI() {
            ListParam param = target as ListParam;
            List<Status> statusList = param._list;

            if (_isInitialized == false) {
                // 初期化
                InitializeList(statusList.Count);
            }
            // Listの折りたたみ表示
            if (_isFold = EditorGUILayout.Foldout(_isFold, "StatusList")) {
                EditorGUI.indentLevel++;
                for (int i = 0; i < statusList.Count; i++) {
                    EditorGUI.indentLevel++;
                    // Listの要素の折りたたみ表示
                    if (_isFoldings[i] = EditorGUILayout.Foldout(_isFoldings[i], "Status" + i)) {
                        statusList[i]._name = EditorGUILayout.TextField("Name", statusList[i]._name);
                        statusList[i]._hp = EditorGUILayout.IntField("HP", statusList[i]._hp);
                        statusList[i]._mp = EditorGUILayout.IntField("MP", statusList[i]._mp);
                        EditorGUILayout.BeginHorizontal();
                        GUILayout.FlexibleSpace();
                        // ステータスの削除
                        if (GUILayout.Button("Delete")) {
                            statusList.RemoveAt(i);
                            InitializeList(i, statusList.Count);
                        }
                        EditorGUILayout.EndHorizontal();
                    }
                    EditorGUI.indentLevel--;
                }
                // 新しいステータスの追加
                if (GUILayout.Button("Add")) {
                    statusList.Add(new Status("New", 0, 0));
                    InitializeList(-1, statusList.Count);
                }
                EditorGUI.indentLevel--;
            }
        }

        /// <summary>
        /// リスト初期化
        /// </summary>
        /// <param name="count"></param>
        private void InitializeList(int count) {
            _isFoldings = new bool[count];
            _isInitialized = true;
        }

        /// <summary>
        /// 指定したindex以外をキャッシュして初期化
        /// indexが-1の場合のみ全てキャッシュして初期化
        /// </summary>
        /// <param name="index"></param>
        /// <param name="count"></param>
        private void InitializeList(int index, int count) {
            bool[] tmp = _isFoldings;
            _isFoldings = new bool[count];
            for (int i = 0, j = 0; i < count; i++) {
                if (index == j) {
                    j++;
                }
                if (tmp.Length - 1 < j) {
                    break;
                }
                _isFoldings[i] = tmp[j++];
            }
        }
    }
}

すると、以下画像のようにinspector上でのAddやDeleteがわかりやすくなりました。

結果

f:id:soramamenatan:20200912193208p:plain

今回は以上となります。
ここまでご視聴ありがとうございました。


参考サイト様

light11.hatenadiary.com

qiita.com

49.233.81.186

qiita.com