見出し画像

Unityエディタ上で動作する簡単なスクリプトを書いてみる

おはようございます、こんにちわ、こんばんわ、かせーです。
この記事はCluster Creator Advent Calendar 2022の13日目です。
昨日はでんこさんの「2022年の誕生日にclusterで1年分泣きました」でした。
誕生日イベントの振り返りをきっかけとした、
思いのこもった素敵な記事となっていましたね。
思い出は、大切だしつながっていってほしいです。。。。
また、でんこさんの今後の盛り上げに期待です!

・・・で、私はと言うと、、、
うって変わって、若干テクニカルな方へ、、、(それしかかけんのじゃ)
clusterというよりUnityでの作業効率を若干上げるかもしれない(?)エディタ上で動作させるスクリプトについて書いていきたいと思います。
※clusterはワールドアップロード後は決められたスクリプトしか実際は動作しませんが、エディタに関するスクリプトはエディタのみで動作するため、エディタ上で動作するオートメーション的なスクリプトを作成しても問題なくアップロードされます。(スクリプト自体は無視されるだけ)

同じような配置やグループに一括登録などは手で行うと大変ですがスクリプトを使うと簡単に出来るため作業の効率化が出来たりします。
そんなTips集になればと思います。
(同じ処理たくさんするとめんどくさいから作ったのがアドカレネタにもなって一石二鳥とかまったくオモッテナイヨ)
※clusterのランキングボード連動スマホ機能をにつくったときのをつかいまわし

まずはじめに

大前提として、ClusterCreatorKitSample-masterにある、
「MinimalSample」をベースに進めていきますので、
ダウンロードをして環境を整えておく必要があります。
準備が出来たら、「MinimalSample」を開いた状態にしてください。
ClusterWorldToolsも入れておくと良いと思います。
(今後ワールド制作においてレイヤー自動設定しておく事をおすすめします。)
地面となる部分がそのままだと小さいため、今回は、XとZのScaleを20に設定して広げます。
(「Field」というGameObjectを作成し、「StaticObjects」と「Colliders」を子に入れて、「Field」の、XとZのScaleを20に設定しました。)

「Field」を追加しの、XとZのScaleを20に設定、「StaticObjects」と「Colliders」を子にした状態

コードの編集はVisualStudioを使用しますので、ライセンスをもっていない方は、「Visual StudioCommunity」をインストールしてください。
私はバージョン2019ですが2022でも動くはず?

指定個数のCube(Prefab)を円上に配置するスクリプト

では準備をして実際コードを書いていきたいと思います。
こんな配置が出来るものを目指します。

スクリプトで200のCubeが円形に並んだ様子

1.
図のようにHierarchy上にGameObject(名前:SampleObjects)とその下にCube(名前:SampleCube)を挿入します。

GameObject(名前:SampleObjects)とその下にCube(名前:SampleCube)を挿入

2.
Projectにある「Asset」直下に「Sample」フォルダを作成、
さらに「Prefab」、「Scripts」フォルダを作成します。

Project内「Sample」フォルダを作成、「Prefab」、「Scripts」フォルダを作成

3.
フォルダを作成したら
"1."で作成したSampleCubeを選択して「Prefab」フォルダにドラッグして、
Prefab化します。

SampleCubeを選択して「Prefab」フォルダにドラッグして、Prefab化

Prefab化したら、Hierarchy上にあるSampleCubeは削除してください。

Hierarchy上にあるSampleCubeは削除する

4.
次に、Projectに先ほど作成した「Sample」フォルダ下の「Scripts」を選択し、右の部分で右クリックし「Create-C#Script」を選び、C#Scriptのベースを作成します。
作成したスクリプトの名前は、「AutoCircleArrangementObjectsScripts」とします。

AutoCircleArrangementObjectsScripts

5.
「AutoCircleArrangementObjectsScripts」を作成したら、ダブルクリックします。
そうするとVisualStudioのIDEが立ち上がり、コードを書く準備が整います。

AutoCircleArrangementObjectsScriptsを開いたところ

6.
では実際、コードを書いていきます。
下記コード全体です。
書き終えたら保存してください。
※細かな説明は、後ほどします。

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

