Creating an InventoryItem Editor Part 3
We’re going to add a GUIStyle to each section to bring the margins of the content in just a touch, but leave the foldout lines the way they are. For each of the modifiers in the list, we’ll get a reference to the modifier, and begin a Horizontal Layout Group. Now we can make our helper methods. cs.
This is the third post in my series on creating an InventoryItem Editor in Unity that will edit any child of InventoryItem. If you're not caught up, you can read the first post here, and the second post here.
A Better Way to Manage Lists
Probably the thing I hate the most about Unity's built in inspectors is the way it manages Lists and Arrays. Editing a list in the inspector is a pain, especially if that list has any structure to it like the Modifier structure used in StatsEquipableItem.

By the end of our lesson, our InventoryEditorWindow's lists will look like this:

We're going to start by making our Setters. In the first few iterations of this inspector, I actually created setters for each of Additive and Percentage modifiers. That meant a separate SetAdditiveStat, SetPercentageStat, RemoveAdditiveStat, RemovePercentageStat, etc. These methods started to pile up very quickly. This time around, I decided to structure things a bit differently by passing the actual list for the setters to work on.
The first thing we need to do is to change our declarations at the top of StatsEquipableItem.cs. We're going to change the Arrays to Lists. Why? Because when you're editing, Lists are an order of magnitude easier to work with than Arrays are.
[SerializeField] List<Modifier> additiveModifiers = new List<Modifier>();
[SerializeField] List<Modifier> percentageModifiers = new List<Modifier>();
Don't worry, you can make this change and not lose any data in our ScriptableObjects because behind the scenes Lists and Arrays are just the same thing, but Lists have better functions for managing the contents.
Now we can make our helper methods. Remember, all of the rest of the code must be within an #if UNITY_EDITOR/#endif block or you won't be able to build your project.
void AddModifier(List<Modifier> modifierList)
{
SetUndo("Add Modifier");
modifierList?.Add(new Modifier());
Dirty();
}
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">RemoveModifier</span>(<span class="hljs-params">List<Modifier>modifierList, <span class="hljs-built_in">int</span> index</span>)</span>
{
SetUndo(<span class="hljs-string">"Remove Modifier"</span>);
modifierList?.RemoveAt(index);
Dirty();
}
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">SetStat</span>(<span class="hljs-params">List<Modifier> modifierList, <span class="hljs-built_in">int</span> i, Stat stat</span>)</span>
{
<span class="hljs-keyword">if</span> (modifierList[i].stat == stat) <span class="hljs-keyword">return</span>;
SetUndo(<span class="hljs-string">"Change Modifier Stat"</span>);
Modifier mod = modifierList[i];
mod.stat = stat;
modifierList[i] = mod;
Dirty();
}</div></code><button aria-label="Copy code" aria-pressed="false" class="flex justify-center group items-center outline-hidden ring-1 focus-visible:ring-gray-50 hover:bg-[#171b28] rounded-xs fill-gray-300 focus-visible:fill-gray-50 hover:fill-gray-50 disabled:opacity-50 cursor-pointer disabled:cursor-auto select-none selection:bg-transparent absolute right-4 top-4 h-8 w-8"><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="group-aria-pressed:hidden"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-520q0-17 11.5-28.5T160-720q17 0 28.5 11.5T200-680v520h400q17 0 28.5 11.5T640-120q0 17-11.5 28.5T600-80H200Zm160-240v-480 480Z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="hidden group-aria-pressed:block"><path d="m382-354 339-339q12-12 28-12t28 12q12 12 12 28.5T777-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143Z"></path></svg></button></pre><p>The handy thing about objects (like List) is that they are passed between functions as reference automatically. This means that if you pass a List to a function expecting a List, it will receive the pointer to your original list. This means we can modify the list that’s passed to us in a method and the modifications will hold.<br>There are still restrictions, however, like you <em>cannot add or remove items</em> from the list in a For loop.</p><p>Next, we need to iterate over that list and show our fields…</p><pre class="flex flex-row w-full items-start justify-between !p-5 relative break-words whitespace-break-spaces"><code class="hljs language-csharp flex flex-col w-full !pr-11 break-word"><div style="white-space: inherit;"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">DrawModifierList</span>(<span class="hljs-params">List<Modifier> modifierList</span>)</span>
{
<span class="hljs-built_in">int</span> modifierToDelete = <span class="hljs-number">-1</span>;
GUIContent statLabel = <span class="hljs-keyword">new</span> GUIContent(<span class="hljs-string">"Stat"</span>);
<span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i < modifierList.Count; i++)
{
Modifier modifier = modifierList[i];
EditorGUILayout.BeginHorizontal();
SetStat(modifierList, i, (Stat) EditorGUILayout.EnumPopup(statLabel, modifier.stat, IsStatSelectable, <span class="hljs-literal">false</span>));
SetValue(modifierList, i, EditorGUILayout.IntSlider(<span class="hljs-string">"Value"</span>, (<span class="hljs-built_in">int</span>) modifier.<span class="hljs-keyword">value</span>, <span class="hljs-number">-20</span>, <span class="hljs-number">100</span>));
<span class="hljs-keyword">if</span> (GUILayout.Button(<span class="hljs-string">"-"</span>))
{
modifierToDelete = i;
}
EditorGUILayout.EndHorizontal();
}
<span class="hljs-keyword">if</span> (modifierToDelete > <span class="hljs-number">-1</span>)
{
RemoveModifier(modifierList, modifierToDelete);
}
<span class="hljs-keyword">if</span> (GUILayout.Button(<span class="hljs-string">"Add Modifier"</span>))
{
AddModifier(modifierList);
}
}
<span class="hljs-function"><span class="hljs-built_in">bool</span> <span class="hljs-title">IsStatSelectable</span>(<span class="hljs-params">Enum candidate</span>)</span>
{
Stat stat = (Stat) candidate;
<span class="hljs-keyword">if</span> (stat == Stat.ExperienceReward || stat == Stat.ExperienceToLevelUp) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}</div></code><button aria-label="Copy code" aria-pressed="false" class="flex justify-center group items-center outline-hidden ring-1 focus-visible:ring-gray-50 hover:bg-[#171b28] rounded-xs fill-gray-300 focus-visible:fill-gray-50 hover:fill-gray-50 disabled:opacity-50 cursor-pointer disabled:cursor-auto select-none selection:bg-transparent absolute right-4 top-4 h-8 w-8"><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="group-aria-pressed:hidden"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-520q0-17 11.5-28.5T160-720q17 0 28.5 11.5T200-680v520h400q17 0 28.5 11.5T640-120q0 17-11.5 28.5T600-80H200Zm160-240v-480 480Z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="hidden group-aria-pressed:block"><path d="m382-354 339-339q12-12 28-12t28 12q12 12 12 28.5T777-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143Z"></path></svg></button></pre><p>We start with a temporary variable modifierToDelete set to -1. The logic is as follows… if we want to delete an element, we’ll set modifierToDelete to the index of the modifer. At the end of the for loop, we’ll check to see if the number is greater than -1 and act accordingly.</p><p>We’re using a standard for loop with an indexer instead of a foreach loop. For each of the modifiers in the list, we’ll get a reference to the modifier, and begin a Horizontal Layout Group. This will put everything for our modifiers on one line, space divided between the elements.<br>First we’ll draw our StatPopup, then the value, then finally a button for deleting the element. I used an IntSlider for the values, but you could just as easily use a regular slider. Just be aware that floats look terrible in tooltips.</p><p>Notice that with the Stat enumpopup, I once again included a validation method. This lets us filter out stats that don’t make sense to add modifiers to, specifically ExperienceReward and ExperienceToLevelUp. Those two options will be greyed out in the popup.</p><p>This leaves our actual DrawCustomInspector() method.</p><pre class="flex flex-row w-full items-start justify-between !p-5 relative break-words whitespace-break-spaces"><code class="hljs language-csharp flex flex-col w-full !pr-11 break-word"><div style="white-space: inherit;"> <span class="hljs-built_in">bool</span> drawStatsEquipableItemData = <span class="hljs-literal">true</span>;
<span class="hljs-built_in">bool</span> drawAdditive = <span class="hljs-literal">true</span>;
<span class="hljs-built_in">bool</span> drawPercentage = <span class="hljs-literal">true</span>;
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">DrawCustomInspector</span>()</span>
{
<span class="hljs-keyword">base</span>.DrawCustomInspector();
drawStatsEquipableItemData =
EditorGUILayout.Foldout(drawStatsEquipableItemData, <span class="hljs-string">"StatsEquipableItemData"</span>, foldoutStyle);
<span class="hljs-keyword">if</span> (!drawStatsEquipableItemData) <span class="hljs-keyword">return</span>;
drawAdditive=EditorGUILayout.Foldout(drawAdditive, <span class="hljs-string">"Additive Modifiers"</span>);
<span class="hljs-keyword">if</span> (drawAdditive)
{
DrawModifierList(additiveModifiers);
}
drawPercentage = EditorGUILayout.Foldout(drawPercentage, <span class="hljs-string">"Percentage Modifiers"</span>);
<span class="hljs-keyword">if</span> (drawPercentage)
{
DrawModifierList(percentageModifiers);
}
}</div></code><button aria-label="Copy code" aria-pressed="false" class="flex justify-center group items-center outline-hidden ring-1 focus-visible:ring-gray-50 hover:bg-[#171b28] rounded-xs fill-gray-300 focus-visible:fill-gray-50 hover:fill-gray-50 disabled:opacity-50 cursor-pointer disabled:cursor-auto select-none selection:bg-transparent absolute right-4 top-4 h-8 w-8"><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="group-aria-pressed:hidden"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-520q0-17 11.5-28.5T160-720q17 0 28.5 11.5T200-680v520h400q17 0 28.5 11.5T640-120q0 17-11.5 28.5T600-80H200Zm160-240v-480 480Z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="hidden group-aria-pressed:block"><path d="m382-354 339-339q12-12 28-12t28 12q12 12 12 28.5T777-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143Z"></path></svg></button></pre><p>I'm using another foldout section for each of the modifier types as well. This lets you tidy up your inspector even more while you're working on it.</p><p>Now our StatsEquipableItemInspector is ready to go, and should look like this:</p><img src="https://strapi.gamedev.tv/uploads/image_2_9e994dc1ff.png" alt="" onload="(()=>{if(this.attributes.height!==null&&this.attributes.height>0){return}window.dispatchEvent(new Event('resize'))})();"><p>Personally, I think this looks a little cluttered, even with the foldouts. Let’s add a little visual polish to make each section a touch more visible. We’re going to add a GUIStyle to each section to bring the margins of the content in just a touch, but leave the foldout lines the way they are.</p><p>In InventoryItem.cs, add a GUIStyle contentStyle to the properties. It should be a protected style, and should be tagged [NonSerialized] or it might appear in the regular inspector.<br>Then in the first line of InventoryItem’s DrawCustomInspector add the following line:</p><pre class="flex flex-row w-full items-start justify-between !p-5 relative break-words whitespace-break-spaces"><code class="hljs language-csharp flex flex-col w-full !pr-11 break-word"><div style="white-space: inherit;">contentStyle = <span class="hljs-keyword">new</span> GUIStyle {padding = <span class="hljs-keyword">new</span> RectOffset(<span class="hljs-number">15</span>, <span class="hljs-number">15</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>)};</div></code><button aria-label="Copy code" aria-pressed="false" class="flex justify-center group items-center outline-hidden ring-1 focus-visible:ring-gray-50 hover:bg-[#171b28] rounded-xs fill-gray-300 focus-visible:fill-gray-50 hover:fill-gray-50 disabled:opacity-50 cursor-pointer disabled:cursor-auto select-none selection:bg-transparent absolute right-4 top-4 h-8 w-8"><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="group-aria-pressed:hidden"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-520q0-17 11.5-28.5T160-720q17 0 28.5 11.5T200-680v520h400q17 0 28.5 11.5T640-120q0 17-11.5 28.5T600-80H200Zm160-240v-480 480Z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="hidden group-aria-pressed:block"><path d="m382-354 339-339q12-12 28-12t28 12q12 12 12 28.5T777-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143Z"></path></svg></button></pre><p>Now for each of the content blocks we've created in the DrawCustomInspector methods, after the if check for the foldouts, inset the following line:</p><pre class="flex flex-row w-full items-start justify-between !p-5 relative break-words whitespace-break-spaces"><code class="hljs language-csharp flex flex-col w-full !pr-11 break-word"><div style="white-space: inherit;">EditorGUILayout.BeginVertical(contentStyle);</div></code><button aria-label="Copy code" aria-pressed="false" class="flex justify-center group items-center outline-hidden ring-1 focus-visible:ring-gray-50 hover:bg-[#171b28] rounded-xs fill-gray-300 focus-visible:fill-gray-50 hover:fill-gray-50 disabled:opacity-50 cursor-pointer disabled:cursor-auto select-none selection:bg-transparent absolute right-4 top-4 h-8 w-8"><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="group-aria-pressed:hidden"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-520q0-17 11.5-28.5T160-720q17 0 28.5 11.5T200-680v520h400q17 0 28.5 11.5T640-120q0 17-11.5 28.5T600-80H200Zm160-240v-480 480Z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="hidden group-aria-pressed:block"><path d="m382-354 339-339q12-12 28-12t28 12q12 12 12 28.5T777-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143Z"></path></svg></button></pre><p>and at the end of the method add</p><pre class="flex flex-row w-full items-start justify-between !p-5 relative break-words whitespace-break-spaces"><code class="flex flex-col w-full !pr-11 break-word"><div style="white-space: inherit;">EditorGUILayout.EndVertical();</div></code><button aria-label="Copy code" aria-pressed="false" class="flex justify-center group items-center outline-hidden ring-1 focus-visible:ring-gray-50 hover:bg-[#171b28] rounded-xs fill-gray-300 focus-visible:fill-gray-50 hover:fill-gray-50 disabled:opacity-50 cursor-pointer disabled:cursor-auto select-none selection:bg-transparent absolute right-4 top-4 h-8 w-8"><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="group-aria-pressed:hidden"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-520q0-17 11.5-28.5T160-720q17 0 28.5 11.5T200-680v520h400q17 0 28.5 11.5T640-120q0 17-11.5 28.5T600-80H200Zm160-240v-480 480Z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" height="20px" width="20px" viewBox="0 -960 960 960" fill="inherit" class="hidden group-aria-pressed:block"><path d="m382-354 339-339q12-12 28-12t28 12q12 12 12 28.5T777-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143Z"></path></svg></button></pre><p>This will bring your content in a small amount on the left and right making things appear a little less cluttered.</p><img src="https://strapi.gamedev.tv/uploads/image_3_d8b97efed5.png" alt="" onload="(()=>{if(this.attributes.height!==null&&this.attributes.height>0){return}window.dispatchEvent(new Event('resize'))})();"><p>We are now at the point where it doesn't matter if it's a WeaponConfig or a StatsInventoryItem, we can see and edit all of the properties in the same Editor Window. </p><p>In the next post, we'll create a simple healing spell/potion that can be dropped in the Actionbar, and set this up for easy editing as well.</p>