【Unity(C#)】ボタン1つで同一オブジェクトを大量に等間隔で配置する

##隙間なく配置
私はこれまでUnityで隙間なく配置していく際には`Shift + V`で頂点を合わせてました。
しかし、何度も繰り返してたのでなんとか自動化できないかと思い試みました。

結果的にはこのような形です。
![chain.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/bcfe3fec-e6a8-32f2-26cb-07191f3f939c.gif)

XYZそれぞれの方向に連ねて配置ができます。


##一定の間隔ごとに配置
ぴったりくっつける必要はないが、何個も配置する必要のあるオブジェクトもあるかと思います。
そんなときのために等間隔で配置できる形も用意しました。
![chain_2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/855f609e-531b-a54f-12b3-036dae5f9642.gif)



##Bounds
`Bounds`を利用することで、`Renderer`及び`Collider`の3次元空間での範囲を取得することができます。
平たく言うとオブジェクトの大きさがわかるってことです。

先ほどの例で示した、
隙間なく配置する場合においては`Renderer`を
一定の間隔ごとに配置する場合においては`Collider`を利用してオブジェクトの大きさを取得しています

特に後者の`Collider`を利用した場合なんかはおもしろくて、
椅子の`Collider`は画像のようになっています。
![chair.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/762a15c6-2399-344f-28f2-87816b54dbf7.png)
コライダーを利用しているという意味が伝わったかと思います。


それぞれのコードを見ていきます。

##Renderer

`Renderer`のBoundsは下記のように取得します。

```C#
    Renderer objRenderer = chainObj.GetComponent<Renderer>();
    Bounds objBounds = objRenderer.bounds;
```

取得したBoundsを使う際は、**objBounds.size.x**のようにXYZ方向の大きさを個別に利用できます。
`size`は**直径が得られる**と考えてしまっていいと思います。

厳密な考え方は違いますのでしっかりと叩き込みたい方は[公式リファレンス](https://docs.unity3d.com/ja/2018.2/ScriptReference/Bounds.html)をご覧ください。


##Collider

`Collider`のBoundsは下記のように取得します。
取得の仕方も使い方もRendererと全く一緒です。

```C#
    Collider objCollider = chainObj.GetComponent<Collider>();
    Bounds objBounds = objCollider.bounds;
```


##Mesh
実は`Mesh`から取得する方法もあるのですが、
World座標でBoundsを返すので注意が必要です。

*公式リファレンスより引用*
>This is the axis-aligned bounding box of the mesh in its local space (that is, not affected by the transform). Note that the Renderer.bounds property is similar but returns the bounds in world space.



##コード

実際にコードに落とし込んだものがこちらです。

```C#
#if UNITY_EDITOR
using UnityEditor;
#endif  
using UnityEngine;
using System.Text.RegularExpressions;

public class CreateChainObj : MonoBehaviour
{
    [SerializeField]
    GameObject chainObj;

    [SerializeField]
    int chainNum = 0;

    [SerializeField]
    bool useX, useY, useZ;

    //指定された方向に連ねて配置する(Renderer)
    void DoneChain_UseMesh()
    {
        Renderer objRenderer = chainObj.GetComponent<Renderer>();
        Bounds objBounds = objRenderer.bounds;
        Vector3 objPos = chainObj.transform.position;

        for (int i = 0; i < chainNum; i++)
        {
            if (useX) objPos.x += objBounds.size.x;

            if (useY) objPos.y += objBounds.size.y;

            if (useZ) objPos.z += objBounds.size.z;

            GameObject tmpObj = Instantiate(chainObj, objPos, chainObj.transform.rotation);
            tmpObj.transform.parent = chainObj.transform.parent;
            string num_InObjName_String = Regex.Replace(chainObj.name, @"[^0-9]", "");

            if (num_InObjName_String != "")
            {
                int num_InObjName_Integer = int.Parse(num_InObjName_String);
                tmpObj.name = Regex.Replace(chainObj.name, @"[0-9]", "") + (i + 1 + num_InObjName_Integer);
            }
            else
            {
                tmpObj.name = chainObj.name + (i + 1);
            }
        }
    }

    //指定された方向に連ねて配置する(Collider)
    void DoneChain_UseBoxCollider()
    {
        Collider objCollider = chainObj.GetComponent<Collider>();
        Bounds objBounds = objCollider.bounds;
        Vector3 objPos = chainObj.transform.position;

        for (int i = 0; i < chainNum; i++)
        {
            if (useX) objPos.x += objBounds.size.x;

            if (useY) objPos.y += objBounds.size.y;

            if (useZ) objPos.z += objBounds.size.z;

            GameObject tmpObj = Instantiate(chainObj, objPos, chainObj.transform.rotation);
            tmpObj.transform.parent = chainObj.transform.parent;
            string num_InObjName_String = Regex.Replace(chainObj.name, @"[^0-9]", "");

            if (num_InObjName_String != "")
            {
                int num_InObjName_Integer = int.Parse(num_InObjName_String);
                tmpObj.name = Regex.Replace(chainObj.name, @"[0-9]", "") + (i + 1 + num_InObjName_Integer);
            }
            else
            {
                tmpObj.name = chainObj.name + (i + 1);
            }
        }
    }

#if UNITY_EDITOR
    [CustomEditor(typeof(CreateChainObj))]
    public class ExampleInspector : Editor
    {
        CreateChainObj rootClass;
        string explanation = 
            @"
                ・オブジェクトの名前の途中に数字を含めないでください。
                ・連ねる方向の指定にはワールド座標を利用します。
                ・Hierarchy上のオブジェクトをアタッチしてください。
                ・連結位置が気に入らない場合はColliderで調節することを推奨します。
                ・連ねたいオブジェクトには親オブジェクトを持たせてください。
                ";

        private void OnEnable()
        {
            rootClass = target as CreateChainObj;
        }

        public override void OnInspectorGUI()
        {
            

            EditorGUILayout.Space();

            EditorGUILayout.BeginVertical(GUI.skin.box);
            {
                EditorGUILayout.HelpBox(explanation, MessageType.Info);
            }

            EditorGUILayout.EndVertical();

            base.OnInspectorGUI();

            if (GUILayout.Button("連ねて配置(Rendererから位置を計測)"))
            {
                // 押下時に実行したい処理
                rootClass.DoneChain_UseMesh();
            }

            if (GUILayout.Button("連ねて配置(BoxColliderから位置を計測)"))
            {
                // 押下時に実行したい処理
                rootClass.DoneChain_UseBoxCollider();
            }

            serializedObject.Update();

            serializedObject.ApplyModifiedProperties();
        }
    }
#endif  
}

}

```

##Regex.Replace
正直、作ってから別にいらないなこれってなったんですが、
せっかく覚えたので書いときます。

```C#
    string num_InObjName_String = Regex.Replace(chainObj.name, @"[^0-9]", "");
            
    if (num_InObjName_String != "")
    {
        int num_InObjName_Integer = int.Parse(num_InObjName_String);
        tmpObj.name = Regex.Replace(chainObj.name, @"[0-9]", "") + (i + 1 + num_InObjName_Integer);
    }
    else
    {
        tmpObj.name = chainObj.name + (i + 1);
    }
```

なにをやっているかというと、生成されたオブジェクトに連番をつけてリネームしています。
**Regex.Replace(指定した文字列, 置換したい文字, 置換後の文字);**という使い方です。

"指定した文字列内の置換機能"を利用して、一度0～9以外の文字を空文字にして`num_InObjName_String`につっこんでます。
`string num_InObjName_String = Regex.Replace(chainObj.name, @"[^0-9]", "");`

あとは、空文字かどうか判定して連番を追加するだけです。

もっと他に用途がありそうな機能なので見つかったら、また まとめようと思います。

---
####HelpBox
完全なる自己満足ですが、[HelpBox](https://qiita.com/OKsaiyowa/items/07e69f15e28387adbcf8)もつけてます。
![ChainObj.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/345701d6-4c7e-984f-40fc-a8732466ef8c.png)


---
####小ネタ
`string hoge = @"文字列";`で改行できるらしい
たぶんみんな知っている...

---
####リンク

・利用したアセット：[昭和の椅子](https://assetstore.unity.com/packages/3d/props/furniture/pbr-japanese-school-desks-chairs-116942?aid=1011lGbg&utm_source=aff)