public class AutoCircleArrangementObjectsScripts : MonoBehaviour
{
    // Inspectorに表示されるプロパティ
    [Header("追加するベースのPrefab")]
    [SerializeField] GameObject AddObjectPrefab = null;
    [Header("追加する数")]
    [SerializeField] int NumbeOfAddObjects = 2;
    [Header("半径")]
    [SerializeField] float Radius = 1;

    // 未使用
    // Start is called before the first frame update
    void Start()
    {
    }

    // 未使用
    // Update is called once per frame
    void Update()
    {
    }

#if UNITY_EDITOR
    [ContextMenu("円形配置を実行(実行するとこのScriptがついているGameObject配下の子オブジェクトは削除されます)")]
    public void ExecCircleArrangementObjects()
    {
        if (AddObjectPrefab != null & NumbeOfAddObjects > 0)
        {
            // このScriptが追加されているGameObjectをのTransform Componentを取得
            Transform tran =  this.gameObject.GetComponent<Transform>();
            if (tran != null)
            {
                int index;

                // このゲームオブジェクトの子としてついているオブジェクトをすべて削除
                for (index = tran.childCount - 1; index >= 0; index--)
                {
                    GameObject.DestroyImmediate(tran.GetChild(index).gameObject);
                }

                // 追加するオブジェクト名の下処理
                int itemms_count_keta = NumbeOfAddObjects.ToString().Length;

                string obj_base_name = AddObjectPrefab.name;

                //オブジェクト間の角度差
                float angle_diff = 360f / (float)NumbeOfAddObjects;
                Vector3 child_postion;

                // 実際Objectを追加
                for (index = 0; index < NumbeOfAddObjects; index++)
                {
                    // Prefabから追加するオブジェクトを生成
                    GameObject add_object = PrefabUtility.InstantiatePrefab(AddObjectPrefab) as GameObject;

                    // Object名を設定
                    add_object.name = obj_base_name + "_" + (index + 1).ToString().PadLeft(itemms_count_keta, '0');

                    // 追加したObjectの位置を設定
                    var target_transform = add_object.GetComponent<Transform>();
                    child_postion = target_transform.position; // ポジションを設定するために一時的変数へ座標値をコピー
                    float angle = (90.0f - angle_diff * (float)index) * Mathf.Deg2Rad; //indexに合わせて1つあたりの角度を計算(ラジアンに変換)※スタート位置をZに対して正面からにしたいので90度を基準にしている。
                    child_postion .x += Radius * Mathf.Cos(angle);
                    child_postion .z += Radius * Mathf.Sin(angle);
                    target_transform.position = child_postion ;

                    // オブジェクトの回転を一度リセットする(今回は回転していないPrefabからなので意味ないが、入れておくと安全)
                    target_transform.transform.rotation = Quaternion.identity;
                    if (Radius != 0.0)
                    {
                        target_transform.transform.Rotate(0, (angle_diff * index), 0); //中心に向けてy軸を回転させる。
                    }
                    target_transform.SetParent(tran); // 作成したObjectを親に追加
                }
            }
        }
    }
#endif
}

7.
コードを書き終えて、Unityの画面にもどります。
戻ったら、
Hierarchy上に作ほど作成した「SampleObjects」を選択します。
そして、Inspectorを参照して、「Add Component」ボタンを押し、
検索の所に「AutoCircleArrangementObjectsScripts」を打つと、先ほど作成したスクリプトが出てくるので、選択します。
そうすると、Inspectorに追加されます。

AutoCircleArrangementObjectsScripts追加

先ほど追加した「AutoCircleArrangementObjectsScripts」の設定を下記のようにします。

「AutoCircleArrangementObjectsScripts」の設定内容

・「追加するベースのPrefab」には"3."で作成したCubeのPrefabを指定します。
・「追加する数」は、「追加するベースのPrefab」で指定したPrefabをいくつコピーするかを指定します。
 例では、200個追加します。
・「半径」は、円形に配置するための半径を指定
 例では80を指定

指定したら、「AutoCircleArrangementObjectsScripts」の右上にある縦の「・・・」を押します。
そうするとメニューが現れ、一番下にある
「円形配置を実行(実行するとこのScriptがついているGameObject配下の子オブジェクトは削除されます)」
を押すと、

「円形配置を実行(実行するとこのScriptがついているGameObject配下の子オブジェクトは削除されます)」を選択

