現場レベルのゲーム制作が、すべてここで学べます。
Unityでゲーム開発をしていると、ScriptableObjectで管理しているマスターデータやパラメータをCSV形式で出力したいケースがよくあります。企画者との連携や、外部ツールでのデータ編集、バックアップなど、様々な場面で役立ちます。
この記事では、フォルダ内のScriptableObjectを一括でCSVに出力するエディタ拡張の作り方を解説します。
この記事で作成するもの
今回作成するエディタ拡張には、以下の2つの出力機能があります:
- 型ごとにまとめて出力: 同じ型のScriptableObjectを1つのCSVファイルにまとめる
- 個別に出力: 各ScriptableObjectを別々のCSVファイルに出力
どちらの方法も、指定したフォルダ内の全ScriptableObjectを自動的に検索して出力します。
実装コード
以下のコードを Assets/Editor フォルダ内に ScriptableObjectToCSV.cs という名前で保存してください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
using UnityEngine; using UnityEditor; using System.IO; using System.Text; using System.Reflection; using System.Collections.Generic; using System.Linq; public class ScriptableObjectToCSV : EditorWindow { private DefaultAsset targetFolder; private string folderPath = ""; [MenuItem("Tools/Export ScriptableObjects to CSV")] public static void ShowWindow() { GetWindow<ScriptableObjectToCSV>("SO to CSV"); } void OnGUI() { GUILayout.Label("Export ScriptableObjects to CSV", EditorStyles.boldLabel); EditorGUILayout.Space(); // フォルダ選択 EditorGUI.BeginChangeCheck(); targetFolder = (DefaultAsset)EditorGUILayout.ObjectField( "Target Folder", targetFolder, typeof(DefaultAsset), false ); if (EditorGUI.EndChangeCheck() && targetFolder != null) { folderPath = AssetDatabase.GetAssetPath(targetFolder); if (!AssetDatabase.IsValidFolder(folderPath)) { targetFolder = null; folderPath = ""; EditorUtility.DisplayDialog("Error", "Please select a folder", "OK"); } } if (!string.IsNullOrEmpty(folderPath)) { EditorGUILayout.LabelField("Selected Path:", folderPath); } EditorGUILayout.Space(); if (GUILayout.Button("Export All to CSV (By Type)")) { if (!string.IsNullOrEmpty(folderPath)) { ExportAllByType(folderPath); } else { EditorUtility.DisplayDialog("Error", "Please select a folder", "OK"); } } if (GUILayout.Button("Export Each to Separate CSV")) { if (!string.IsNullOrEmpty(folderPath)) { ExportEachToSeparateCSV(folderPath); } else { EditorUtility.DisplayDialog("Error", "Please select a folder", "OK"); } } } // 同じ型のScriptableObjectごとに1つのCSVファイルにまとめる void ExportAllByType(string folderPath) { // フォルダ内の全ScriptableObjectを取得 string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new[] { folderPath }); if (guids.Length == 0) { EditorUtility.DisplayDialog("Info", "No ScriptableObjects found in the folder", "OK"); return; } // 型ごとにグループ化 Dictionary<System.Type, List<ScriptableObject>> groupedObjects = new Dictionary<System.Type, List<ScriptableObject>>(); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); ScriptableObject obj = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path); if (obj != null) { System.Type type = obj.GetType(); if (!groupedObjects.ContainsKey(type)) { groupedObjects[type] = new List<ScriptableObject>(); } groupedObjects[type].Add(obj); } } // 保存先フォルダを選択 string saveFolderPath = EditorUtility.OpenFolderPanel("Select Save Folder", "", ""); if (string.IsNullOrEmpty(saveFolderPath)) return; int exportedCount = 0; // 型ごとにCSVを出力 foreach (var kvp in groupedObjects) { System.Type type = kvp.Key; List<ScriptableObject> objects = kvp.Value; string csvPath = Path.Combine(saveFolderPath, type.Name + ".csv"); ExportToCSV(objects.ToArray(), csvPath); exportedCount++; } EditorUtility.DisplayDialog( "Success", $"Exported {exportedCount} CSV file(s) from {guids.Length} ScriptableObject(s)", "OK" ); } // 各ScriptableObjectを個別のCSVファイルに出力 void ExportEachToSeparateCSV(string folderPath) { string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new[] { folderPath }); if (guids.Length == 0) { EditorUtility.DisplayDialog("Info", "No ScriptableObjects found in the folder", "OK"); return; } string saveFolderPath = EditorUtility.OpenFolderPanel("Select Save Folder", "", ""); if (string.IsNullOrEmpty(saveFolderPath)) return; foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); ScriptableObject obj = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path); if (obj != null) { string csvPath = Path.Combine(saveFolderPath, obj.name + ".csv"); ExportToCSV(new ScriptableObject[] { obj }, csvPath); } } EditorUtility.DisplayDialog( "Success", $"Exported {guids.Length} CSV file(s)", "OK" ); } void ExportToCSV(ScriptableObject[] objects, string path) { if (objects.Length == 0) return; StringBuilder csv = new StringBuilder(); FieldInfo[] fields = objects[0].GetType().GetFields( BindingFlags.Public | BindingFlags.Instance ); // ヘッダー行 StringBuilder header = new StringBuilder(); header.Append("Name,"); // オブジェクト名も追加 foreach (FieldInfo field in fields) { header.Append(field.Name + ","); } csv.AppendLine(header.ToString().TrimEnd(',')); // データ行 foreach (ScriptableObject obj in objects) { StringBuilder row = new StringBuilder(); row.Append(obj.name + ","); // オブジェクト名 foreach (FieldInfo field in fields) { object value = field.GetValue(obj); string valueString = ConvertValueToString(value); // CSVエスケープ処理 if (valueString.Contains(",") || valueString.Contains("\"") || valueString.Contains("\n")) { valueString = "\"" + valueString.Replace("\"", "\"\"") + "\""; } row.Append(valueString + ","); } csv.AppendLine(row.ToString().TrimEnd(',')); } File.WriteAllText(path, csv.ToString(), Encoding.UTF8); } string ConvertValueToString(object value) { if (value == null) return ""; // 配列やリストの処理 if (value is System.Collections.IList list) { List<string> items = new List<string>(); foreach (var item in list) { items.Add(item != null ? item.ToString() : ""); } return "[" + string.Join("; ", items) + "]"; } // Unityオブジェクトの処理 if (value is UnityEngine.Object unityObj) { return unityObj != null ? unityObj.name : ""; } return value.ToString(); } } |
使い方
1. エディタウィンドウを開く
Unityエディタの上部メニューから Tools > Export ScriptableObjects to CSV を選択します。

2. フォルダを選択
エディタウィンドウが開いたら、「Target Folder」の欄にProjectビューからエクスポートしたいScriptableObjectが入っているフォルダをドラッグ&ドロップします。

3. エクスポート方法を選択
2つのボタンから出力方法を選べます:
「Export All to CSV (By Type)」
- 同じ型のScriptableObjectを1つのCSVファイルにまとめます
- 例:
EnemyData型が10個あれば、EnemyData.csvという1つのファイルに全て出力されます - マスターデータとして管理したい場合に便利です
「Export Each to Separate CSV」
- 各ScriptableObjectを個別のCSVファイルに出力します
- 例:
Enemy_Slime.assetはEnemy_Slime.csvとして出力されます - 個別に管理したい場合に便利です
4. 保存先を選択
ボタンをクリックすると、CSVファイルの保存先フォルダを選択するダイアログが表示されます。任意のフォルダを選択してください。
5. 完了
エクスポートが完了すると、ダイアログで結果が表示されます。

↑このようにサブフォルダ内にあるデータもばっちりCSV化されてることがわかります。
実際に中身を見てみましょう。

こちらは「魔女と召喚獣」のゲームアプリの守りのベールのScriptable Objectです。
CSVデータにすると・・・
![]()
↑少し見づらいですがこのようにCSVデータとして表計算ソフトなどで開けています。
守りのベールの前にマジックボルトのカード情報も記載されていますね。
各ファイル同様に必要なデータ構造がScriptable ObjectからCSVとして出力されていることがわかります。
コードの解説
フォルダ内のScriptableObjectを検索
|
1 2 |
string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new[] { folderPath }); |
AssetDatabase.FindAssetsを使用して、指定フォルダ内(サブフォルダ含む)の全ScriptableObjectを検索しています。
型ごとにグループ化
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Dictionary<System.Type, List<ScriptableObject>> groupedObjects = new Dictionary<System.Type, List<ScriptableObject>>(); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); ScriptableObject obj = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path); if (obj != null) { System.Type type = obj.GetType(); if (!groupedObjects.ContainsKey(type)) { groupedObjects[type] = new List<ScriptableObject>(); } groupedObjects[type].Add(obj); } } |
リフレクションを使って各ScriptableObjectの型を取得し、Dictionaryでグループ化しています。
CSVへの変換
|
1 2 3 4 5 6 7 8 9 10 11 |
void ExportToCSV(ScriptableObject[] objects, string path) { // フィールド情報を取得 FieldInfo[] fields = objects[0].GetType().GetFields( BindingFlags.Public | BindingFlags.Instance ); // ヘッダー行を作成 // データ行を作成 } |
リフレクションでpublicフィールドを取得し、ヘッダー行とデータ行を生成しています。
特殊な値の処理
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
string ConvertValueToString(object value) { if (value == null) return ""; // 配列やリストは [item1; item2; item3] 形式に変換 if (value is System.Collections.IList list) { List<string> items = new List<string>(); foreach (var item in list) { items.Add(item != null ? item.ToString() : ""); } return "[" + string.Join("; ", items) + "]"; } // UnityオブジェクトはNameを返す if (value is UnityEngine.Object unityObj) { return unityObj != null ? unityObj.name : ""; } return value.ToString(); } |
配列、リスト、Unityオブジェクトなどの特殊な型も適切に文字列化しています。
応用例
特定の型だけをエクスポート
特定の型のScriptableObjectだけをエクスポートしたい場合は、検索条件を変更します:
|
1 2 |
string[] guids = AssetDatabase.FindAssets("t:EnemyData", new[] { folderPath }); |
Propertyも出力対象にする
publicプロパティも出力したい場合は、以下のように変更します:
|
1 2 3 4 5 |
// FieldだけでなくPropertyも取得 PropertyInfo[] properties = objects[0].GetType().GetProperties( BindingFlags.Public | BindingFlags.Instance ); |
カスタムフォーマット
出力フォーマットをカスタマイズしたい場合は、ConvertValueToStringメソッドを編集します。
まとめ
この記事では、UnityのScriptableObjectをCSV形式で一括エクスポートするエディタ拡張を作成しました。
主な機能:
- フォルダ内のScriptableObjectを自動検索
- 型ごとにグループ化して出力
- 個別ファイルとしても出力可能
- 配列やリストなどの複雑なデータにも対応
この拡張機能を使えば、企画者との連携やデータのバックアップ、外部ツールでの編集など、様々な場面で活用できます。
ぜひプロジェクトに導入して、開発効率を向上させてください!
参考リンク:
現場レベルのゲーム制作が、すべてここで学べます。






コメント