I never have done this before, but I would like to add a small update following my last Dev Log post since i shared a version of my recent code that was not fully doing what it was intended to do.
“Current build clears previews in most cases, but still leaves generated slot blocks in some edge cases. For now, I’m manually resetting the container before returning to my base prefab. This is a known limitation, and the next iteration will focus on making the reset bulletproof.”
The final code I shared previously was working upon initial early test, and I was too eager to log my process, prematurely, upon further testing showed it was still not totally accurate to what I described.
Here is a updated version of that same code, with a method to stop actual runtime testing, when the simulated runtime testing inside of editor mode, leaves traces of item data that should not remain. To stop any possible cross contamination of data and causing any unforeseen errors.
I also added a "Nuke" option to force reset it back to a previous state before the generation was initiated.
This version keeps all the existing logic, but now also:
Clears the Inspector selection before destroying anything.
Caches the list of objects to destroy, clears the tracking list, then destroys them — so there’s no double‑destroy or hierarchy modification during iteration.
Null‑checks before destroying in case Unity has already started tearing something down.
Assertion fix: Selection.activeObject = null + cache/clear/destroy pattern means you can click Generate Preview without Unity complaining about destroying selected objects.
No double‑destroy: spawnedBlocks is cleared before destruction.
Still restores originals when restoreOriginals is true.
It was still leaving the remnant game objects in my editor screen, so i created a editor tool that appears in my unity windows tabs , under tools/inventory/resetslotblockcontainer
this tool when selected automatically clears the children objects of my SlotBlockContainer and regenerates it with a single instance of my Prefab SlotBlock which is exactly what is required.
UPDATED CODE BLOCK -
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.UI;
#endif
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
[ExecuteAlways]
public class RuntimeEditor : MonoBehaviour
{
[Header("RuntimeEditor Test")]
public bool active = true;
[Header("Preview Settings")]
public bool previewInEditor = true;
[Tooltip("Scroll amount for Page Up/Down (0–1 range for ScrollRect)")]
public float pageScrollAmount = 0.2f;
[Header("References")]
public PlayerInventoryManager inventoryManager;
public ScrollRect scrollRect;
#if UNITY_EDITOR
private readonly List<GameObject> originalBlocks = new List<GameObject>();
private readonly List<GameObject> spawnedBlocks = new List<GameObject>();
private List<GearItem> originalEquippedGear = new List<GearItem>();
private readonly List<(GearEquipSlotEnum slot, string displayName)> testItems =
new List<(GearEquipSlotEnum, string)>
{
(GearEquipSlotEnum.Backpack, "Small Paramedic Backpack"),
(GearEquipSlotEnum.Face, "Fisherman's Face Cover"),
(GearEquipSlotEnum.Head, "Cowboy Hat"),
(GearEquipSlotEnum.Eyewear, "Tanker Goggles"),
(GearEquipSlotEnum.Neck, "Winter Neck Warmer"),
(GearEquipSlotEnum.Torso_Inner,"Plain White Cotton Shirt"),
(GearEquipSlotEnum.Torso_Outer,"Faded Denim Jacket"),
(GearEquipSlotEnum.Vest, "Duck Hunter's Vest"),
(GearEquipSlotEnum.Wrist, "Red Sweatband"),
(GearEquipSlotEnum.Legs, "Faded Denim Jeans"),
(GearEquipSlotEnum.Feet_Inner, "NBC Inner Footliner"),
(GearEquipSlotEnum.Feet_Outer, "Firefighter Boots"),
(GearEquipSlotEnum.Gloves, "Firefighter Heat Resistant Gloves"),
(GearEquipSlotEnum.Belt, "Firefighter Safety Belt")
};
private void CacheOriginalState()
{
originalBlocks.Clear();
foreach (Transform child in inventoryManager.SlotBlockContainer)
originalBlocks.Add(child.gameObject);
originalEquippedGear = new List<GearItem>(inventoryManager.EquippedGear);
}
private void HideOriginalBlocks()
{
foreach (var block in originalBlocks)
if (block != null) block.SetActive(false);
}
private void ShowOriginalBlocks()
{
foreach (var block in originalBlocks)
if (block != null) block.SetActive(true);
}
public void GeneratePreview()
{
if (inventoryManager == null)
{
Debug.LogWarning("⚠️ Assign PlayerInventoryManager.");
return;
}
if (originalBlocks.Count == 0 && originalEquippedGear.Count == 0)
CacheOriginalState();
HideOriginalBlocks();
ClearPreview(false); // safe clear before generating
if (!active) return;
string[] guids = AssetDatabase.FindAssets("t:GearItem", new[] { "Assets/GearAssets" });
var allItems = guids
.Select(guid => AssetDatabase.LoadAssetAtPath<GearItem>(AssetDatabase.GUIDToAssetPath(guid)))
.Where(item => item != null)
.ToList();
var previewItems = new List<GearItem>();
foreach (var (slot, displayName) in testItems)
{
var chosen = allItems.FirstOrDefault(i => i.DisplayName == displayName);
if (chosen != null)
previewItems.Add(chosen);
else
Debug.LogWarning($"[RuntimeEditor] Missing: '{displayName}'");
}
inventoryManager.EquippedGear = previewItems;
inventoryManager.GenerateSlotBlocks();
foreach (Transform child in inventoryManager.SlotBlockContainer)
{
child.gameObject.hideFlags = HideFlags.DontSave;
spawnedBlocks.Add(child.gameObject);
}
Debug.Log($"✅ Preview generated with {spawnedBlocks.Count} blocks.");
}
public void ClearPreview(bool restoreOriginals = true)
{
#if UNITY_EDITOR
// ✅ Deselect to avoid inspector errors
Selection.activeObject = null;
#endif
// ✅ Cache and clear list first
var toDestroy = new List<GameObject>(spawnedBlocks);
spawnedBlocks.Clear();
// ✅ Destroy safely
foreach (var go in toDestroy)
{
if (go != null)
DestroyImmediate(go);
}
if (restoreOriginals)
{
inventoryManager.EquippedGear = new List<GearItem>(originalEquippedGear);
ShowOriginalBlocks();
inventoryManager.GenerateSlotBlocks();
}
#if UNITY_EDITOR
EditorApplication.DirtyHierarchyWindowSorting();
EditorApplication.RepaintHierarchyWindow();
#endif
}
private void OnDisable()
{
if (!Application.isPlaying)
ClearPreview(true);
}
private void OnDestroy()
{
if (!Application.isPlaying)
ClearPreview(true);
}
private void OnApplicationPlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingEditMode)
{
ClearPreview(true);
}
}
public void PageUp()
{
if (scrollRect != null)
scrollRect.verticalNormalizedPosition = Mathf.Clamp01(scrollRect.verticalNormalizedPosition + pageScrollAmount);
}
public void PageDown()
{
if (scrollRect != null)
scrollRect.verticalNormalizedPosition = Mathf.Clamp01(scrollRect.verticalNormalizedPosition - pageScrollAmount);
}
public IEnumerable<string> GetGeneratedSlotNames()
{
foreach (var (slot, displayName) in testItems)
yield return $"{slot} → {displayName}";
yield return "Hands → (Skipped)";
}
#endif
private void OnEnable()
{
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += OnApplicationPlayModeChanged;
if (!Application.isPlaying && previewInEditor)
GeneratePreview();
#endif
}
private void OnDisableEditorHooks()
{
#if UNITY_EDITOR
EditorApplication.playModeStateChanged -= OnApplicationPlayModeChanged;
#endif
}
private void Update()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
if (active && spawnedBlocks.Count == 0)
GeneratePreview();
else if (!active && spawnedBlocks.Count > 0)
ClearPreview(true);
}
#endif
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(RuntimeEditor))]
public class RuntimeEditorInspector : Editor
{
public override void OnInspectorGUI()
{
RuntimeEditor script = (RuntimeEditor)target;
script.active = EditorGUILayout.Toggle("RuntimeEditor Test - Active", script.active);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Preview Controls", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Generate Preview"))
script.GeneratePreview();
if (GUILayout.Button("Clear Preview"))
script.ClearPreview(true);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Paging", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("⬆️ Page Up")) script.PageUp();
if (GUILayout.Button("⬇️ Page Down")) script.PageDown();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Generated Slots Overview", EditorStyles.boldLabel);
EditorGUI.BeginDisabledGroup(true);
foreach (var line in script.GetGeneratedSlotNames())
EditorGUILayout.LabelField(line);
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
if (GUILayout.Button("🗑 Full Reset (Clear Preview & Restore Originals)"))
{
script.ClearPreview(true);
Debug.Log("🔄 RuntimeEditor: Full Reset executed.");
}
EditorGUILayout.Space();
DrawDefaultInspector();
}
}
#endif
TOOL CODING
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.Collections.Generic;
public static class SlotBlockContainerReset
{
private const string PrefabPath = "Assets/Prefabs/SlotBlockPrefab.prefab"; // <-- Change to your actual prefab path
[MenuItem("Tools/Inventory/Reset SlotBlockContainer")]
public static void ResetSlotBlockContainer()
{
var container = GameObject.Find("SlotBlockContainer");
if (container == null)
{
EditorUtility.DisplayDialog(
"Reset SlotBlockContainer",
"❌ Could not find a GameObject named 'SlotBlockContainer' in the active scene.",
"OK");
return;
}
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
if (prefab == null)
{
EditorUtility.DisplayDialog(
"Reset SlotBlockContainer",
$"❌ Could not load SlotBlockPrefab from {PrefabPath}.\nPlease adjust the path in the script.",
"OK");
return;
}
if (!EditorUtility.DisplayDialog(
"Reset SlotBlockContainer",
"This will delete ALL children of SlotBlockContainer and replace them with SlotBlockPrefab.\n\nProceed?",
"Yes, Reset",
"Cancel"))
{
return;
}
// ✅ Deselect to avoid inspector errors
Selection.activeObject = null;
// ✅ Cache children first
var children = new List<GameObject>();
foreach (Transform child in container.transform)
{
if (child != null)
children.Add(child.gameObject);
}
// ✅ Destroy cached children
foreach (var child in children)
{
if (child != null)
Object.DestroyImmediate(child);
}
// ✅ Instantiate prefab as only child
var newBlock = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
newBlock.transform.SetParent(container.transform, false);
// ✅ Mark scene dirty so changes can be saved
EditorSceneManager.MarkSceneDirty(container.scene);
Debug.Log("✅ SlotBlockContainer reset complete — all children removed and SlotBlockPrefab added.");
}
}
Overall, a huge improvement and a much better version of this code now, from Dev Log 16.
TLDR - Fixed the code and updated here in a secondary log update, with improvements.
Next update will likely be in a few days. Hopefully will have the fully working inventory system implemented for the actual gameplay by then. Wishing myself good luck.
Top comments (0)