「SampleObjects」の子として、200個の「SampleCube」が追加されます。
(SampleCube_001~SampleCube_200)

「SampleObjects」の子として、200個の「SampleCube」が追加される

このように、指定したPrefubを規則正しく配置するなどは、スクリプトで一括で行う事が可能です。

この状態で、ワールドをアップしても特にエラーもなくアップされ表示もされます。

特にエラーもなくワールドをアップ(Cubeが半分埋まってるのはわざと)

こんな感じで、スクリプトで書くと一括で処理させたりする事が出来ます。

-以下スクリプトの説明-

では、細かな説明を。。。

冒頭部分

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

ここは、C#の必要な宣言などを読み込んでいます。
「using UnityEditor;」の前後を「#if UNITY_EDITOR~#endif」で囲っているのは、エディタでのみ動作させるという宣言です。
これをしないとアップロード時のコンパイルでエラーが出るので、
必ず設定してください。

「AutoCircleArrangementObjectsScripts」クラスの説明に入ります

    // Inspectorに表示されるプロパティ
    [Header("追加するベースのPrefab")]
    [SerializeField] GameObject AddObjectPrefab = null;
    [Header("追加する数")]
    [SerializeField] int NumbeOfAddObjects = 2;
    [Header("半径")]
    [SerializeField] float Radius = 1;

ここは、Unity上のInspectorに出てくる設定項目を指定している場所になります。(変数宣言)
=の後ろの値や文字は、初期値を表します。
「AddObjectPrefab」がPrefabを指定する変数
「NumbeOfAddObjects 」が、「AddObjectPrefab」で指定したPrefabをいくつコピーするかを指定する変数
「Radius」が半径の値を指定する変数を表します。

    // 未使用
    // Start is called before the first frame update
    void Start()
    {
    }

    // 未使用
    // Update is called once per frame
    void Update()
    {
    }

ここはデフォルトで追加されるメソッドです。なので今回は未使用のため説明は割愛します。

#if UNITY_EDITOR
    [ContextMenu("円形配置を実行(実行するとこのScriptがついているGameObject配下の子オブジェクトは削除されます)")]
    public void ExecCircleArrangementObjects()
    {
        if (AddObjectPrefab != null & NumbeOfAddObjects > 0)
        {
            // このScriptが追加されているGameObjectをのTransform Componentを取得
            Transform tran =  this.gameObject.GetComponent<Transform>();
            if (tran != null)
            {
                int index;

                // このゲームオブジェクトの子としてついているオブジェクトをすべて削除
                for (index = tran.childCount - 1; index >= 0; index--)
                {
                    GameObject.DestroyImmediate(tran.GetChild(index).gameObject);
                }

                // 追加するオブジェクト名の下処理
                int itemms_count_keta = NumbeOfAddObjects.ToString().Length;

                string obj_base_name = AddObjectPrefab.name;

                //オブジェクト間の角度差
                float angle_diff = 360f / (float)NumbeOfAddObjects;
                Vector3 child_postion;

                // 実際Objectを追加
                for (index = 0; index < NumbeOfAddObjects; index++)
                {
                    // Prefabから追加するオブジェクトを生成
                    GameObject add_object = PrefabUtility.InstantiatePrefab(AddObjectPrefab) as GameObject;

                    // Object名を設定
                    add_object.name = obj_base_name + "_" + (index + 1).ToString().PadLeft(itemms_count_keta, '0');

                    // 追加したObjectの位置を設定
                    var target_transform = add_object.GetComponent<Transform>();
                    child_postion = target_transform.position; // ポジションを設定するために一時的変数へ座標値をコピー
                    float angle = (90.0f - angle_diff * (float)index) * Mathf.Deg2Rad; //indexに合わせて1つあたりの角度を計算(ラジアンに変換)※スタート位置をZに対して正面からにしたいので90度を基準にしている。
                    child_postion .x += Radius * Mathf.Cos(angle);
                    child_postion .z += Radius * Mathf.Sin(angle);
                    target_transform.position = child_postion;

                    // オブジェクトの回転を一度リセットする(今回は回転していないPrefabからなので意味ないが、入れておくと安全)
                    target_transform.transform.rotation = Quaternion.identity;
                    if (Radius != 0.0)
                    {
                        target_transform.transform.Rotate(0, (angle_diff * index), 0); //中心に向けてy軸を回転させる。
                    }
                    target_transform.SetParent(tran); // 作成したObjectを親に追加
                }
            }
        }
    }
