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("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( rootFolder: _itemRootFolder, folderName: "Pickups", noneValue: ItemKey.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( rootFolder: _itemRootFolder, folderName: "Environment", noneValue: EnvironmentKey.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( rootFolder: _itemRootFolder, folderName: "PuzzleSlots", noneValue: PuzzleSlotKey.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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(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( rootFolder: _assetEntryRootFolder, folderName: "UI", noneValue: UIKey.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( rootFolder: _assetEntryRootFolder, folderName: "Scenes", noneValue: SceneKey.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( rootFolder: _assetEntryRootFolder, folderName: "Levels", noneValue: LevelKey.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( rootFolder: _assetEntryRootFolder, folderName: "Items", noneValue: AssetItemKey.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( rootFolder: _codexRootFolder, folderName: "Books", noneValue: BookEntryID.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( rootFolder: _codexRootFolder, folderName: "Clues", noneValue: ClueEntryID.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( rootFolder: _codexRootFolder, folderName: "Photos", noneValue: PhotoEntryID.None, assetExtensionName: key => key.ToString(), loadExistingAsset: path => AssetDatabase.LoadAssetAtPath(path), createAsset: () => CreateInstance(), 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( string rootFolder, string folderName, TEnum noneValue, Func assetExtensionName, Func loadExistingAsset, Func createAsset, Action 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.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(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(folderPath); if (folder != null) { Selection.activeObject = folder; EditorGUIUtility.PingObject(folder); } } } }