Programmer: What was the problem with IMGUI?

I started using Unity right around when uGUI first being released, so I spent too much time with it predecessor. As an Asset Store publisher, I do still work with IMGUI occasionally, and really loved its simplicity.

With UI Toolkits on the horizon, I wonder why IMGUI is being completely phased out. I understand it have no GUI builder, and how it can be performance intensive for complex UIs. However, in other software engineering circle, I heard nothing but praise for imgui as a concept.

Those who has worked with any kind of imgui in the past, Unity’s or any other, what problems has the paradigm gave you personally?

None (used it in editor). Aside from performance.

I actually thought the idea was really clever and really convenient, as you can make complex dynamic layouts this way, and it resembles functional programming somewhat.

Kinda like Qt nailed OOP GUI design, IMGUI nailed code-driven design.

Defining layout with code is not human readable. You cant look at the code and in realtime transform it into visuals like you can html/css or XAML/styles.

MVVM and MVC fixes these problems. You abstract the code from the view in a clever way

uGUI isnt perfect either, they should have gone with a MVVM approuch

Right? A such a shame it being phased out

You never actually worked with these, did you?

5 Likes

I have worked with both. Well, mostly my colleague who is the UI designer but some for me too. Our entire UI is made in uGUI. The old crap we only use for our internal editor tools

Most of the issues I had with it were in the implementation, not the concept. Stuff like some info being hard to access in certain contexts, or some accessible info being highly context dependent in unclear and sometimes inconsistent ways.

Still, you mention performance and… yeah… that’s a big one, even in the Editor. I’ve got a few objects in my current project which make the game noticably slower just from showing in the Inspector while testing.

Huh? HTML defines the content, not visuals. That’s what CSS is for, unless you’re working with really old stuff or ignoring best practices.

1 Like

See edit. css is not perfect but alot better than anything else. styles in XAML is crap, but alot better then the out of the box uGUI stuff.

I worked with all kind of XML based retained mode UI. They all get extremely complex very fast. In fact, nowaday retained mode UI are being written in a more immediate mode like syntax, see Flutter or Swift UI.

For editor code, my experience is that a big part of the performance hit is the reflection heavy architecture, not so much imgui itself.

1 Like

A what you see is what you get editor can be nice, maybe, I don’t use it for web or app work. But I know some do, none of the pro web deisgner or web app UI guys I worked with though.

That’s a big aspect, for sure.

Another is that the whole GUI is re-run every time something might have changed. I change something in a text field and the whole GUI it’s a part of gets re-run, including all of the serialisation and/or reflection stuff going on to populate it. And the result of that might be one extra character appearing in a text box.

I have one UI that basically shows a table of data from a SerializedObject. It’s pretty simple stuff. Alas, when imeplemented the way the docs say, after there are more than a few dozen rows it gets to the point where I can type faster than the input can be handled.

I’ve only said bad things, so it sounds like I’m really sour on it. :stuck_out_tongue:

For the most part it’s a nice and quick way that I can throw simple custom inetrfaces together, and in that regard it works pretty well. It’s only when things get complicated that it runs into issues. And some of those issues I could probably overcome by not using the *GUILayout classes, because they (reasonably enough) come with a bunch of assumptions.

Biggest problem with IMGUI is that something as simple as

<ul bind="foreach: items">
   <li bind="text: Name"></li>
</ul>

Becomes alot of code, and as more elements you bring in, the harder the code gets to read and transform to actual layout flow in your brain

Immediate mode GUI is a great way for making relatively useable UI very quickly, with very little code. Unity’s implementation does just that, but it’s

  • very hard to make it look good
  • very poorly performing
  • very, very buggy
  • has a poorly designed API full of inconsistencies and holes.

It’s very good for adding a few buttons to an editor UI in a situation where it’s fine that it looks just like any other Unity editor control. An alternative is needed when you either need to make something that looks good, are doing something complicated, or have very much data to edit.

So getting rid of it for in-game uses isn’t a big deal, since if we want to make any UI that looks decent, it’s a no-go. It allows Unity to strip out some code from the engine, which speeds things up for us (and for their iteration speed).

It’s going to stay there in the editor, which is good - while UI Toolkit gives better results, it takes longer to get things done, and you often don’t need that good results when what you’re doing is adding a readout of some data or a button that does a debug thing.

UGUI can go die in a fire, it should never have been shipped, it’s never been production ready, God it’s so bad.

To MaKe A tExTbOx FiT iTs CoNtEnT, FiRsT aDd A HoRiZoNtAlLaYoUtGrOuP.

???

foreach(var item in items) {
    EditorGUILayout.LabelField(item.name);
}
4 Likes

Now you want the text to float right :wink:

You get the point, defining views in code get messy and unreadable fast, very fast.

If a situation like that causes trouble for a developer, you need to replace that developer with another one that has more skill.

As this kind of thing is very trivial to do.

Nope. 2 lines of code for two controls is pretty much definition of readability.

