PEGI (Player & Editor GUI) | Unity | Custom Inspectors

Unity's inspectors are pretty good at showing you the state of your MonoBehaviours and ScriptableObjects. But to allow your scripts to be used by others it is often better to provide a user-friendly interface for your fellow co-workers... sure we can also write documentation for our classes and keep it updated as things get changed, but it's objectively more preferable to instead bash one's own brain out with a hammer. Unity lets you make your own interfaces. And as I was making them I would often create a wrapper function to make code shorter. Time went by and I kept adding/removing and changing those wrappers. Hundreds of custom inspectors later the PEGI class is now a big helper.

To demonstrate I'll compare the code I'd need to write before with the one I write now to achieve the same result (A slider that calls a recalculate when changed, and a button with icon right next to it that sets a default value).

Without PEGI wrapper (Standard Unity GUILayout):

var controller = (MyMaterialController )target;

controller .transparency = EditorGUILayout.IntField("Object's Transparency:", controller .transparency); 
if (EditorGUI.EndChangeCheck())
            controller .RecalculateShaderParameters();  
  GUIContent cont = new GUIContent
                tooltip = "Will load default value",
                image = refreshIcon

if (controller .transparency != 0.5 && GUILayout.Button(cont,  GUILayout.MaxWidth(25), GUILayout.MaxHeight(25)))
             controller .transparency = 0.5;

With PEGI:

if ("Object's  Transparency:".edit(ref transparency))
if (transparency != 0.5f && icon.Refresh.Click("Will load default value",25).nl())
transparency = 0.5f;

* The PEGI function is usually inside of your class, that is why the target part is not needed. They can access private & static members, and game can build with them.

* nl(); or; means New Line. Before PEGI class, I would often want to hide parts of the inspector when they are not needed, and as a result have a BeginHorizontal without a matching "End", which would throw tons of warnings. Now, everything is in one line by default, and only when I call nl() the EndHorizontal is called.

To actually replace Unity Inspectors with Custom inspectors add the code below to Your Class. I know from experience that colleagues often prefer to create a separate folder and put all Inspector code as partial classes. But there never been an issue when we do put the inspector inside a class. But I get that it may feel unconventional)

using PlayerAndEditorGUI;

    using UnityEditor;


    public class YourClassDrawer : PEGI_Inspector_Mono<YourClass> {}

Usually, I just do it for a couple of manager classes because you can Nested_Inspect() any class from the inspector of any other class. For example, pegi's edit_List will let you inspect all of it's elements if that element has a PEGI function (implements an IPEGI interface).

Custom List Inspector

"Image Datas:".edit_List(imageDatas);

* It lets you reorder elements (right image);

*Inspected element's class can have PEGI_inList function which will tell how it looks in list inspector (first image), For example: a tick-box to enable/disable it.

* And in this way I can inspect non-serialized classes which are not Unity Objects.

Another thing that have proven extremely useful is the iNeedAttention interface. Which when implemented will cause a list to show a warning sign when NeedAttention() returns a not empty string and the string is the message that will show when hovering over the warning icon.

I notify myself when reference is not set, list not filled up, basically any mistake that can be detected automatically is highlighted in this way so I don't have to do a same mistake twice.


Scripts come with more than enough examples (lots of examples actually, there are tons of other classes in my Tools GitHub repository that uses PEGI), but here is another small example:

class YourCoolClass : PEGI 
private int textureSize;
Texture2D texture;
 public bool Inspect()
  bool changes = false;
  changes |= "Resolution:".edit(ref textureSize).nl();
  if (textureSize <= 8)
    "Texture is too small".writeHint();
  else if (textureSize > 4096)
    "Texture is too big".writeHint();
  else if ("Create new texture".ClickConfirm("newTex"))
    texture = new Texture2D(textureSize,textureSize);
    Debug.Log("Texture was created");
  return changes;

The benefits:

* No reflection or serialization.

* It is just a wrapper on top of Unity native EditorGui so even if something changes dramatically, updating PEGI will be easy.

* It can be seamlessly added into an existing project (Something I actually did as part of my work). Also, it can be easily removed if anything)

* You are letting users control the private and static fields via inspector fields. I even used it to control Unity's render settings such as fog strength and sky colours, also Main Camera's background colour, all from a single inspector.

* I don't need to worry about mismatching BeginHorizontal() / EndHorizontal() functions, which would often happen as I want to hide and show some buttons under different conditions. For example, I can hide the "Create new" button if the name for a new object is too short or is already included in the list.

* Every function returns true if the value was modified. Unity provides "BeginChangeCheck/EndChangeCheck" to see if values within the block were changed, but I found myself writing less code by having a function return true if the value was changed.

* Inspecting Non-Serialized classes, private, static stuff.

* And finally, I wanted to use the same code to draw class in the inspector, and in build (screenshot of what I'm talking about is somewhere on this page). You can see that the painter tool uses the same function to draw itself in-game and in the inspector. So basically as a bonus you get an inspector in your build - it allowed me to debug my projects very easily.

* In addition to scripts there is also a folder with icons, which I found myself reusing very often. To add your icons, drop them in the folder and add its name to icon enum.

The PEGI scripts are at my public GitHub.

Maybe it's because I was using them so much that they seem easy to me, but using this class is a difference between doing a tedious job I never want to do again, and something so easy that it feels like a win every time I make an inspector and other people's life easier with it. And it's always a straight-forward process, something I don't need to think too much about. Join the Discord if you want to try and use it. Seeing other people using it would help me improve its usability and intuitiveness.

Facebook icon
Instagram icon
GitHub icon
Discord icon