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.
new List<Modifier>();
[] 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=""><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>