1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Unity】カジュアルゲーム向けScriptableObjectでシーンオブジェクトデータの保存【C#】【エディタ拡張】

Last updated at Posted at 2019-11-28

【Unity】カジュアルゲーム向けScriptableObjectでシーンオブジェクトデータの保存【C#】【エディタ拡張】

はじめに

ステージ数が多くなりがちなUnityでのカジュアルゲーム開発。
シーン上のTag付きObjectのロードとセーブをエディタ上で行える実用的なスクリプトを作りました。

Scene内のObjectをPosition、Rotation、ObjectTypeを一括セーブ、ロードできるエディタは拡張性に富んでいて便利だと思います。ぜひ応用してもらいたいです。

荒い実装ではありますが、エクセルではなくScriptableObjectにセーブすることを学んだので共有。
エディタ拡張も用いてプランナーのステージ開発も助けます。
ちなみに本スクリプトは城を構成するPrefab(レンガなど)をセーブ、ロードするための変数などの命名をしています。

作成したEditorはUnityのEditorタブに表示されます。

スクリーンショット 2019-11-21 21.23.46.png

開発環境

OS: MacOS Mojave
開発環境: Unity 2018.4.11f1 personal
開発言語: C#

動作環境

あるオブジェクト以下(本スクリプトではCastleオブジェクト)のTag付きObjectのPrefab(Prefab名とTag名を同じにしてください)のPosition、Rotation、種類を保存、ロードできるようになります。実行しなくてもエディタ上で編集したまま保存できます。

ソースコードとその説明

このスクリプト(EditorWindow.cs)ではEditor上にセーブ機能とロード機能をもつエディタウインドウを作成します。(記事のメインテーマのクラスです)

EditorWindow.cs
using System;
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;

#if UNITY_EDITOR
public class EditorWindowScript : EditorWindow{
    [MenuItem("Editor/SceneEditWindow")]
    private static void Create(){
        // エディタウインドウ生成
        GetWindow<EditorWindowScript>("SceneEditWindow");
    }

    //テキストフィールド用変数
    string textField1 = "";
    string textField2 = "";

    void OnGUI(){
        //エディタレイアウト
        //2行開ける
        EditorGUILayout.Space();
        EditorGUILayout.Space();
        //セーブ用テキストフィールド作成
        textField1 = EditorGUILayout.TextField( "セーブするシーン名",textField1 );

        using (new GUILayout.HorizontalScope()){
            //セーブ用ボタン作成
            if (GUILayout.Button("Save Scene")){
                //Castle Object内のTag付きObjectを全てセーブします
                GameObject castle = GameObject.Find("Castle");
                //Resources内のCastleObjectData(ScriptableObject)をtextField1を用いてアクセス
                CastleObjectData data = Resources.Load<CastleObjectData>("CastleObjectData/CastleObjectData"+textField1);
                //すでに存在するデータ名であれば上書きして良いか警告する
                if(data != null){
                    //Canselボタンあり、OKかCanselかで処理分けする場合のUnityDialog
                    bool b = EditorUtility.DisplayDialog("警告", "上書きしますか??", "はい", "いいえ");
                    if(!b) {
                        //”いいえ”ならセーブに必要なTextField1をリセット
                        textField1 = "";
                    } 
                }
                Resources.UnloadUnusedAssets();

                if(textField1!=""){
                    //データ保存用CastleObjectData(ScriptableObject)を作成
                    CastleObjectData castleObjectData = ScriptableObject.CreateInstance<CastleObjectData>();
                    //Castle Object以下の全てのTag付きObjectのPosition,Rotation,ObjectType(Tag)を保存します。(形式についてはCastleObjectDataクラスを参照)(GetAllメソッドについてはGetAllChildrenクラスを参照)
                    List<GameObject> list = GetAllChildren.GetAll(castle);
		            foreach (GameObject obj in list) {
                        ObjectInfo objectInfo = new ObjectInfo();
                        if(obj.tag!="Untagged"){
                            objectInfo.objectTransform = obj.transform.position;
                            objectInfo.objectRotation  = obj.transform.rotation;
                            objectInfo.objectType = obj.tag;
                            
                            castleObjectData.castleObjects.Add(objectInfo);
                        }
		            }
                    //ファイル書き出し
                    AssetDatabase.CreateAsset(castleObjectData, "Assets/Resources/CastleObjectData/CastleObjectData"+textField1+".asset");
                }
            }
        }
        //2行開ける
        EditorGUILayout.Space();
        EditorGUILayout.Space();
        //ロード用テキストフィールド作成
        textField2 = EditorGUILayout.TextField( "ロードするシーン名",textField2);
        
        using (new GUILayout.HorizontalScope()){
            //ロードボタン
            if (GUILayout.Button("Load Scene")){
                int prefabNum = 0;
                GameObject castle = GameObject.Find("Castle");
                //Canselボタンあり、OKかCanselかで処理分けする場合
                bool b = EditorUtility.DisplayDialog("警告", "ロードしますか??", "はい", "いいえ");
                if(b) {
                    //castle以下のObjectを取得(GetAllメソッドについてはGetAllChildrenクラスを参照)
                    List<GameObject> list = GetAllChildren.GetAll(castle);
                    //元々のシーン上のオブジェクトを破棄
                    foreach(GameObject obj in list){
                        DestroyImmediate(obj);
                    }

                    CastleObjectData data = Resources.Load<CastleObjectData>("CastleObjectData/CastleObjectData"+textField2);
                    foreach(ObjectInfo objectInfo in data.castleObjects){
                        prefabNum++;
                        //プレハブを取得(Tag名とPrefab名は便宜上同じにしておく)
                        GameObject prefab;
                        prefab = (GameObject)Resources.Load("Prefabs/"+objectInfo.objectType);
                        string prefabName = objectInfo.objectType;
                        
                        //プレハブからインスタンスを生成
                        if(prefab!=null){
                            var obj = Instantiate(prefab, objectInfo.objectTransform , objectInfo.objectRotation ,castle.transform);
                            obj.name = prefabName;
                        }
                    }
                }
            }
	    }
    }
}
#endif