“Making things float right” is not the problem solved by UI layout generator code, but by stylesheets.


Maybe it would be a good idea to implement alternative UI toolkit that uses the same approach.

As I said, it is one of the two cleanest approaches I ever saw when it comes to generating UI from code.

In a somewhat comlex UI it will be a huge pain.

I don’t think you can be performant at scale - at least by default - with any imgui approach.

Say you’re drawing some thousands of elements in a list with a scroll view. In an immediate approach, you don’t know anything upfront about how many elements there are, or how large they are, so you have to (by default) run the code for every element every time you want to update anything on the screen.

So what you end up having to do is for the developer to manually roll their own thing to handle that performance issue, or some other workaround.
In a retain mode you by definition have the elements and their sizes upfront, so culling the list ends up being based on data you already have.

But for the other things, yeah. GUIStyle could’ve been good, the API could have been well designed, and the bugs could have not been there.

Example, still pretty trivial UI but its a pain to read, this is a real world example of a editro tool we have made for our game. Code is not perfect, just quick made for use in editor

GUILayout.BeginHorizontal("box");

            if (session.Instance)
                if (GUILayout.Button("Revert"))
                {
                    PrefabUtility.RevertPrefabInstance(session.Instance.gameObject);
                }

            if (session.Instance)
                if (GUILayout.Button("Apply"))
                {
                    ApplyPrefab(session.Instance);
                }

            if (GUILayout.Button("Refresh session"))
            {
                RefreshSession();
                Next(0);
            }

            if (GUILayout.Button("Toggle hands"))
            {
                HandIKRigger.ToggleHandConfig();
            }

            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal("box");

            if (GUILayout.Button("Prev. rigg"))
            {
                Next(-1);
            }

            if (GUILayout.Button("Next rigg"))
            {
                Next(1);
            }

            if (Selection.gameObjects.Length != 0)
                if (GUILayout.Button("Highlighted as secondary"))
                {
                    SetupSecondary();
                }

            GUILayout.EndHorizontal();

            GUILayout.BeginHorizontal("box");

            if (session.Rigg)
                if (GUILayout.Button("Highlight rigg"))
                {
                    Selection.objects = new[] {session.Rigg.gameObject};
                }

            if (session.Item && Selection.gameObjects.Length != 0 && GetEndTrigger())
                if (GUILayout.Button("Copy trigger to end point"))
                {
                    CopyTriggerStart();
                }

            GUILayout.EndHorizontal();

            if (HasRigError())
            {

                GUILayout.BeginHorizontal("box");

                if (GUILayout.Button("Correct finger tip rotation errors"))
                {
                    var errors = new List<Transform>();
                    GetErrorBones(session.Rigg, errors);
                    foreach (var error in errors)
                    {
                        error.localEulerAngles = new Vector3(0, 0, error.localEulerAngles.z);
                    }
                }

                if (GUILayout.Button("Highlight finger tip rotation errors"))
                {
                    var errors = new List<Transform>();
                    GetErrorBones(session.Rigg, errors);
                    Selection.objects = errors.Select(t => t.gameObject).ToArray();
                }

                GUILayout.EndHorizontal();

            }


            var prefab = EditorGUILayout.ObjectField("Jump to", null, typeof(Transform), true) as Transform;
            if (prefab)
            {
                Jump(prefab);
            }

            var template = EditorGUILayout.ObjectField ("Rig from", null, typeof(Transform), true) as Transform;
            if(template)
            {
                CopyRig (template);
            }

            if (Selection.gameObjects.Length != 0)
            {
                EditorGUILayout.LabelField("Selected transform");

                var trans = Selection.gameObjects[0].transform;

                var pos = EditorGUILayout.Vector3Field("Position", trans.transform.localPosition);
                if (trans.localPosition != pos)
                {
                    trans.localPosition = pos;
                }

                var rot = EditorGUILayout.Vector3Field("Rotation", trans.transform.localEulerAngles);
                if (trans.localEulerAngles != rot)
                {
                    trans.localEulerAngles = rot;
                }

                var scale = EditorGUILayout.Vector3Field("Scale", trans.transform.localScale);
                if (trans.localScale != scale)
                {
                    trans.localScale = scale;
                }

            }

Looks like this

Could look like this in some pseudo UI framework

<panel>
   <button name="RevertPrefab">Revert</button>
   <button name="ApplyPrefab">Apply</button>
   <button name="RefreshSession">Refresh session</button>
   <button name="ToggleHands">Toggle hands</button>
</panel>
<panel>
   <button name="PreviousRig">Prev. rigg</button>
   <button name="NextRig">Next rigg</button>
   <button name="HighlightAsSecondary">Highlight as secondary</button>
</panel>
<panel>
   <button name="HighlightRig">Highlight rigg</button>
</panel>

etc. etc

, by convention if a button is bound to method HighlightAsSecondary a property bool CanHighlightAsSecondary can be used to determin if button can be pressed, etc. Works very well for Caliburn Micro in WPF