[Add] Debugger Inventory & small ui fixes

This commit is contained in:
2026-03-05 09:03:19 +07:00
parent d75dad3fa3
commit dc901b9895
10 changed files with 334 additions and 249 deletions
@@ -516,7 +516,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &2533462241018374848
RectTransform:
m_ObjectHideFlags: 0
@@ -740,7 +740,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 703621256034232799, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_SizeDelta.x
value: 122.6
value: 75
objectReference: {fileID: 0}
- target: {fileID: 703621256034232799, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_SizeDelta.y
@@ -776,7 +776,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 703621256034232799, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_AnchoredPosition.x
value: 61.302
value: 37.158817
objectReference: {fileID: 0}
- target: {fileID: 703621256034232799, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -1019,7 +1019,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 703621256034232799, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_SizeDelta.x
value: 77.396
value: 125
objectReference: {fileID: 0}
- target: {fileID: 703621256034232799, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_SizeDelta.y
@@ -1055,7 +1055,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 703621256034232799, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_AnchoredPosition.x
value: -38.698
value: -62.671
objectReference: {fileID: 0}
- target: {fileID: 703621256034232799, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_AnchoredPosition.y
@@ -465,7 +465,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Color: {r: 0.5188679, g: 0.5188679, b: 0.5188679, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -655,7 +655,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Color: {r: 0.2830189, g: 0.2830189, b: 0.2830189, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -1450,15 +1450,15 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontColor.b
value: 0
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontColor.g
value: 0
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontColor.r
value: 0
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontSizeBase
@@ -1466,7 +1466,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontColor32.rgba
value: 4278190080
value: 4294967295
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_VerticalAlignment
@@ -243,7 +243,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Color: {r: 0.7924528, g: 0.7924528, b: 0.7924528, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
+2 -2
View File
@@ -65,7 +65,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Color: {r: 0.4433962, g: 0.4433962, b: 0.4433962, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -323,7 +323,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Color: {r: 0.2924528, g: 0.2924528, b: 0.2924528, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
+8 -5
View File
@@ -18,7 +18,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &133637959725470091
RectTransform:
m_ObjectHideFlags: 0
@@ -38,7 +38,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 39.7049, y: -46.6634}
m_AnchoredPosition: {x: 512.5834, y: 300.2}
m_SizeDelta: {x: 150, y: 150}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &7291137291065553443
@@ -186,7 +186,8 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_text
value: Name
value: "\u0427\u0438\u0441\u0442\u044B\u0439 \u043C\u043D\u043E\u0436\u0438\u0442\u0435\u043B\u044C
x1.5"
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontSize
@@ -327,7 +328,9 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_text
value: Description Text
value: "\u0423\u043C\u043D\u043E\u0436\u0430\u0435\u0442 \u0438\u0442\u043E\u0433\u043E\u0432\u044B\u0439
\u0441\u0447\u0435\u0442 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438
\u043D\u0430 1.5."
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontSize
@@ -476,7 +479,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_text
value: 123
value: 'Price: 260'
objectReference: {fileID: 0}
- target: {fileID: 3472307258458128773, guid: 0829148555ff32841afde9f6193a0b72, type: 3}
propertyPath: m_fontSize
-4
View File
@@ -2041,10 +2041,6 @@ PrefabInstance:
propertyPath: m_Name
value: Game Info View
objectReference: {fileID: 0}
- target: {fileID: 5167299410648281090, guid: 010ebb8b9becf6b45930c5fb7a5c95e0, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
@@ -1,224 +0,0 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using YachtDice.Modifiers.Definition;
namespace YachtDice.Modifiers.Editor
{
public static class ModifierDefinitionValidator
{
[MenuItem("YachtDice/Validate All Modifier Definitions")]
public static void ValidateAll()
{
string[] guids = AssetDatabase.FindAssets("t:ModifierDefinitionSO");
if (guids.Length == 0)
{
Debug.Log("[ModifierValidator] No ModifierDefinitionSO assets found.");
return;
}
var errorCount = 0;
var warnCount = 0;
var usedIds = new Dictionary<string, string>(); // id -> asset path
foreach (var t in guids)
{
var path = AssetDatabase.GUIDToAssetPath(t);
var def = AssetDatabase.LoadAssetAtPath<ModifierDefinition>(path);
if (def == null)
{
Debug.LogError($"[ModifierValidator] Failed to load asset at {path}");
errorCount++;
continue;
}
// ── Id checks ────────────────────────────────────────
if (string.IsNullOrWhiteSpace(def.Id))
{
Debug.LogError($"[ModifierValidator] {path}: Id is empty.", def);
errorCount++;
}
else if (usedIds.TryGetValue(def.Id, out string existingPath))
{
Debug.LogError($"[ModifierValidator] {path}: Duplicate Id '{def.Id}' (also used by {existingPath}).", def);
errorCount++;
}
else
{
usedIds[def.Id] = path;
}
// ── Economy checks ───────────────────────────────────
if (def.ShopPrice < 0)
{
Debug.LogError($"[ModifierValidator] {path}: ShopPrice is negative ({def.ShopPrice}).", def);
errorCount++;
}
if (def.SellPrice < 0)
{
Debug.LogError($"[ModifierValidator] {path}: SellPrice is negative ({def.SellPrice}).", def);
errorCount++;
}
if (def.SellPrice > def.ShopPrice && def.ShopPrice > 0)
{
Debug.LogWarning($"[ModifierValidator] {path}: SellPrice ({def.SellPrice}) > ShopPrice ({def.ShopPrice}). Infinite money exploit?", def);
warnCount++;
}
// ── Durability checks ────────────────────────────────
if (def.HasLimitedUses && def.MaxUses <= 0)
{
Debug.LogError($"[ModifierValidator] {path}: HasLimitedUses is true but MaxUses is {def.MaxUses}.", def);
errorCount++;
}
if (!def.HasLimitedUses && def.MaxUses > 0)
{
Debug.LogWarning($"[ModifierValidator] {path}: HasLimitedUses is false but MaxUses is {def.MaxUses}. Ignored at runtime.", def);
warnCount++;
}
if (def.MaxStacks < 1)
{
Debug.LogError($"[ModifierValidator] {path}: MaxStacks must be >= 1 (is {def.MaxStacks}).", def);
errorCount++;
}
// ── Behavior checks ──────────────────────────────────
if (def.Behaviors == null || def.Behaviors.Count == 0)
{
Debug.LogWarning($"[ModifierValidator] {path}: No behaviors assigned. Modifier will do nothing.", def);
warnCount++;
continue;
}
for (int b = 0; b < def.Behaviors.Count; b++)
{
var behavior = def.Behaviors[b];
if (behavior == null)
{
Debug.LogError($"[ModifierValidator] {path}: Behavior slot [{b}] is null.", def);
errorCount++;
continue;
}
// Check for null conditions
if (behavior.Conditions != null)
{
for (int c = 0; c < behavior.Conditions.Count; c++)
{
if (behavior.Conditions[c] == null)
{
Debug.LogWarning($"[ModifierValidator] {path}: Behavior '{behavior.name}' has null condition at slot [{c}].", behavior);
warnCount++;
}
}
}
// Check for null or empty effects
if (behavior.Effects == null || behavior.Effects.Count == 0)
{
Debug.LogWarning($"[ModifierValidator] {path}: Behavior '{behavior.name}' has no effects.", behavior);
warnCount++;
}
else
{
for (int e = 0; e < behavior.Effects.Count; e++)
{
if (behavior.Effects[e] == null)
{
Debug.LogError($"[ModifierValidator] {path}: Behavior '{behavior.name}' has null effect at slot [{e}].", behavior);
errorCount++;
}
}
}
}
// ── Display checks ───────────────────────────────────
if (string.IsNullOrWhiteSpace(def.DisplayName))
{
Debug.LogWarning($"[ModifierValidator] {path}: DisplayName is empty.", def);
warnCount++;
}
if (string.IsNullOrWhiteSpace(def.Description))
{
Debug.LogWarning($"[ModifierValidator] {path}: Description is empty.", def);
warnCount++;
}
}
string summary = $"[ModifierValidator] Validated {guids.Length} modifier(s): {errorCount} error(s), {warnCount} warning(s).";
if (errorCount > 0)
Debug.LogError(summary);
else if (warnCount > 0)
Debug.LogWarning(summary);
else
Debug.Log(summary);
}
[MenuItem("YachtDice/Validate Modifier Catalog")]
public static void ValidateCatalog()
{
string[] guids = AssetDatabase.FindAssets("t:ModifierCatalogSO");
if (guids.Length == 0)
{
Debug.LogWarning("[ModifierValidator] No ModifierCatalogSO asset found. Create one via Create > YachtDice/Modifiers/Catalog.");
return;
}
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
var catalog = AssetDatabase.LoadAssetAtPath<ModifierCatalog>(path);
if (catalog == null)
{
Debug.LogError($"[ModifierValidator] Failed to load catalog at {path}");
continue;
}
if (catalog.All == null || catalog.All.Count == 0)
{
Debug.LogWarning($"[ModifierValidator] Catalog at {path} is empty.");
continue;
}
int nullCount = 0;
var catalogIds = new HashSet<string>();
for (int j = 0; j < catalog.All.Count; j++)
{
if (catalog.All[j] == null)
{
nullCount++;
Debug.LogError($"[ModifierValidator] Catalog {path}: Null entry at index [{j}].", catalog);
continue;
}
string id = catalog.All[j].Id;
if (!catalogIds.Add(id))
{
Debug.LogError($"[ModifierValidator] Catalog {path}: Duplicate modifier Id '{id}' in catalog.", catalog);
}
}
Debug.Log($"[ModifierValidator] Catalog {path}: {catalog.All.Count} entries, {nullCount} null.");
}
}
}
}
#endif
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 2f07f633fcb085e49890cec1280b4129
@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using YachtDice.Inventory;
using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Runtime;
namespace YachtDice.Modifiers.Editor
{
public class ModifierInventoryDebugWindow : EditorWindow
{
private const double RepaintIntervalSeconds = 0.5d;
private readonly List<ModifierDefinition> _allDefinitions = new();
private Vector2 _catalogScroll;
private Vector2 _inventoryScroll;
private string _search = string.Empty;
private InventoryController _inventoryController;
private double _nextRepaintTime;
[MenuItem("YachtDice/Debug/Modifier Inventory")]
public static void Open()
{
var window = GetWindow<ModifierInventoryDebugWindow>("Modifier Debug");
window.minSize = new Vector2(700f, 400f);
window.RefreshDefinitions();
window.ResolveInventoryController();
}
private void OnEnable()
{
RefreshDefinitions();
ResolveInventoryController();
EditorApplication.playModeStateChanged += HandlePlayModeStateChanged;
}
private void OnDisable()
{
EditorApplication.playModeStateChanged -= HandlePlayModeStateChanged;
}
private void OnFocus()
{
RefreshDefinitions();
ResolveInventoryController();
}
private void OnHierarchyChange()
{
if (!Application.isPlaying)
return;
ResolveInventoryController();
Repaint();
}
private void Update()
{
if (EditorApplication.timeSinceStartup < _nextRepaintTime)
return;
_nextRepaintTime = EditorApplication.timeSinceStartup + RepaintIntervalSeconds;
if (Application.isPlaying && (_inventoryController == null || _inventoryController.Model == null))
ResolveInventoryController();
Repaint();
}
private void OnGUI()
{
DrawToolbar();
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("Enter Play Mode to edit runtime inventory state.", MessageType.Info);
}
var model = GetInventoryModel();
if (Application.isPlaying && model == null)
{
EditorGUILayout.HelpBox("InventoryController was not found in the scene.", MessageType.Warning);
}
DrawOwnedModifiers(model);
EditorGUILayout.Space(8f);
DrawDefinitionCatalog(model);
}
private void DrawToolbar()
{
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
{
if (GUILayout.Button("Refresh Catalog", EditorStyles.toolbarButton))
RefreshDefinitions();
if (GUILayout.Button("Find Inventory", EditorStyles.toolbarButton))
ResolveInventoryController();
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField("Search", GUILayout.Width(45f));
_search = EditorGUILayout.TextField(_search, GUILayout.Width(220f));
}
}
private void DrawOwnedModifiers(InventoryModel model)
{
EditorGUILayout.LabelField("Owned Modifiers", EditorStyles.boldLabel);
if (model == null)
{
EditorGUILayout.HelpBox("Inventory is unavailable.", MessageType.None);
return;
}
EditorGUILayout.LabelField($"Active slots: {model.ActiveCount}/{model.MaxActiveSlots}");
var snapshot = new List<ModifierInstance>(model.OwnedModifiers);
if (snapshot.Count == 0)
{
EditorGUILayout.HelpBox("Inventory is empty.", MessageType.None);
return;
}
_inventoryScroll = EditorGUILayout.BeginScrollView(_inventoryScroll, GUILayout.MinHeight(140f));
foreach (var instance in snapshot)
{
DrawOwnedModifierRow(model, instance);
}
EditorGUILayout.EndScrollView();
}
private void DrawOwnedModifierRow(InventoryModel model, ModifierInstance instance)
{
var definition = instance.Definition;
if (definition == null)
{
using (new EditorGUILayout.VerticalScope("box"))
{
EditorGUILayout.LabelField("<missing definition>", EditorStyles.boldLabel);
if (GUILayout.Button("Remove", GUILayout.Width(90f)))
model.RemoveModifier(instance);
}
return;
}
var state = instance.IsActive ? "Active" : "Inactive";
var uses = definition.HasLimitedUses
? $"Uses: {instance.RemainingUses}/{definition.MaxUses}"
: "Uses: infinite";
using (new EditorGUILayout.VerticalScope("box"))
{
EditorGUILayout.LabelField(BuildDefinitionLabel(definition), EditorStyles.boldLabel);
EditorGUILayout.LabelField($"State: {state} | {uses}");
using (new EditorGUILayout.HorizontalScope())
{
using (new EditorGUI.DisabledScope(instance.IsActive))
{
if (GUILayout.Button("Activate", GUILayout.Width(90f)))
{
if (!model.TryActivate(instance))
Debug.LogWarning($"[ModifierDebug] Failed to activate '{definition.Id}'. Check slot limits.");
}
}
using (new EditorGUI.DisabledScope(!instance.IsActive))
{
if (GUILayout.Button("Deactivate", GUILayout.Width(90f)))
model.Deactivate(instance);
}
if (GUILayout.Button("Remove", GUILayout.Width(90f)))
model.RemoveModifier(instance);
}
}
}
private void DrawDefinitionCatalog(InventoryModel model)
{
EditorGUILayout.LabelField("All Modifier Definitions", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"Loaded assets: {_allDefinitions.Count}");
if (_allDefinitions.Count == 0)
{
EditorGUILayout.HelpBox("No ModifierDefinition assets found.", MessageType.Warning);
return;
}
_catalogScroll = EditorGUILayout.BeginScrollView(_catalogScroll);
var loweredSearch = string.IsNullOrWhiteSpace(_search)
? string.Empty
: _search.Trim().ToLowerInvariant();
for (var i = 0; i < _allDefinitions.Count; i++)
{
var definition = _allDefinitions[i];
if (definition == null)
continue;
if (!MatchesSearch(definition, loweredSearch))
continue;
DrawDefinitionRow(model, definition);
}
EditorGUILayout.EndScrollView();
}
private void DrawDefinitionRow(InventoryModel model, ModifierDefinition definition)
{
var uses = definition.HasLimitedUses ? $"limited ({definition.MaxUses})" : "permanent";
using (new EditorGUILayout.HorizontalScope("box"))
{
EditorGUILayout.LabelField($"{BuildDefinitionLabel(definition)} | {uses}");
using (new EditorGUI.DisabledScope(model == null))
{
if (GUILayout.Button("Add", GUILayout.Width(90f)))
model.AddModifier(definition);
}
}
}
private static bool MatchesSearch(ModifierDefinition definition, string loweredSearch)
{
if (string.IsNullOrEmpty(loweredSearch))
return true;
var id = definition.Id ?? string.Empty;
var displayName = definition.DisplayName ?? string.Empty;
return id.IndexOf(loweredSearch, StringComparison.OrdinalIgnoreCase) >= 0
|| displayName.IndexOf(loweredSearch, StringComparison.OrdinalIgnoreCase) >= 0;
}
private static string BuildDefinitionLabel(ModifierDefinition definition)
{
var displayName = string.IsNullOrWhiteSpace(definition.DisplayName)
? "<unnamed>"
: definition.DisplayName;
var id = string.IsNullOrWhiteSpace(definition.Id)
? "<no-id>"
: definition.Id;
return $"{displayName} [{id}]";
}
private InventoryModel GetInventoryModel()
{
if (!Application.isPlaying)
return null;
if (_inventoryController == null)
ResolveInventoryController();
return _inventoryController != null ? _inventoryController.Model : null;
}
private void ResolveInventoryController()
{
if (!Application.isPlaying)
{
_inventoryController = null;
return;
}
_inventoryController = FindObjectOfType<InventoryController>();
}
private void RefreshDefinitions()
{
_allDefinitions.Clear();
var guids = AssetDatabase.FindAssets("t:ModifierDefinition");
for (var i = 0; i < guids.Length; i++)
{
var path = AssetDatabase.GUIDToAssetPath(guids[i]);
var definition = AssetDatabase.LoadAssetAtPath<ModifierDefinition>(path);
if (definition != null)
_allDefinitions.Add(definition);
}
_allDefinitions.Sort((a, b) =>
{
var nameComparison = string.Compare(a.DisplayName, b.DisplayName, StringComparison.OrdinalIgnoreCase);
if (nameComparison != 0)
return nameComparison;
return string.Compare(a.Id, b.Id, StringComparison.OrdinalIgnoreCase);
});
}
private void HandlePlayModeStateChanged(PlayModeStateChange change)
{
if (change == PlayModeStateChange.EnteredPlayMode)
ResolveInventoryController();
if (change == PlayModeStateChange.ExitingPlayMode || change == PlayModeStateChange.EnteredEditMode)
_inventoryController = null;
Repaint();
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4a8b096304878c94e83fb2a8c711a969