以下、CastleObjectDataを保存するScriptableObjectの雛形クラス

CastleObjectData.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

//ここにPrefab名とTag名と同じObjectTypeをEnumで定義しておく
public enum ObjectType{
    Block = 0,
    Bomb = 1,
    FiveBlock = 2,
    TowerBlock = 3
}

//ScriptableObjectの雛形を定義
public class CastleObjectData : ScriptableObject{
    //各ObjectのObjectInfoデータセットのList
    public List<ObjectInfo> castleObjects = new List<ObjectInfo>();

}

//ObjectInfoにはTransformPosition、TransformRotation、TagをStringで保存
[System.Serializable]
public class ObjectInfo{
    public Vector3 objectTransform;
    public Quaternion objectRotation;
    public string objectType;
}

以下、指定したObject以下の全てのObjectをListで取得するためのClass(参考文献1のScript参照)

GetAllChildren.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public static class GetAllChildren
{
	public static List<GameObject>  GetAll (this GameObject obj)
	{
		List<GameObject> allChildren = new List<GameObject> ();
		GetChildren (obj, ref allChildren);
		return allChildren;
	}

        //子要素を取得してリストに追加
	public static void GetChildren (GameObject obj, ref List<GameObject> allChildren)
	{
		Transform children = obj.GetComponentInChildren<Transform> ();
		//子要素がいなければ終了
		if (children.childCount == 0) {
			return;
		}
		foreach (Transform ob in children) {
			allChildren.Add (ob.gameObject);
			GetChildren (ob.gameObject, ref allChildren);
		}
	}
}

終わりに

Scene内のObjectをPosition、Rotation、ObjectTypeを一括セーブ、ロードできるエディタは拡張性に富んでいて便利だと思います。ぜひ応用してもらいたいです。

##参考文献
1.「全ての子要素を取得する(子要素の子要素の子要素の‥)」
http://kazuooooo.hatenablog.com/entry/2015/08/07/010938
2.「【エディタ拡張徹底解説】初級編①:ウィンドウを自作してみよう【Unity】」
https://caitsithware.com/wordpress/archives/1377
3.「【エディタ拡張徹底解説】初級編③:いろいろなGUI(EditorGUILayout編)【Unity】」
https://caitsithware.com/wordpress/archives/1454

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?