【Unity】カジュアルゲーム向けScriptableObjectでシーンオブジェクトデータの保存【C#】【エディタ拡張】
はじめに
ステージ数が多くなりがちなUnityでのカジュアルゲーム開発。
シーン上のTag付きObjectのロードとセーブをエディタ上で行える実用的なスクリプトを作りました。
Scene内のObjectをPosition、Rotation、ObjectTypeを一括セーブ、ロードできるエディタは拡張性に富んでいて便利だと思います。ぜひ応用してもらいたいです。
荒い実装ではありますが、エクセルではなくScriptableObjectにセーブすることを学んだので共有。
エディタ拡張も用いてプランナーのステージ開発も助けます。
ちなみに本スクリプトは城を構成するPrefab(レンガなど)をセーブ、ロードするための変数などの命名をしています。
作成したEditorはUnityのEditorタブに表示されます。
開発環境
OS: MacOS Mojave
開発環境: Unity 2018.4.11f1 personal
開発言語: C#
動作環境
あるオブジェクト以下(本スクリプトではCastleオブジェクト)のTag付きObjectのPrefab(Prefab名とTag名を同じにしてください)のPosition、Rotation、種類を保存、ロードできるようになります。実行しなくてもエディタ上で編集したまま保存できます。
ソースコードとその説明
このスクリプト(EditorWindow.cs)ではEditor上にセーブ機能とロード機能をもつエディタウインドウを作成します。(記事のメインテーマのクラスです)
usingSystem;usingUnityEngine;usingUnityEditor;usingSystem.Collections;usingSystem.Collections.Generic;#if UNITY_EDITOR
publicclassEditorWindowScript:EditorWindow{[MenuItem("Editor/SceneEditWindow")]privatestaticvoidCreate(){// エディタウインドウ生成GetWindow<EditorWindowScript>("SceneEditWindow");}//テキストフィールド用変数stringtextField1="";stringtextField2="";voidOnGUI(){//エディタレイアウト//2行開けるEditorGUILayout.Space();EditorGUILayout.Space();//セーブ用テキストフィールド作成textField1=EditorGUILayout.TextField("セーブするシーン名",textField1);using(newGUILayout.HorizontalScope()){//セーブ用ボタン作成if(GUILayout.Button("Save Scene")){//Castle Object内のTag付きObjectを全てセーブしますGameObjectcastle=GameObject.Find("Castle");//Resources内のCastleObjectData(ScriptableObject)をtextField1を用いてアクセスCastleObjectDatadata=Resources.Load<CastleObjectData>("CastleObjectData/CastleObjectData"+textField1);//すでに存在するデータ名であれば上書きして良いか警告するif(data!=null){//Canselボタンあり、OKかCanselかで処理分けする場合のUnityDialogboolb=EditorUtility.DisplayDialog("警告","上書きしますか??","はい","いいえ");if(!b){//”いいえ”ならセーブに必要なTextField1をリセットtextField1="";}}Resources.UnloadUnusedAssets();if(textField1!=""){//データ保存用CastleObjectData(ScriptableObject)を作成CastleObjectDatacastleObjectData=ScriptableObject.CreateInstance<CastleObjectData>();//Castle Object以下の全てのTag付きObjectのPosition,Rotation,ObjectType(Tag)を保存します。(形式についてはCastleObjectDataクラスを参照)(GetAllメソッドについてはGetAllChildrenクラスを参照)List<GameObject>list=GetAllChildren.GetAll(castle);foreach(GameObjectobjinlist){ObjectInfoobjectInfo=newObjectInfo();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(newGUILayout.HorizontalScope()){//ロードボタンif(GUILayout.Button("Load Scene")){intprefabNum=0;GameObjectcastle=GameObject.Find("Castle");//Canselボタンあり、OKかCanselかで処理分けする場合boolb=EditorUtility.DisplayDialog("警告","ロードしますか??","はい","いいえ");if(b){//castle以下のObjectを取得(GetAllメソッドについてはGetAllChildrenクラスを参照)List<GameObject>list=GetAllChildren.GetAll(castle);//元々のシーン上のオブジェクトを破棄foreach(GameObjectobjinlist){DestroyImmediate(obj);}CastleObjectDatadata=Resources.Load<CastleObjectData>("CastleObjectData/CastleObjectData"+textField2);foreach(ObjectInfoobjectInfoindata.castleObjects){prefabNum++;//プレハブを取得(Tag名とPrefab名は便宜上同じにしておく)GameObjectprefab;prefab=(GameObject)Resources.Load("Prefabs/"+objectInfo.objectType);stringprefabName=objectInfo.objectType;//プレハブからインスタンスを生成if(prefab!=null){varobj=Instantiate(prefab,objectInfo.objectTransform,objectInfo.objectRotation,castle.transform);obj.name=prefabName;}}}}}}}#endif
以下、CastleObjectDataを保存するScriptableObjectの雛形クラス
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingUnityEditor;//ここにPrefab名とTag名と同じObjectTypeをEnumで定義しておくpublicenumObjectType{Block=0,Bomb=1,FiveBlock=2,TowerBlock=3}//ScriptableObjectの雛形を定義publicclassCastleObjectData:ScriptableObject{//各ObjectのObjectInfoデータセットのListpublicList<ObjectInfo>castleObjects=newList<ObjectInfo>();}//ObjectInfoにはTransformPosition、TransformRotation、TagをStringで保存[System.Serializable]publicclassObjectInfo{publicVector3objectTransform;publicQuaternionobjectRotation;publicstringobjectType;}
以下、指定したObject以下の全てのObjectをListで取得するためのClass(参考文献1のScript参照)
usingUnityEngine;usingSystem.Collections;usingSystem.Collections.Generic;publicstaticclassGetAllChildren{publicstaticList<GameObject>GetAll(thisGameObjectobj){List<GameObject>allChildren=newList<GameObject>();GetChildren(obj,refallChildren);returnallChildren;}//子要素を取得してリストに追加publicstaticvoidGetChildren(GameObjectobj,refList<GameObject>allChildren){Transformchildren=obj.GetComponentInChildren<Transform>();//子要素がいなければ終了if(children.childCount==0){return;}foreach(Transformobinchildren){allChildren.Add(ob.gameObject);GetChildren(ob.gameObject,refallChildren);}}}
終わりに
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