【Unity(C#)】Lightｍap設定関連の一括変更Editor拡張

##BakeされるStaticなオブジェクト
![StaticBake.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/bf7c1553-28e0-5bc0-e8fc-b028bfb2c1c7.png)

画像の箇所にチェックを入れると、
GenerateLighting(Bake)した際に、光源からの情報をオブジェクトに焼き付けることができます。

Unity触りたての頃は、何のために使うのか？という疑問がありましたが、
今となっては使わないことはありません。

簡単に言うと**RealTimeで光を反映させると処理が重くなってしまう**からです。

なので、オブジェクトが大量にあったり、光源が大量にあったりするシーンにおいては、
StaticなオブジェクトにBakeして、あらかじめ光の情報を焼き付けておくというのは重要となってきます。

##一括変更したい項目

StaticなオブジェクトにBakeする際に、オブジェクト毎に設定が必要となる場合があります。
Staticにチェックを入れると`MeshRenderer`の項目にBake時の設定項目が増えます。
![StaticBake.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/07ff0455-2610-95ff-037f-6f32c36e6640.gif)

今回はこの増えた設定項目を変更するScriptを書きます。

具体的には、`ScaleInLightmap`、`LightmapParameters`、`StitchSeams`です。
他にも項目は増えますが、個人的によく変更する上記3つを一括変更することにします。

##コード

一旦全部載せて、後程細かくメモ残します。

```C#
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;

public class LightmapSettingsEdit : EditorWindow
{
    static EditorWindow window;
    float slider;
    Object source;
    bool isOn;
  
    [MenuItem("MyTools/LightmapSettingsEdit")]
    static void ShowWindow()
    {
        window = EditorWindow.GetWindow(typeof(LightmapSettingsEdit));
        window.position = new Rect(Screen.width /2, Screen.height / 2, 300, 400);
    }

    void OnGUI()
    {
        GUILayout.Space(25);

        //ここでScale In Lightmapの値を設定
        slider = EditorGUILayout.Slider("Scale In Lightmap", slider, 0, 20);

        GUILayout.Space(10);

        //＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
        //Scale In Lightmap一括変更ボタン
        //＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
        if (GUILayout.Button("Change all Scale In Lightmap"))
        {
            GameObject selectionObj = Selection.activeGameObject;

            if(selectionObj == null)
            {
                Debug.LogError("オブジェクトを選択してください");
                return;
            }

            ChangeScaleInLightmap(selectionObj);
        }

        GUILayout.Space(50);

        //ここでLightmapParametersを選択
        source = EditorGUILayout.ObjectField(source, typeof(LightmapParameters), true);

        GUILayout.Space(10);

        //＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
        //LightmapParameters一括変更ボタン
        //＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
        if (GUILayout.Button("Change all LightmapParameters"))
        {
            GameObject selectionObj = Selection.activeGameObject;

            if (source == null)
            {
                Debug.LogError("LightmapParametersを選択してください");
                return;
            }

            if (selectionObj == null)
            {
                Debug.LogError("オブジェクトを選択してください");
                return;
            }

            ChangeLightmapParameters(selectionObj);
        }

        GUILayout.Space(50);

        //ここでStitch Seamsを選択
        isOn =  EditorGUILayout.Toggle("Stitch Seams", isOn);

        GUILayout.Space(10);

        //＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
        //Stitch Seams一括変更ボタン
        //＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
        if (GUILayout.Button("Change all Stitch Seams"))
        {
            GameObject selectionObj = Selection.activeGameObject;

            if (selectionObj == null)
            {
                Debug.LogError("オブジェクトを選択してください");
                return;
            }

            ChangeStitchSeams(selectionObj);
        }

        GUILayout.Space(50);

        //＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
        //一括変更ボタン
        //＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝
        if (GUILayout.Button("All Apply"))
        {
            GameObject selectionObj = Selection.activeGameObject;

            if (source == null)
            {
                Debug.LogError("LightmapParametersを選択してください");
                return;
            }

            if (selectionObj == null)
            {
                Debug.LogError("オブジェクトを選択してください");
                return;
            }

            //全部いっぺんにするだけ
            ChangeScaleInLightmap(selectionObj);
            ChangeLightmapParameters(selectionObj);
            ChangeStitchSeams(selectionObj);
        }
    }

    void ChangeScaleInLightmap(GameObject obj)
    {
        foreach (Transform child in obj.GetComponentsInChildren<Transform>(true))
        {
            MeshRenderer mr = child.GetComponent<MeshRenderer>();
            if(mr != null)
            {
                SerializedObject so = new SerializedObject(mr);
                so.FindProperty("m_ScaleInLightmap").floatValue = slider;
                so.ApplyModifiedProperties();

                Debug.Log("ScaleInLightmapを一括変更完了！");
            }         
        }
    }

    void ChangeLightmapParameters(GameObject obj)
    {
        foreach (Transform child in obj.GetComponentsInChildren<Transform>(true))
        {
            MeshRenderer mr = child.GetComponent<MeshRenderer>();
            if(mr != null)
            {
                SerializedObject so = new SerializedObject(mr);
               
                SerializedProperty sp = so.FindProperty("m_LightmapParameters");
                if (sp != null)
                {
                    sp.objectReferenceValue = source;
                    so.ApplyModifiedProperties();
                    Debug.Log("LightmapParametersを一括変更完了！");
                }
            }         
        }
    }

    void ChangeStitchSeams(GameObject obj)
    {
        foreach (Transform child in obj.GetComponentsInChildren<Transform>(true))
        {
            MeshRenderer mr = child.GetComponent<MeshRenderer>();
            if (mr != null)
            {
                SerializedObject so = new SerializedObject(mr);
                so.FindProperty("m_StitchLightmapSeams").boolValue = isOn;
                so.ApplyModifiedProperties();

                Debug.Log("ScaleInLightmapを一括変更完了！");
            }
        }
    }
}
#endif
```

実際にできたEditorWindowがこれです。
Hierarchyで選択したオブジェクト含む、子階層を全て一括で変更します。

![LightmapSettings.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/5a126306-0b29-af85-ae60-3102fa447081.png)


##FindProperty

今回編集するプロパティがPublicでは無い為(?)、
`MeshRenderer.LightmapParameters`のようにしてプロパティにアクセスすることはできません。

下記コードのように一度SerializedObjectのコンストラクタに渡して変換してから
FindPropertyを利用します。

```C#
    MeshRenderer mr = child.GetComponent<MeshRenderer>();         
    SerializedObject so = new SerializedObject(mr);               
    SerializedProperty sp = so.FindProperty("m_LightmapParameters");
    sp.objectReferenceValue = 適用したいLightmapParameters;
    so.ApplyModifiedProperties();
```
ただ、この`m_LightmapParameters`の見つけ方がやっかいでした。
私が行った方法としては、**`Scene`をExplorerからテキストエディタ**で開きます。

![SceneTex.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/8a414f3f-82c2-109c-3fa7-ffcf1a8a0a9c.png)

MeshRendererの箇所に該当するプロパティを見つけました。
少しめんどうですね。他のやり方あれば教えてください。

###テキストエディタでSceneを開いた際の注意点
![moving.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/9762e9f1-556b-fdfd-6059-4a37df10ec8b.png)

テキストエディタでSceneを開いた状態でSceneを変更、Saveすると画像のようなエラーウィンドウが出ます。
何回かに一回、編集データが吹き飛んだので気を付ける必要がありそうです。


##FindPropertyするもう一つの理由

実はこのFindPropertyをSerializedObject経由で行う理由はもう１つありまして、
それは**Undoが可能になる**という点です。`Ctrl`+`Z`、`Ctrl`+`Y`で元に戻せますよってことです。

例えばEditor拡張で下記のように重力をOffにした場合、Undoできません。

```C#
     rb = child.GetComponent<Rigidbody>();
     rb.useGravity = false;
```

下記のように変更することでUndoが可能となります。

```C#
     rb = child.GetComponent<Rigidbody>();
     SerializedObject so_rb = new SerializedObject(rb);
     so_rb.FindProperty("m_UseGravity").boolValue = false;
     so_rb.ApplyModifiedProperties();
```

##EditorGUILayout.ObjectField
これはInspectorで[オブジェクトをアタッチできるアレ](https://docs.unity3d.com/ja/2017.4/ScriptReference/EditorGUILayout.ObjectField.html)です。

任意のObjectの種類を指定してあげれば
`public GameObject hogehoge`とした時と同様の表示が行えます。

```C#
Object source;
source = EditorGUILayout.ObjectField(source, typeof(LightmapParameters), true);
```
2番目の引数(typeofの箇所)で型を指定します。3番目の引数のbool値はScene上のオブジェクトを参照するかどうかの設定です。

##`GetComponent<T>(true)`
GetComponent時の引数にtrueを渡すと、
非アクティブなオブジェクトからも取得が可能になります。

##グループごとの設定も可能
選択したオブジェクトと、子のオブジェクトを一括で変更できるので、
Hierarchy上でグループ化したオブジェクト毎にLightmapParametersを設定するなどもできます。

GameObjectに設定されたタグに応じて設定できる。。。といった拡張なども便利かもしれません。

引き続き便利そうなのを思いついたら更新します。