#endif

ここが実際の処理部分です。

「[ContextMenu("円形配置を実行(実行するとこのScriptがついているGameObject配下の子オブジェクトは削除されます)")]」

は、
Inspectorにある「AutoCircleArrangementObjectsScripts」右上にある縦の「・・・」を押した時にでるメニューの部分に追加される項目です。
この項目を押すと、「ExecCircleArrangementObjects」メソッドが呼ばれるようになります。

「Transform tran =  this.gameObject.GetComponent<Transform>();」

これで、「AutoCircleArrangementObjectsScripts」ScriptがついているGameObject(今回は「SampleObjects」)の情報を取得しています。
Unityは、この情報などは「Transform 」に含まれているため、
「GetComponent」で、「SampleObjects」に含まれている「Transfrom」情報を取得しています。

                // このゲームオブジェクトの子としてついているオブジェクトをすべて削除
                for (index = tran.childCount - 1; index >= 0; index--)
                {
                    GameObject.DestroyImmediate(tran.GetChild(index).gameObject);
                }

この処理は、「SampleObjects」の子のデータがある場合、一度破棄するっ処理をしています。(実行するたびにオブジェクトが追加されないようにするため)

                // 追加するオブジェクト名の下処理
                int itemms_count_keta = NumbeOfAddObjects.ToString().Length;

                string obj_base_name = AddObjectPrefab.name;

                //オブジェクト間の角度差
                float angle_diff = 360f / (float)NumbeOfAddObjects;
                Vector3 child_postion;

                // 実際Objectを追加
                for (index = 0; index < NumbeOfAddObjects; index++)
                {
                    // Prefabから追加するオブジェクトを生成
                    GameObject add_object = PrefabUtility.InstantiatePrefab(AddObjectPrefab) as GameObject;

                    // Object名を設定
                    add_object.name = obj_base_name + "_" + (index + 1).ToString().PadLeft(itemms_count_keta, '0');

                    // 追加したObjectの位置を設定
                    var target_transform = add_object.GetComponent<Transform>();
                    child_postion = target_transform.position; // ポジションを設定するために一時的変数へ座標値をコピー
                    float angle = (90.0f - angle_diff * (float)index) * Mathf.Deg2Rad; //indexに合わせて1つあたりの角度を計算(ラジアンに変換)※スタート位置をZに対して正面からにしたいので90度を基準にしている。
                    child_postion .x += Radius * Mathf.Cos(angle);
                    child_postion .z += Radius * Mathf.Sin(angle);
                    target_transform.position = child_postion;

                    // オブジェクトの回転を一度リセットする(今回は回転していないPrefabからなので意味ないが、入れておくと安全)
                    target_transform.transform.rotation = Quaternion.identity;
                    if (Radius != 0.0)
                    {
                        target_transform.transform.Rotate(0, (angle_diff * index), 0); //中心に向けてy軸を回転させる。
                    }
                    target_transform.SetParent(tran); // 作成したObjectを親に追加
                }

ここが、実際「SampleCube」を指定個数円形に設置している処理になります。
SampleCubeの名前の設定や、配置する位置や中心に向けて向くように回転させるなどの処理をしています。(詳しく説明すると頭から知恵熱が出てしまうので、細かな説明は割愛(めんどい)

以上がこの処理の説明になります。
今回の例は、それほど大きな意味はないですが、エディタ上で動作するScriptを書く事で作業効率が上がることもあるので、同じような処理を行う必要がある場合は、挑戦してみると良いかもしれません。
参考になれば。。。
※実は、もう一つエディタ拡張スクリプトネタがあるので、
 後日追加で書こうかと思います。
 (まだ少し続くんじゃ。。。)

ではでは、
よき、メタバースライフを!!

明日(12月14日)はuMe店長_mXizmさんの
「難しく考えないメタバースイベント」です!
イベント開こうと思っていてもなかなか踏み出せない方には必見かもしれません!!
私も、楽しみです~。


(追加してみた)