Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Linq;
using System.Reflection;
using System.Text;
using Game.Shared.Scriptable.Database;
using UnityEditor;
using UnityEngine;

Expand All @@ -23,13 +22,12 @@ namespace Game.Shared.Scriptable.Database.EditorTools
public static class ScriptableDatabaseBuilder
{
private const string OutDir = "Assets/Programs/Runtime/Shared/Scriptable/Database/Generated";
private const string DatabaseAssetPath = "Assets/ProjectAssets/Database/ScriptableDatabase.asset";
internal const string DatabaseAssetPath = "Assets/ProjectAssets/Scriptable/Database/ScriptableDatabase.asset";
private const string DatabaseClassName = "ScriptableDatabase";
private const string DatabaseNamespace = "Game.Shared.Scriptable.Database";

// ---- コマンド①: コンテナクラス生成 ----

[MenuItem("Tools/Scriptable Database/Build")]
public static void Build()
{
Directory.CreateDirectory(OutDir);
Expand Down Expand Up @@ -75,13 +73,12 @@ private static string TableTypeName(Type recordType)

// ---- コマンド②: テーブル資産の自動登録 ----

[MenuItem("Tools/Scriptable Database/Register")]
public static void Register()
{
var dbType = FindDatabaseType();
if (dbType == null)
{
Debug.LogError("[ScriptableDatabaseBuilder] ScriptableDatabase 型が見つかりません。先に 'Tools/Scriptable Database/Build' を実行してください。");
Debug.LogError("[ScriptableDatabaseBuilder] ScriptableDatabase 型が見つかりません。先に ScriptableDatabaseWindow の 'Build' を実行してください。");
return;
}

Expand Down Expand Up @@ -123,7 +120,7 @@ public static void Register()
Debug.Log($"[ScriptableDatabaseBuilder] Register 完了: 結線 {wired} 件 / 欠落 {missing} 件。");
}

private static Type FindDatabaseType() =>
internal static Type FindDatabaseType() =>
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(SafeTypes)
.FirstOrDefault(t => t.FullName == $"{DatabaseNamespace}.{DatabaseClassName}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Game.Shared.Scriptable.Database;
using UnityEditor;
using UnityEngine;

namespace Game.Shared.Scriptable.Database.EditorTools
{
/// <summary>
/// ScriptableDatabase.asset の Inspector に一括 CSV/TSV 入出力ボタンを表示する。
/// 実処理は <see cref="ScriptableDatabaseIO"/> に委譲する(target を ScriptableObject として渡す)。
/// </summary>
[CustomEditor(typeof(ScriptableDatabase))]
public class ScriptableDatabaseEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();

var database = (ScriptableObject)target;

EditorGUILayout.Space();
EditorGUILayout.LabelField("一括 CSV / TSV", EditorStyles.boldLabel);

EditorGUILayout.LabelField("Export", EditorStyles.miniBoldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Export All (CSV)")) ScriptableDatabaseIO.BatchExport(database, "csv");
if (GUILayout.Button("Export All (TSV)")) ScriptableDatabaseIO.BatchExport(database, "tsv");
}

EditorGUILayout.LabelField("Import (Replace)", EditorStyles.miniBoldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Import All (CSV)")) ScriptableDatabaseIO.BatchImport(database, "csv", mergeByPrimaryKey: false);
if (GUILayout.Button("Import All (TSV)")) ScriptableDatabaseIO.BatchImport(database, "tsv", mergeByPrimaryKey: false);
}

EditorGUILayout.LabelField("Import (Merge by PrimaryKey)", EditorStyles.miniBoldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Import All (CSV)")) ScriptableDatabaseIO.BatchImport(database, "csv", mergeByPrimaryKey: true);
if (GUILayout.Button("Import All (TSV)")) ScriptableDatabaseIO.BatchImport(database, "tsv", mergeByPrimaryKey: true);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Game.Shared.Scriptable.Database;
using UnityEditor;
using UnityEngine;

namespace Game.Shared.Scriptable.Database.EditorTools
{
/// <summary>
/// ScriptableDatabase 配下の全テーブルを一括で CSV/TSV 入出力するエディタ機能。
/// テーブル列挙はリフレクション(ScriptableTableBase フィールド)で行い、ScriptableDatabase 型へ
/// コンパイル時依存しない(未生成でも壊れないビルダー方針と同じ)。ファイル名規約は {TableType.Name}.{ext}。
/// 文字列⇔ファイルの変換は <see cref="ScriptableTableFileIO"/>、副作用(ダイアログ/Undo/保存)はここで担う。
/// </summary>
public static class ScriptableDatabaseIO
{
// ---- 一括処理本体(ScriptableDatabase 型に依存せず ScriptableObject + リフレクションで扱う) ----

public static void BatchExport(ScriptableObject database, string extension)
{
if (database == null) return;

var dir = EditorUtility.SaveFolderPanel("Export All Tables", ScriptableTableIO.DefaultDirectory(), string.Empty);
if (string.IsNullOrEmpty(dir)) return;

try
{
int n = 0;
foreach (var table in Tables(database))
{
var path = Path.Combine(dir, table.GetType().Name + "." + extension);
ScriptableTableFileIO.ExportToFile(table, path, ScriptableTableIO.Utf8NoBom);
n++;
}
Debug.Log($"[ScriptableDatabaseIO] {n} テーブルを書き出しました({extension.ToUpperInvariant()}): {dir}", database);
}
catch (Exception e)
{
Debug.LogError($"[ScriptableDatabaseIO] 一括エクスポートに失敗しました: {e}", database);
EditorUtility.DisplayDialog("Export All 失敗", e.Message, "OK");
}
}

public static void BatchImport(ScriptableObject database, string extension, bool mergeByPrimaryKey)
{
if (database == null) return;

var dir = EditorUtility.OpenFolderPanel("Import All Tables", ScriptableTableIO.DefaultDirectory(), string.Empty);
if (string.IsNullOrEmpty(dir)) return;

try
{
int imported = 0, skipped = 0;
foreach (var table in Tables(database))
{
var path = Path.Combine(dir, table.GetType().Name + "." + extension);
if (!File.Exists(path))
{
skipped++;
Debug.LogWarning($"[ScriptableDatabaseIO] {Path.GetFileName(path)} が見つかりません。スキップします。", table);
continue;
}

Undo.RecordObject(table, "Import All Tables");
ScriptableTableFileIO.ImportFromFile(table, path, mergeByPrimaryKey);
EditorUtility.SetDirty(table);
imported++;
}

AssetDatabase.SaveAssets();
Debug.Log($"[ScriptableDatabaseIO] 取り込み {imported} 件 / スキップ {skipped} 件" +
$"({(mergeByPrimaryKey ? "Merge" : "Replace")}, {extension.ToUpperInvariant()}): {dir}", database);
}
catch (Exception e)
{
Debug.LogError($"[ScriptableDatabaseIO] 一括インポートに失敗しました: {e}", database);
EditorUtility.DisplayDialog("Import All 失敗", e.Message, "OK");
}
}

// database が保持する ScriptableTableBase フィールドを列挙する(null/未結線は警告してスキップ)。
private static IEnumerable<ScriptableTableBase> Tables(ScriptableObject database)
{
var fields = database.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (var field in fields)
{
if (!typeof(ScriptableTableBase).IsAssignableFrom(field.FieldType)) continue;

if (field.GetValue(database) is ScriptableTableBase table)
{
yield return table;
}
else
{
Debug.LogWarning($"[ScriptableDatabaseIO] フィールド '{field.Name}' が未結線です。スキップします。(Register を実行してください)", database);
}
}
}

// ---- 対象 ScriptableDatabase の解決(ScriptableDatabaseWindow から利用) ----

// 固定パスの ScriptableDatabase.asset をロードしてアクションを実行する(型はリフレクションで解決)。
internal static void RunWithDatabase(Action<ScriptableObject> action)
{
var dbType = ScriptableDatabaseBuilder.FindDatabaseType();
if (dbType == null)
{
Debug.LogError("[ScriptableDatabaseIO] ScriptableDatabase 型が見つかりません。先に ScriptableDatabaseWindow の 'Build' を実行してください。");
return;
}

var database = AssetDatabase.LoadAssetAtPath(ScriptableDatabaseBuilder.DatabaseAssetPath, dbType) as ScriptableObject;
if (database == null)
{
Debug.LogError($"[ScriptableDatabaseIO] {ScriptableDatabaseBuilder.DatabaseAssetPath} が見つかりません。先に ScriptableDatabaseWindow の 'Register' を実行してください。");
return;
}

action(database);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using UnityEditor;
using UnityEngine;

namespace Game.Shared.Scriptable.Database.EditorTools
{
/// <summary>
/// Scriptable Database の生成・登録・一括 CSV/TSV 入出力を集約したエディタウィンドウ。
/// 各ボタンは既存の static コマンド(Generator / Builder / IO)を呼ぶだけで、ロジックは持たない。
/// </summary>
public class ScriptableDatabaseWindow : EditorWindow
{
[MenuItem("Project/Database/ScriptableDatabaseWindow")]
public static void Open() => GetWindow<ScriptableDatabaseWindow>("Scriptable Database");

private void OnGUI()
{
EditorGUILayout.LabelField("コード生成", EditorStyles.boldLabel);
if (GUILayout.Button("Generate(テーブルクラス {Type}Table.g.cs)")) ScriptableTableGenerator.Generate();
if (GUILayout.Button("Build(コンテナクラス ScriptableDatabase.g.cs)")) ScriptableDatabaseBuilder.Build();

EditorGUILayout.Space();
EditorGUILayout.LabelField("資産登録", EditorStyles.boldLabel);
if (GUILayout.Button("Register(テーブル資産を .asset へ結線)")) ScriptableDatabaseBuilder.Register();

EditorGUILayout.Space();
EditorGUILayout.LabelField("一括 Export", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Export All (CSV)"))
ScriptableDatabaseIO.RunWithDatabase(db => ScriptableDatabaseIO.BatchExport(db, "csv"));
if (GUILayout.Button("Export All (TSV)"))
ScriptableDatabaseIO.RunWithDatabase(db => ScriptableDatabaseIO.BatchExport(db, "tsv"));
}

EditorGUILayout.Space();
EditorGUILayout.LabelField("一括 Import (Replace)", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Import All (CSV)"))
ScriptableDatabaseIO.RunWithDatabase(db => ScriptableDatabaseIO.BatchImport(db, "csv", mergeByPrimaryKey: false));
if (GUILayout.Button("Import All (TSV)"))
ScriptableDatabaseIO.RunWithDatabase(db => ScriptableDatabaseIO.BatchImport(db, "tsv", mergeByPrimaryKey: false));
}

EditorGUILayout.Space();
EditorGUILayout.LabelField("一括 Import (Merge by PrimaryKey)", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Import All (CSV)"))
ScriptableDatabaseIO.RunWithDatabase(db => ScriptableDatabaseIO.BatchImport(db, "csv", mergeByPrimaryKey: true));
if (GUILayout.Button("Import All (TSV)"))
ScriptableDatabaseIO.RunWithDatabase(db => ScriptableDatabaseIO.BatchImport(db, "tsv", mergeByPrimaryKey: true));
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Game.Shared.Scriptable.Database;
using UnityEditor;
using UnityEngine;

Expand All @@ -25,6 +24,8 @@ public override void OnInspectorGUI()
}

EditorGUILayout.Space();
EditorGUILayout.LabelField("Validation", EditorStyles.boldLabel);

if (GUILayout.Button("Sort & Validate"))
{
foreach (var o in targets)
Expand All @@ -35,6 +36,43 @@ public override void OnInspectorGUI()
EditorUtility.SetDirty(tb);
}
}

EditorGUILayout.Space();
EditorGUILayout.LabelField("CSV / TSV", EditorStyles.boldLabel);

// Import は対象アセットが一意に定まる単一選択時のみ。
if (targets.Length == 1)
{
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Import (Replace)"))
ScriptableTableIO.Import(table, mergeByPrimaryKey: false);
if (GUILayout.Button("Import (Merge by PrimaryKey)"))
ScriptableTableIO.Import(table, mergeByPrimaryKey: true);
}
}
else
{
EditorGUILayout.HelpBox("Import は単一アセット選択時のみ実行できます。", MessageType.Info);
}

EditorGUILayout.Space();

// Export は複数選択でも各アセットを個別に書き出す。
// SaveFilePanel は単一拡張子しか扱えないため形式ごとにボタンを分ける。
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Export to CSV"))
{
foreach (var o in targets)
ScriptableTableIO.Export((ScriptableTableBase)o, "csv");
}
if (GUILayout.Button("Export to TSV"))
{
foreach (var o in targets)
ScriptableTableIO.Export((ScriptableTableBase)o, "tsv");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Linq;
using System.Reflection;
using System.Text;
using Game.Shared.Scriptable.Database;
using UnityEditor;
using UnityEngine;

Expand All @@ -18,7 +17,6 @@ public static class ScriptableTableGenerator
{
private const string OutDir = "Assets/Programs/Runtime/Shared/Scriptable/Database/Generated";

[MenuItem("Tools/Scriptable Database/Generate")]
public static void Generate()
{
Directory.CreateDirectory(OutDir);
Expand Down
Loading
Loading