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

About

Unity's default 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 editors. And as I was making them I would often create a wrapper function to make the code shorter. Time went by and I kept adding/removing and changing those wrappers. Many custom inspectors later the PEGI class is now a big helper.


Game View inspector (in Build)


Main benefit: the code you write will let you see your class on the device as well as in the Editor

public void OnGUI() => _window.Render(this);
private readonly pegi.GameView.Window _window = new(upscale: 2);

As Unity's GUILayout has fever options then EditorGUILayout, the GamaView is a bit more janky. But for Development and debugging purposes it proves to be sufficient.


Easy to write

To demonstrate I'll compare the code I'd need to write before with the one I write now. Both examples will show us a slider that calls a Recalculate() when anything changes, and a button with an icon right next to it that reverts to a default value).


Without PEGI wrapper (MyMaterialControllerDrawer.cs):

var controller = (MyMaterialController )target;
 
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();

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;
 
EditorGUILayout.EndHorizontal();


With PEGI (MyMaterialController.cs - inside the script)::

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

The PEGI's Inspect() function is inside your class, which is why the target part is not needed. They can access private & static members, and games can build with them (The Editor code is stripped by platform-dependent compilation).

nl(); or pegi.nl(); means New Line. Before PEGI class, I would often want to hide parts of the inspector when they are not needed.

 

Overriding the inspector

To actually see the result of Inspect() function, use Unity's [PEGI_Inspector_Override()] Attribute.


[PEGI_Inspector_Override(typeof(YourClass))]  
internal class YourClassDrawer : PEGI_Inspector_Override {}


 

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 its elements if that element has an Inspect() function (implements an IPEGI interface).


Custom List Inspector

"Image Datas:".PegiLabel().edit_List(imageDatas);

* 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).

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


Another thing that has proven extremely useful is the iNeedAttention interface. 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 a reference is not set, or the list is not filled up, basically, any mistake that can be detected automatically is highlighted in this way so I don't have to search for the same mistake twice.


Example:

My Tools GitHub repository has plenty of examples. But here is just one more:

class YourCoolClass : PEGI 
{
 
  private int textureSize;
  Texture2D texture;
 
 public void Inspect()
 {
   "Resolution:".PegiLabel(width: 90).edit(ref textureSize).nl();
    if (textureSize <= 8)
     "Texture is too small".PegiLabel().writeHint();
   else if (textureSize > 4096)
     "Texture is too big".PegiLabel().writeHint();
  else if ("Create new texture".PegiLabel().ClickConfirm(key:"newTex",toolTip: "This will delete Old Texture"))
   {
     texture = new Texture2D(textureSize,textureSize);
     Debug.Log("Texture was created");
   }
 }
}

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 to an existing project (Something I actually did as part of my work). Also, it can be easily removed)

* You can control the private and static fields via the inspector. I used it to control Unity's render settings such as fog strength and sky colours, and also the 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.

* And finally, I wanted to use the same code to draw class in the inspector, and in build (a 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 their name to the 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 straightforward 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.