Files
A-Fairytale-Gone-Bad-Briar-…/Assets/Scripts/Editor/Windows/ScriptableObjectGenerator.cs

655 lines
27 KiB
C#

using System;
using System.Collections.Generic;
using BriarQueen.Data.Assets;
using BriarQueen.Data.Identifiers;
using BriarQueen.Framework.Managers.Player.Data;
using UnityEditor;
using UnityEngine;
namespace BriarQueen.Editor.Windows
{
public class ScriptableObjectGenerator : EditorWindow
{
private const string DEFAULT_ITEM_ROOT_FOLDER = "Assets/Data/Information/Items";
private const string DEFAULT_ICON_FOLDER = "Assets/Data/Artwork/Icons";
private const string DEFAULT_ASSET_ENTRY_ROOT_FOLDER = "Assets/Data/Information/Asset Keys";
private const string DEFAULT_CODEX_ROOT_FOLDER = "Assets/Data/Information/Codex";
private string _itemRootFolder = DEFAULT_ITEM_ROOT_FOLDER;
private string _iconFolder = DEFAULT_ICON_FOLDER;
private string _assetEntryRootFolder = DEFAULT_ASSET_ENTRY_ROOT_FOLDER;
private string _codexRootFolder = DEFAULT_CODEX_ROOT_FOLDER;
private bool _overwriteExisting;
private bool _assignIconsDuringCreate = true;
private bool _overwriteExistingIcons;
private bool _createPickups = true;
private bool _createEnvironment = true;
private bool _createPuzzleSlots = true;
private bool _createUIAssetEntries = true;
private bool _createSceneAssetEntries = true;
private bool _createLevelAssetEntries = true;
private bool _createItemAssetEntries = true;
private bool _createBookEntries = true;
private bool _createPuzzleClues = true;
private bool _createPhotos = true;
[MenuItem("Briar Queen/Tools/Assets/Scriptable Object Generator")]
public static void Open()
{
var window = GetWindow<ScriptableObjectGenerator>("ItemDataSO Generator");
window.minSize = new Vector2(700f, 600f);
}
private void OnGUI()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Content Generator", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"Create or update ItemDataSo assets from enums, assign icons to existing ItemDataSo assets, create/update AssetEntry assets from asset enums, and create/update CodexEntrySO assets from codex enums.",
MessageType.Info);
EditorGUILayout.Space();
DrawItemDataSection();
EditorGUILayout.Space(10f);
DrawAssetEntrySection();
EditorGUILayout.Space(10f);
DrawCodexSection();
}
private void DrawItemDataSection()
{
EditorGUILayout.LabelField("ItemDataSo", EditorStyles.boldLabel);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Folders", EditorStyles.miniBoldLabel);
_itemRootFolder = EditorGUILayout.TextField("Items Root Folder", _itemRootFolder);
_iconFolder = EditorGUILayout.TextField("Icons Folder", _iconFolder);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Create / Update Options", EditorStyles.miniBoldLabel);
_overwriteExisting = EditorGUILayout.Toggle("Overwrite Existing Assets", _overwriteExisting);
_assignIconsDuringCreate = EditorGUILayout.Toggle("Assign Icons During Create", _assignIconsDuringCreate);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Generate Groups", EditorStyles.miniBoldLabel);
_createPickups = EditorGUILayout.Toggle("Pickups", _createPickups);
_createEnvironment = EditorGUILayout.Toggle("Environment", _createEnvironment);
_createPuzzleSlots = EditorGUILayout.Toggle("Puzzle Slots", _createPuzzleSlots);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Icon Assignment Options", EditorStyles.miniBoldLabel);
_overwriteExistingIcons = EditorGUILayout.Toggle("Overwrite Existing Icons", _overwriteExistingIcons);
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Create / Update ItemDataSO Assets", GUILayout.Height(32f)))
CreateOrUpdateItemAssets();
if (GUILayout.Button("Assign Icons To Existing ItemDataSOs", GUILayout.Height(32f)))
AssignIconsToExistingItemAssets();
}
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Reveal Items Folder", GUILayout.Height(28f)))
RevealFolder(_itemRootFolder);
if (GUILayout.Button("Reveal Icons Folder", GUILayout.Height(28f)))
RevealFolder(_iconFolder);
}
}
private void DrawAssetEntrySection()
{
EditorGUILayout.LabelField("AssetEntry", EditorStyles.boldLabel);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Folders", EditorStyles.miniBoldLabel);
_assetEntryRootFolder = EditorGUILayout.TextField("AssetEntry Root Folder", _assetEntryRootFolder);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Generate AssetEntry Groups", EditorStyles.miniBoldLabel);
_createUIAssetEntries = EditorGUILayout.Toggle("UI Entries", _createUIAssetEntries);
_createSceneAssetEntries = EditorGUILayout.Toggle("Scene Entries", _createSceneAssetEntries);
_createLevelAssetEntries = EditorGUILayout.Toggle("Level Entries", _createLevelAssetEntries);
_createItemAssetEntries = EditorGUILayout.Toggle("Item Entries", _createItemAssetEntries);
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Create / Update AssetEntry Assets", GUILayout.Height(32f)))
CreateOrUpdateAssetEntries();
if (GUILayout.Button("Reveal AssetEntry Folder", GUILayout.Height(32f)))
RevealFolder(_assetEntryRootFolder);
}
}
private void DrawCodexSection()
{
EditorGUILayout.LabelField("CodexEntrySO", EditorStyles.boldLabel);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Folders", EditorStyles.miniBoldLabel);
_codexRootFolder = EditorGUILayout.TextField("Codex Root Folder", _codexRootFolder);
EditorGUILayout.Space(4f);
EditorGUILayout.LabelField("Generate Codex Groups", EditorStyles.miniBoldLabel);
_createBookEntries = EditorGUILayout.Toggle("Book Entries", _createBookEntries);
_createPuzzleClues = EditorGUILayout.Toggle("Puzzle Clues", _createPuzzleClues);
_createPhotos = EditorGUILayout.Toggle("Photos", _createPhotos);
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Create / Update CodexEntrySO Assets", GUILayout.Height(32f)))
CreateOrUpdateCodexEntries();
if (GUILayout.Button("Reveal Codex Folder", GUILayout.Height(32f)))
RevealFolder(_codexRootFolder);
}
}
private void CreateOrUpdateItemAssets()
{
if (!IsValidAssetsPath(_itemRootFolder))
{
Debug.LogError("[Content Generator] Items Root Folder must start with 'Assets'.");
return;
}
EnsureFolderExists(_itemRootFolder);
int created = 0;
int updated = 0;
int skipped = 0;
if (_createPickups)
{
ProcessEnum<ItemKey, ItemDataSo>(
rootFolder: _itemRootFolder,
folderName: "Pickups",
noneValue: ItemKey.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<ItemDataSo>(path),
createAsset: () => CreateInstance<ItemDataSo>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_idType", (int)ItemDataSo.ItemIdType.Pickup);
SetEnumField(so, "_itemKey", (int)key);
SetEnumField(so, "_environmentKey", (int)EnvironmentKey.None);
SetEnumField(so, "_puzzleSlotKey", (int)PuzzleSlotKey.None);
SetStringFieldIfEmpty(so, "_itemName", ObjectNames.NicifyVariableName(key.ToString()));
if (_assignIconsDuringCreate)
AssignIcon(so, key.ToString(), _overwriteExistingIcons);
},
ref created,
ref updated,
ref skipped);
}
if (_createEnvironment)
{
ProcessEnum<EnvironmentKey, ItemDataSo>(
rootFolder: _itemRootFolder,
folderName: "Environment",
noneValue: EnvironmentKey.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<ItemDataSo>(path),
createAsset: () => CreateInstance<ItemDataSo>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_idType", (int)ItemDataSo.ItemIdType.Environment);
SetEnumField(so, "_itemKey", (int)ItemKey.None);
SetEnumField(so, "_environmentKey", (int)key);
SetEnumField(so, "_puzzleSlotKey", (int)PuzzleSlotKey.None);
SetStringFieldIfEmpty(so, "_itemName", ObjectNames.NicifyVariableName(key.ToString()));
if (_assignIconsDuringCreate)
AssignIcon(so, key.ToString(), _overwriteExistingIcons);
},
ref created,
ref updated,
ref skipped);
}
if (_createPuzzleSlots)
{
ProcessEnum<PuzzleSlotKey, ItemDataSo>(
rootFolder: _itemRootFolder,
folderName: "PuzzleSlots",
noneValue: PuzzleSlotKey.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<ItemDataSo>(path),
createAsset: () => CreateInstance<ItemDataSo>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_idType", (int)ItemDataSo.ItemIdType.PuzzleSlot);
SetEnumField(so, "_itemKey", (int)ItemKey.None);
SetEnumField(so, "_environmentKey", (int)EnvironmentKey.None);
SetEnumField(so, "_puzzleSlotKey", (int)key);
SetStringFieldIfEmpty(so, "_itemName", ObjectNames.NicifyVariableName(key.ToString()));
if (_assignIconsDuringCreate)
AssignIcon(so, key.ToString(), _overwriteExistingIcons);
},
ref created,
ref updated,
ref skipped);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"[Content Generator] ItemDataSO generation complete. Created: {created}, Updated: {updated}, Skipped: {skipped}");
}
private void AssignIconsToExistingItemAssets()
{
if (!IsValidAssetsPath(_iconFolder))
{
Debug.LogError("[Content Generator] Icons Folder must start with 'Assets'.");
return;
}
if (!AssetDatabase.IsValidFolder(_iconFolder))
{
Debug.LogError($"[Content Generator] Icons folder not found: {_iconFolder}");
return;
}
string[] guids = AssetDatabase.FindAssets("t:ItemDataSo", new[] { _itemRootFolder });
int assigned = 0;
int skipped = 0;
int missing = 0;
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath<ItemDataSo>(assetPath);
if (asset == null)
continue;
var so = new SerializedObject(asset);
so.Update();
bool changed = AssignIcon(so, asset.name, _overwriteExistingIcons);
if (changed)
{
so.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(asset);
assigned++;
}
else
{
var iconProp = so.FindProperty("_icon");
if (iconProp != null && iconProp.objectReferenceValue != null && !_overwriteExistingIcons)
skipped++;
else
missing++;
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"[Content Generator] Icon assignment complete. Assigned: {assigned}, Skipped: {skipped}, Missing Icons: {missing}");
}
private void CreateOrUpdateAssetEntries()
{
if (!IsValidAssetsPath(_assetEntryRootFolder))
{
Debug.LogError("[Content Generator] AssetEntry Root Folder must start with 'Assets'.");
return;
}
EnsureFolderExists(_assetEntryRootFolder);
int created = 0;
int updated = 0;
int skipped = 0;
if (_createUIAssetEntries)
{
ProcessEnum<UIKey, AssetEntry>(
rootFolder: _assetEntryRootFolder,
folderName: "UI",
noneValue: UIKey.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<AssetEntry>(path),
createAsset: () => CreateInstance<AssetEntry>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_entryType", (int)AssetEntry.AssetEntryType.UI);
SetEnumField(so, "_uiKey", (int)key);
SetEnumField(so, "_sceneKey", (int)SceneKey.None);
SetEnumField(so, "_levelKey", (int)LevelKey.None);
SetEnumField(so, "_itemKey", (int)AssetItemKey.None);
},
ref created,
ref updated,
ref skipped);
}
if (_createSceneAssetEntries)
{
ProcessEnum<SceneKey, AssetEntry>(
rootFolder: _assetEntryRootFolder,
folderName: "Scenes",
noneValue: SceneKey.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<AssetEntry>(path),
createAsset: () => CreateInstance<AssetEntry>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_entryType", (int)AssetEntry.AssetEntryType.Scene);
SetEnumField(so, "_uiKey", (int)UIKey.None);
SetEnumField(so, "_sceneKey", (int)key);
SetEnumField(so, "_levelKey", (int)LevelKey.None);
SetEnumField(so, "_itemKey", (int)AssetItemKey.None);
},
ref created,
ref updated,
ref skipped);
}
if (_createLevelAssetEntries)
{
ProcessEnum<LevelKey, AssetEntry>(
rootFolder: _assetEntryRootFolder,
folderName: "Levels",
noneValue: LevelKey.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<AssetEntry>(path),
createAsset: () => CreateInstance<AssetEntry>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_entryType", (int)AssetEntry.AssetEntryType.Level);
SetEnumField(so, "_uiKey", (int)UIKey.None);
SetEnumField(so, "_sceneKey", (int)SceneKey.None);
SetEnumField(so, "_levelKey", (int)key);
SetEnumField(so, "_itemKey", (int)AssetItemKey.None);
},
ref created,
ref updated,
ref skipped);
}
if (_createItemAssetEntries)
{
ProcessEnum<AssetItemKey, AssetEntry>(
rootFolder: _assetEntryRootFolder,
folderName: "Items",
noneValue: AssetItemKey.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<AssetEntry>(path),
createAsset: () => CreateInstance<AssetEntry>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_entryType", (int)AssetEntry.AssetEntryType.Item);
SetEnumField(so, "_uiKey", (int)UIKey.None);
SetEnumField(so, "_sceneKey", (int)SceneKey.None);
SetEnumField(so, "_levelKey", (int)LevelKey.None);
SetEnumField(so, "_itemKey", (int)key);
},
ref created,
ref updated,
ref skipped);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"[Content Generator] AssetEntry generation complete. Created: {created}, Updated: {updated}, Skipped: {skipped}");
}
private void CreateOrUpdateCodexEntries()
{
if (!IsValidAssetsPath(_codexRootFolder))
{
Debug.LogError("[Content Generator] Codex Root Folder must start with 'Assets'.");
return;
}
EnsureFolderExists(_codexRootFolder);
int created = 0;
int updated = 0;
int skipped = 0;
if (_createBookEntries)
{
ProcessEnum<BookEntryID, CodexEntrySo>(
rootFolder: _codexRootFolder,
folderName: "Books",
noneValue: BookEntryID.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<CodexEntrySo>(path),
createAsset: () => CreateInstance<CodexEntrySo>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_codexType", (int)CodexType.BookEntry);
SetEnumField(so, "_bookEntryID", (int)key);
SetEnumField(so, "_clueEntryID", (int)ClueEntryID.None);
SetEnumField(so, "_photoEntryID", (int)PhotoEntryID.None);
SetStringFieldIfEmpty(so, "_title", ObjectNames.NicifyVariableName(key.ToString()));
},
ref created,
ref updated,
ref skipped);
}
if (_createPuzzleClues)
{
ProcessEnum<ClueEntryID, CodexEntrySo>(
rootFolder: _codexRootFolder,
folderName: "Clues",
noneValue: ClueEntryID.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<CodexEntrySo>(path),
createAsset: () => CreateInstance<CodexEntrySo>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_codexType", (int)CodexType.PuzzleClue);
SetEnumField(so, "_bookEntryID", (int)BookEntryID.None);
SetEnumField(so, "_clueEntryID", (int)key);
SetEnumField(so, "_photoEntryID", (int)PhotoEntryID.None);
SetStringFieldIfEmpty(so, "_title", ObjectNames.NicifyVariableName(key.ToString()));
},
ref created,
ref updated,
ref skipped);
}
if (_createPhotos)
{
ProcessEnum<PhotoEntryID, CodexEntrySo>(
rootFolder: _codexRootFolder,
folderName: "Photos",
noneValue: PhotoEntryID.None,
assetExtensionName: key => key.ToString(),
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<CodexEntrySo>(path),
createAsset: () => CreateInstance<CodexEntrySo>(),
applyValues: (so, key) =>
{
SetEnumField(so, "_codexType", (int)CodexType.Photo);
SetEnumField(so, "_bookEntryID", (int)BookEntryID.None);
SetEnumField(so, "_clueEntryID", (int)ClueEntryID.None);
SetEnumField(so, "_photoEntryID", (int)key);
SetStringFieldIfEmpty(so, "_title", ObjectNames.NicifyVariableName(key.ToString()));
},
ref created,
ref updated,
ref skipped);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"[Content Generator] CodexEntrySO generation complete. Created: {created}, Updated: {updated}, Skipped: {skipped}");
}
private void ProcessEnum<TEnum, TAsset>(
string rootFolder,
string folderName,
TEnum noneValue,
Func<TEnum, string> assetExtensionName,
Func<string, TAsset> loadExistingAsset,
Func<TAsset> createAsset,
Action<SerializedObject, TEnum> applyValues,
ref int created,
ref int updated,
ref int skipped)
where TEnum : struct, Enum
where TAsset : UnityEngine.Object
{
string folderPath = $"{rootFolder}/{folderName}";
EnsureFolderExists(folderPath);
foreach (TEnum key in Enum.GetValues(typeof(TEnum)))
{
if (EqualityComparer<TEnum>.Default.Equals(key, noneValue))
continue;
string assetName = assetExtensionName(key);
string assetPath = $"{folderPath}/{assetName}.asset";
var existing = loadExistingAsset(assetPath);
if (existing != null && !_overwriteExisting)
{
skipped++;
continue;
}
TAsset asset;
bool isNew = false;
if (existing == null)
{
asset = createAsset();
AssetDatabase.CreateAsset(asset, assetPath);
isNew = true;
}
else
{
asset = existing;
}
var so = new SerializedObject(asset);
so.Update();
applyValues(so, key);
so.ApplyModifiedPropertiesWithoutUndo();
EditorUtility.SetDirty(asset);
if (isNew)
created++;
else
updated++;
}
}
private bool AssignIcon(SerializedObject so, string assetOrEnumName, bool overwriteExisting)
{
var iconProp = so.FindProperty("_icon");
if (iconProp == null)
return false;
if (!overwriteExisting && iconProp.objectReferenceValue != null)
return false;
Sprite sprite = FindIconByName(assetOrEnumName);
if (sprite == null)
return false;
iconProp.objectReferenceValue = sprite;
return true;
}
private Sprite FindIconByName(string iconName)
{
if (!AssetDatabase.IsValidFolder(_iconFolder))
return null;
string[] guids = AssetDatabase.FindAssets($"{iconName} t:Sprite", new[] { _iconFolder });
if (guids == null || guids.Length == 0)
return null;
string path = AssetDatabase.GUIDToAssetPath(guids[0]);
return AssetDatabase.LoadAssetAtPath<Sprite>(path);
}
private static void SetEnumField(SerializedObject so, string propertyName, int enumValue)
{
var prop = so.FindProperty(propertyName);
if (prop != null)
prop.enumValueIndex = enumValue;
}
private static void SetStringFieldIfEmpty(SerializedObject so, string propertyName, string value)
{
var prop = so.FindProperty(propertyName);
if (prop == null)
return;
if (string.IsNullOrWhiteSpace(prop.stringValue))
prop.stringValue = value;
}
private static bool IsValidAssetsPath(string path)
{
return !string.IsNullOrWhiteSpace(path) && path.StartsWith("Assets", StringComparison.Ordinal);
}
private static void EnsureFolderExists(string fullPath)
{
string[] parts = fullPath.Split('/');
if (parts.Length == 0 || parts[0] != "Assets")
throw new InvalidOperationException($"Invalid Unity folder path: {fullPath}");
string current = "Assets";
for (int i = 1; i < parts.Length; i++)
{
string next = $"{current}/{parts[i]}";
if (!AssetDatabase.IsValidFolder(next))
AssetDatabase.CreateFolder(current, parts[i]);
current = next;
}
}
private static void RevealFolder(string folderPath)
{
if (!IsValidAssetsPath(folderPath))
return;
EnsureFolderExists(folderPath);
UnityEngine.Object folder = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(folderPath);
if (folder != null)
{
Selection.activeObject = folder;
EditorGUIUtility.PingObject(folder);
}
}
}
}