Mythruna's new menuing system

As some of you know, I’m replacing my old menuing system with something more JME-friendly than nifty. It’s far enough along that I actually have something to show for it. I thought some might find it interesting.

[video]http://www.youtube.com/watch?v=3hxe2iLEEWE&feature=youtu.be[/video]

There are sort of a few levels here. There is the base GUI toolkit (which I hope to release to the monkeys someday) that for the most part acts a lot like swing except with CSS-like style support:
[java]
// Some elements using some kind of “paper” style
Container c = new Container( “paper” );
c.addChild( new Label( “Hello, world.”, “paper” );
c.addChild( new Button( “Exit”, “paper” );
[/java]

The menu system is actually another layer of abstraction created from “MenuElements” that have a loosely hierarchical organization. Some of these relate to meta-components like a slider + a label. Basically, the “menu elements” are created in groovy scripts and are visualization independent. The UI side takes these menu elements and makes a UI out of them in some implementation specific way (in this case pages of parchment).

Menu system groovy files look like: (with dummy code in the event handlers)
[java]
section( “Single Player” ) {

action( "Continue last game" ).onClick {
    println "Continue last game."
}

action( "Create new world" ).onClick {
    println "Create new world."
}

action( "Load existing world" ).onClick {
    println "Load existing world."
}

}
[/java]

That part is kind of Mythruna-specific (or at least specific to my game’s engine) but will make it easier to support total conversions later.

5 Likes

For the really curious, here is the (mostly) functioning code for the video options tab. The game quality section is the only part not hooked up to real settings. The rest is 100% functional and works like one would expect to allow changing the video settings at runtime.
[java]
section( “Options” ) {

tab( "Video" ) {

    section( "Display" ) {
        label( "Display Info" ).onInit {  
            name = DisplaySettings.instance.rendererName.take(30);
            name += "\n" + DisplaySettings.instance.vendor
            name += "\nOpenGL:" + DisplaySettings.instance.openGlVersion 
            name += "  GLSL:" + DisplaySettings.instance.glslVersion 
            name += "\nGPU: " + DisplaySettings.instance.adapter 
            name += "  ver: " + DisplaySettings.instance.driverVersion;
        }

        columns {               
            checkbox( name:"Full Screen", checked:app.fullScreen ).onChange {
                def resolutions = getElement( "Options.Video.Display.Resolution" ) 
                if( checked ) {
                    resolutions.options = DisplaySettings.instance.screenResolutions
                    resolutions.defaultValue = DisplaySettings.instance.defaultScreenResolution                     
                } else {
                    resolutions.options = DisplaySettings.instance.windowedResolutions
                    resolutions.defaultValue = DisplaySettings.instance.defaultWindowedResolution 
                }
                
                app.setFullScreen( checked );
                getElement( "Options.Video.Display.Apply" )?.fireChange()
            }
        
            checkbox( name:"V-sync", checked:app.VSync ).onChange {
                app.setVSync( checked );
                getElement( "Options.Video.Display.Apply" )?.fireChange()
            }
        }
        
        slider( name:"Resolution", options:DisplaySettings.instance.windowedResolutions, defaultValue:DisplaySettings.instance.defaultWindowedResolution ).onChange {
            app.setResolution( value );
            getElement( "Options.Video.Display.Apply" )?.fireChange()
        }
        
        slider( name:"Anti-aliasing", options:["Off", "x2", "x4", "x6", "x8", "x16"], value:app.aaString, defaultValue:"Off" ).onChange {
            app.setAaString( value );
            getElement( "Options.Video.Display.Apply" )?.fireChange()
        }
        
        action( name:"Apply", enabled:false ).onChange {
            // Check the UI settings against the real current settings and
            // set enabled state if they are different.
            enabled = !app.areSettingsCurrent();
        }.onClick {
            app.applySettings()
            fireChange()
        }
    }
    
    section( "Game Quality" ) {
        checkbox( "Low Quality Shaders" )
        slider( name:"Clip", options:["64 m", "96 m", "128 m", "160 m", "192 m"], defaultValue:"128 m" )
    }
}


[/java]

You can kind of see how powerful this level of abstraction is. Directly hooking up the abstract structure and events to real code.

Congratulations. This is very impressive!
I hope this will be shared soon. Seems to be a nice replacement for nifty.

How do your groovy scripts do interact with Java? Are you using some kind of compilation? Or runtime interpretation?

This looks really nice!

Though, I’m a bit confused. Is Nifty going buh-bye?

This isn’t an official replacement for nifty. It’s an alternative :slight_smile:

Really nice !

In Mythruna, nifty is going bye-bye.

When I release my GUI toolkit, as zarch says, it will just be an alternative. Nifty has more features and probably always will. Mine is based directly on JME classes, can support fully 3D UIs, etc… and in my opinion just throwing up a panel with a handful of buttons is easier in mine.

In Mythruna I will have crafting interfaces and such that will be actual objects in the game. It is easier all around if those can use the same UI listeners, etc. as the regular 2D UI. When I have implemented more use-cases and have time to properly package it, I will release my toolkit as a plug-in. (Currently I call it lemur.)

re: Groovy, I use runtime interpretation, sort of. The scripts are called from Java as part of startup and they create the menu structure and register closures as different listeners (onInit, onChange, etc.). These listeners are not compiled into Java byte code but they have already been compiled with the groovy runtime. And note: the groovy integration is not part of the Lemur. I tried to leave Lemur as dependency-free as possible. I may publish a separate plug-in for configuring styles using Groovy as it’s a lot easier in a DSL than in Java.

2 Likes

May I ask you what was the motivation for creating this and abandoning nifty? I’m just curious, don’t take as an offense!

When I started with nifty, there was virtually no documentation for the version that JME had just adopted. Everything I learned was by extracting the examples from the demo jar file and through hours of trial and error. To me, everything always felt 10x harder than I thought it should be. I’ve since been made to feel stupid and that it was my own failings. That’s fine. I’ve only coded to about half a dozen GUI APIs without issue and written at least as many myself. No worries. When it comes to nifty, I am completely stupid. I won’t argue there.

Other than the lingering bad taste in my mouth, the way nifty and JME interact is pretty inefficient. Nifty basically wants to write to an immediate mode “canvas” and JME tries to emulate that with scene graph components. We’ve made a lot of optimizations to make that better but it still seems like a lot of work if all I’m doing is putting a few buttons on a HUD screen. So for simple things, I wanted something JME-based that could interact with regular nodes and controls. Where parts of it could be batched or whatever.

Now that I’ve gotten the more contentious reasons out of the way, the final straw was that I wanted 3D support. I’d already been creating hybrid 3D+2D GUIs for Mythruna and it was becoming painful to essentially reinvent the wheel every time. A core of common components was slowly developing. I already had some pretty nice mouse event support (See below) and felt like it wouldn’t be much to take the next step.

So, now I have a UI toolkit that works in 2D or 3D or both. It uses a couple app states and a couple controls at the base level and then wraps that in some nice wrapper classes (Label, Button, Slider, etc.) but really these are just hooking up the CSS-like style support to the underlying pure JME concepts.

Basically:
There is a GuiControl that when added to a node can manage a stack of GuiComponents. These are responsible for creating the geometry children that represent the node. So if you stack a QuadComponent with a TextComponent then you get a label with a box around it. And so on. There is an interaction between the layers that is too much to explain here. But the QuadComponent is automatically sized for the TextComponent… and if you add two QuadComponents the first one will be big enough for the second and the second will be big enough for the text.

The GuiControl can also take a layout that can be used for managing the position of child nodes that have GuiControls. Layouts are 3D but the default implementations are 2D so far… though they let you pick which 2 axes.

There is also a MouseEventControl that can be added to any Spatial. Combine this with an app state that handles mouse events and picking and any spatial (2D or 3D) that has a MouseEventControl will get classic enter/exit/move/button events. I’ve created entire GUIs with just this control and app state. All of the in game non-nifty Mythruna GUIs in the last release are built this way.

The Label, Button, etc. style elements and the style support are just wrappers above all of that… either of which is useful on its own without the wrappers.

2 Likes

Great to see a real write-up of it… The little snippets of code I’ve been seeing of this have been looking mighty interesting… even if they look a lot like the AWT API :wink:

@sbook said:
Great to see a real write-up of it... The little snippets of code I've been seeing of this have been looking mighty interesting.. even if they look a lot like the AWT API ;)

You just say that because of “Button” and “Label”… which is what swing would have called them if AWT hadn’t already stolen the names. :slight_smile: I could have used LButton but LLabel just looked weird. :wink:

@pspeed said:
@sbook said:
Great to see a real write-up of it... The little snippets of code I've been seeing of this have been looking mighty interesting.. even if they look a lot like the AWT API ;)

You just say that because of “Button” and “Label”… which is what swing would have called them if AWT hadn’t already stolen the names. :slight_smile: I could have used LButton but LLabel just looked weird. :wink:

Container was actually the give-away :wink:

@sbook said:
@pspeed said:
@sbook said:
Great to see a real write-up of it... The little snippets of code I've been seeing of this have been looking mighty interesting.. even if they look a lot like the AWT API ;)

You just say that because of “Button” and “Label”… which is what swing would have called them if AWT hadn’t already stolen the names. :slight_smile: I could have used LButton but LLabel just looked weird. :wink:

Container was actually the give-away :wink:

Heheh… only in this case, Container extends Panel… and not the other way around. Technically all gui controls can be containers but it’s the Container class that provides convenient methods so you don’t have to call getControl(GuiControl.class).addChild(), etc… It also gets a default layout set as part of its style support and so on.

Well, looks interesting actually. You think it has better performance than nifty? (just think i.e. guess)

@shirkit said:
Well, looks interesting actually. You think it has better performance than nifty? (just think i.e. guess)

Theoretically, yes. After all, it’s just JME objects doing what they normally do.

@shirkit said:Well, looks interesting actually. You think it has better performance than nifty? (just think i.e. guess)

I’m betting that is HUGE yes. Just read the write-up again…

@pspeed said:Nifty basically wants to write to an immediate mode "canvas" and JME tries to emulate that with scene graph components.

No offense intended towards Nifty… but I for one will be using this when it’s available. I spent a good 6 months building out an extensive UI with Nifty + no documentation… it was PAINFUL and contains a TON of hackery to get it to do what I did. As more and more documentation became available, I realized there was potentially other ways of handle SOME of this… but I was far to committed to the side of my head that I had bashed in getting this far to rewrite anything on a maybe.

Yes, I totally agree. Nifty is painful, and I gave up after only a few hours… Just wanted to make a log… Reverted back to JME text, and it works in a few minutes only :(.

But, yes… I would be glad to test this new menu framework. Reinventing wheel is not that good! And your work seems very well thought & built.
Every thing I read in this thread makes the waiting more difficult! Thanks for this!!!

Well I had decent results with nifty…the main difference though could be that the nifty bible was available when I started working with it :slight_smile:

Having said that having a choice of tools (so long as things don’t get too fragmented) is a good thing…and this does look like a nice system so it is good to have the option.

That looks very promising. I also fiddled weeks and months to come up with a partially working nifty gui and is still hinges somewhere. JME Scaleform to go :wink:

The bad thing is these GUIs cannot be edited if you are not the programmer. If you’re not like Paul and don’t do everything yourself its much better to edit external description files. So its kind of normal it instantly appeals to programmers but without that feature its definitely nothing for the core engine, so nobody needs to worry about his nifty guis :slight_smile:

Lately I found more and more that the main point of the SDK (collaboration) is still its strongest point :smiley: It was very possible to edit gui and sound files in an external project. In a jME project was created with another IDE it was the pain! :wink: