hdxgui2

The HeroDex GUI – jME3 User Interfaces

The HeroDex GUI – jME3 User Interfaces

[toc levels=3 title=”Table of contents”]

Nifty is no longer the only choice for creating a GUI within jMonkeyEngine 3, however it is the oldest and the most fully featured GUI library.

The three main choices are:

Nifty – A very long running and highly featured library with a good track record for support and maintenance. The API can be cumbersome but it works well when you work with it and the results are impressive. There is also a new update coming soon which looks likely to give a large improvement to performance.

TonegodGUI – The most recent entry to the GUI options this library already has an impressive range of features and very active and enthusiastic support, although no proven longevity as yet.

Lemur – The main selling point of Lemur is the ability to embed GUI elements into the scene graph, allowing them to exist in the world. It shows much lower activity levels than either TonegodGUI or Nifty but the modular nature of its components means that you can choose to use just some sections of it as required.

These libraries are not exclusive, you can mix them in order to get the strengths of each. For example our GUI is primarily written using Nifty, however there are some areas where we needed panels displaying in the scene graph. For those we took one of the components from Lemur and used that for the embedded panels.

For HeroDex Nifty was the only option available when we started, so the choice was simple. For a new project we would recommend that you evaluate all three libraries keeping in mind the requirements of your project. It would not be impossible to switch afterwards but it would involve a lot of rewriting, as all three libraries have very different APIs and design philosophies.

This article has been split into a number of sections. For the most part each section is self contained so please feel free to jump to the section that interests you.

GUI Design, Look and Feel

When designing a GUI you need to think about the overall style that you are presenting. It needs to tie in with the feel of the game while also presenting the information in a clear and consistent way. Do not be afraid to experiment with a few different looks until you find the correct one, but when you have settled on something then make sure you update everything to be consistent.

For HeroDex we experimented with a few different approaches, for example the metallic panel with glowing border from the main logo looks good. Perhaps that would work for the in-game GUI too?

hdxgui0

In fact when we tried it the results were not so good. It was hard to make it work well at variable resolutions, scales, and aspect ratios. It also tended to dominate the screen rather, which while it is good for a title is not so good for an in-game GUI.

hdxgui1

The final design we settled on was much more minimalist and can be seen here. It is actually mostly built up from just a few images, and then a few icons. The main images are:

buttonGrey buttonGreyDown andMenuBackgroundS MenuBackground MenuBackgroundH

Can you see how these Images are used in the screenshot above? Through use of the Nifty ImageMode parameter these are stretched out to form the various buttons and panels on the screen.

 

The first pair are the buttons in their un-pressed and pressed states. By having a consistent look and feel for every press-able item it immediately lets users know where to try clicking.

The second set of images are used to build vertical, horizontal, and rectangular panels. You can find examples of all three in use on the screen above.

These images are copyright Zero Separation and are part of the look and feel of HeroDex, so we are sorry but no you can’t use them The nifty style libraries though present a range of different images that you are free to use in your projects.

In these examples you can see how by using the ImageMode parameter you can use really very small images to form your User Interface and have the results look impressive. You can also use them to build transparent interfaces very easily by using PNG images with transparent areas for your panels.

Nifty comes with a number of built in styles such as NiftyStyleBlack which include a range of panels and buttons that you can use for your project. These styles work well and look professional so I would suggest using them to start with and then you can always modify them over a time to customize the look and feel to your liking.

Architecture: App States and Screen Controllers

Ideally only one part of the system should be deciding what your application is doing at any one time. Unfortunately Nifty and jME3 both provide their own unconnected mechanisms, in the form of Nifty ScreenControllers and jME3 AppStates.

We tried a few approaches but the final pattern that we settled on and that we recommend is that Nifty controls the process via gotoScreen. When the screen is started we then attach the appropriate appStates for that screen.

We create a singleton class to be both an AppState and a ScreenController.

We do not use the singleton pattern for every AppState but we find it very useful for these cases where you have one specific state of the game and then want to access it from anywhere in the code. It also avoids potential synchronisation problems where you end up with multiple versions of the AppState and or ScreenController in various parts of the code. Instead you know there is one and always one.

To make sure Nifty knows about the screen controller you need to register it before loading any screens or XML:

Now we allow Nifty to control which is the current screen by using the nifty.gotoScreen(“example”) method. In our case we created a central class that knew the names of all the screens and we call a method in that class with an enum in order to issue the actual change. We find that removed the risk of potential typos in the names of screens, particularly ones that you might go to from multiple points in the code.

When the screen starts from Nifty it calls the callback method onStartScreen in the ScreenController. That method then attaches the state to the state manager (or enables it if it is already attached).

You will note that we start a custom effect called “startScreen” in the onStartScreen handler. This is because the onStartScreen method is only called when all start-of-screen effects inside Nifty have completed. This means for example if you have the GUI fading or moving into position then the AppState change will only happen once the start-of-screen effects have completed. Instead create custom effects with “startScreen” as the custom key and this call will trigger them all after the screen has switched.

The End of Screen effects work the same way, but fortunately in this case that is the behaviour we desire, so the sequence becomes:

  1. nifty.gotoScreen(“example”);
  2. Current screen end of screen effects play
  3. Once the effects have all finished onEndScreen is called and the AppState is detached or disabled
  4. Nifty switches to the new screen
  5. OnStartScreen is called, the new AppState is attached or enabled
  6. The custom “startScreen” effect is triggered and starting effects are played

The big advantages of this architecture is that you do not have any interdependencies between screens, or any special transition logic. You don’t need to know what the current screen is, you just tell Nifty which screen you want to be displayed now and then everything is handled automatically.

Mixing GUIs: Lemur and Nifty

One of the main limitations of Nifty is that all its elements are always drawn on its own GUI node and always after the scene and before anything added to the jME3 GUI node. This is fine for 99.9% of use cases, but occasionally you need more fine control.

In this screen shot we actually have both Nifty and a small component from Lemur working together. The main interface is provided using Nifty, but the two floating panels in the center are using tbtQuad from Lemur with a jME3 billboard control attached to them. 

hdxgui2

Why are they done like that?

hdxgui3

This screen shot demonstrates why. If they were done as standard Nifty panels then they would always appear in front of everything else, whereas in this case we needed them to be visible but we also needed the cards to be able to pass in front of them. You can see that by applying the same image to the tbtQuad and the Nifty panels and by using the same font for the text we were able to make them look the same, even though they were using a completely different mechanism behind the scenes.

You can mix elements of the different GUIs and keep a consistent look so long as you take care to do so. Ideally though you would find one GUI and use that everywhere you can as it helps with maintenance in the long run.

XML Versus Builders

One of the big decisions when using Nifty is whether to define your screens using XML or using builders. For single-developer projects the XML does not really add any large advantages, although it does help define the structure of your project by laying out all of your screens ahead of time to then allow gotoScreen to be used to switch between them.

What we found worked well is to define all of the main screens using XML, however for all the popups and more dynamic elements that we show we use Builders to construct them dynamically. This gives us flexibility where we need to while having the main screens defined ahead of time.

It also removed the link between popups and certain screens, allowing us to open up our own popups from any screen and even when required recreate them after a screen transition.

Nifty Popup State

NiftyPopupState is how we provide our own popups, unfortunately our implementation is too tied to the HeroDex look and feel to be published. However we will outline the basic principle with some source snippets to get you started. The basic concept is to have an AppState that handles the custom popups. When adding a popup it creates a layer on the current screen if it does not already exist – and then adds the popup as a panel inside that layer.

Whenever the screen changes it creates a new layer (deleting any previous one it might have created on that screen) and then recreates any popups that are still needed on that new layer. All the popups implement an interface:

The update loop in the App State looks something like:

We wrote an abstract implementation of the interface that provides utility methods to do things like construct buttons, etc. It also provides a method:

If any clickable panel or control is added then its name is built using buildButtonName – a number of NiftyEventSubscriber methods (don’t forget to subscribe the AppState to Nifty for annotations!) then pick up those actions and forward them to the appropriate popup for processing.

Conclusion

Nifty has its quirks and limitations but nothing stops you producing responsive and professional looking user interfaces using it. With the growing competition from TonegodGUI and Lemur the possibilities and options are improving all the time, so you will be able to find something that suits you. jME3 has a rich selection of interface options available so you are limited only by your imagination and your design skills, not by the engine!