Wrangling GUIs with Unity iPhone

Dealing with GUI creation is both a rewarding and frustrating venture. While working on my iPhone game SpringFling, I’ve come across some GUI conundrums. I wanted to achieve a UI effect like this:


The height bar would dynamically fill up based on the y-height of the player. It seemed simple to do, as I would just modify the scale attribute of an overlaid half transparent plane that would represent the filled portion of the bar. I could then rotate the bar slightly to match the 10 degree angle on which the ruler is set and all would be well. That doesn’t quite work out as simply though. When you’re working with GUI in Unity, it is a special graphics layer that is rendered last. That means that no 3d elements can ever go in front of your UI. I knew of this limitation and thought that I would just use two GUITextures and scale the red bar dynamically. Unfortunately, you are not able to rotate GUITextures so I first moved to a much more dynamic solution: Render textures. Using render textures in the Unity editor without touching a script, I was able to achieve the GUI element below:

Due to the awkward size of the video, you can also view the source .mov here:
http://gtproductions.net/projects/springFling/springfling_heightbar.mov

Behind the scenes, this progress bar consists of two planes, one with the ruler texture and height text, and a separate single poly plane positioned over the ruler. The shader is then set to additive and the scale is modified in the playerScript. The y-height of the player directly affects the scale of the red bar. The single poly plane used for the red bar needed to scale from the left side so the pivot point had to be manually moved in my DCC app (Maya) in order to get scaling to happen in the correct location. Once this setup was working, I moved it out of view of my main camera and created a second camera and pointed it at my height bar. This second camera would look at the UI setup I created out in space to the side of the main level geometry. I then created a new render texture and in the properties dialog of this second camera, I added my render texture into the ‘target texture’ field. Lastly I positioned my render texture near the bottom of my main camera and voila, a 3d UI overlaid on my game scene. This unfortunately is a Unity Pro-only feature so I needed to find another way to do this.

EDIT: Thanks to Dreamora and Matt for the guidance. It also seems that render textures do not actually work on iPhone hardware so this solution is wonderful for Unity Pro desktop games but for the iPhone, another solution can be used. You can accomplish the same result using two cameras and no render texture. Just set everything up as I mentioned above but instead of using the render texture, set the “Clear flags” on camera2 to ‘none’ so basically the background of will be transparent and camera2 will be rendered first and then the main level camera second. This allows the gui to be overlaid on top of the scene while allowing 3d geometry to come BEFORE the gui elements. This is key for any type of crazy/interactive gui in almost any game. Hey we’re using this technique at work for rendering a 3d UI over a background scene.

One other thing I’m experimenting with is the use of Unity’s great new GUI system known as UnityGUI vs the old tried and true method of GUITextures and GUITexts and manual UI development. Instead of event driven GUI development, UnityGUI uses an Immediate Mode GUI system, or ‘IMGUI’ which basically boils down to the fact that you work with the GUI in a separate GUI-specific update loop where you use logic to change GUI elements dynamically. This makes fades, slides, transforms, and other animated GUI operations much simpler and mirrors the regular Unity update loop in almost every way. Below is a basic example of a flashing button:

//flashing button example
function OnGUI () {
if (Time.time % 2 < 1) {
if (GUI.Button (Rect (10,10,200,20), "Meet the flashing button")) {
print ("You clicked me!");
}
}
}


Those familiar with Unity iPhone development will note that it is not advised to use Unity’s built in UnityGUI system and instead opt for GUITexts and GUITextures due to performance reasons. This is a valid concern as Unity’s GUI system does have a significant overhead associated with it but it really only comes into play with HUD UIs where UI elements are rendered over a 3d scene that requires a high framerate for gameplay. In a dedicated menu scene, UnityGUI works perfectly on the iPhone but as I move forward with custom UI art, I might switch over to the old GUITexture method to save resources. The last week of development will surely be focused on performance testing so I’ll be able to give more solid statistics on the impact of UnityGUI compared to single UI element placement. If you’re planning on using UnityGUI on the iPhone make sure to turn of automatic layout by putting this line in your Start() or Awake() method:

useGUILayout = false;


It will reduce your GUI overhead to an almost acceptable level for iPhone use but as I mentioned before, it’s not a great idea for a HUD where the game scene runs behind the UI. :)

Lastly, I stumbled across a post about iPhone optimizations and learned that string comparisons are much more expensive than int comparisons. Previously I was managing both game and GUI states with a static state string variable in my main game script. All all other scripts would query this state during the update loop ala:

if(game.state == "flying"){
//do stuff
}


This worked for me as I was able to easily see which state was being addressed by the string name, but after after hearing about the performance hit, I decided to switch over to enumeration. I also rely on a second state variable which I have named guistate to keep track of my different (for a lack of a better term) GUI states. These, for example, consist of ‘mainmenu’, ‘highscores’, ‘shop’, ‘stats’, and of course ‘none’. With the ability to get and set these global states at any time from any script, I can determine where the user is in the logical flow of the game and exactly what they are doing there. Then my onGUI function contains each of my GUIs and wraps each one in a quick if statement ala:

if(guistate == aguistate.death){ //player has died
GUI.BeginGroup ( blah blah GUI creation for death screen goes here);
}


Well there you have it. And if I can leave you with some words of wisdom, GUI takes twice as long as you think it will, even if you know this fact to be true.

6 Comments

  1. A question on it: you mention render textures, yet the main problem is, that those will not work outside of the editor and the regular Unity Pro, as neither Unity iPhone Basic nor Unity iPhone Advanced supports them.

    Did you replicate the functionality through manual texture updates on the actual iphone?

  2. Looks like you find a solid solution for this! However if this is unable to work on the IPhone I may have some ideas for you.

    I'm not 100% sure if this is possible but if you are able to have 1 texture as an alpha and another texture use that alpha this will work. All you'd need is one texture that is the bounds of your meter, this will act as an alpha for the dynamic bar. Then your dynamic bar can just be a texture, drawn at an angle so it wouldn't need to be rotated, which is moved in x coord when the height is changed. The mask should effectively hide all of the dynamic bar spilling from the left of the meter and should do the trick.

    Alternatively you could always fall back on an animated GIF

    -Dan K

  3. Dan beat me to it. I think you just need an all-red texture that shows through the transparent (or translucent) hole in the ruler.

    It is also possible to put GUI elements behind 3D objects, by using multiple cameras.

    You comment about using ints instead of strings for storing state is good. For a further improvement, try using enum's. This lets you write code like:

    if ( guiState == GameState.PlayerHasDied ) {

    It's similar to defining constants:
    const int PlayerHasDied = 2;
    But it's better because you can't accidentally use a non-existant state in that variable, and I think if you write a switch statement the compiler might even warn you if you forget to handle one of the states.

    Thanks for the reminder about useGUILayout, I will surely need that… although, I suspect/hope that Unity GUI performance will have improved in the forthcoming release 1.1.

  4. Nice Matt,

    Yeah I'm going to try the dual camera method with transparent background. That seems to be the way to do it since what I'm doing doesn't actually require a render texture.

    I'll also try switching to enums for state. Sounds like the most efficient and easy to read method. :)

  5. Edits have been made to the article to make note of limitations and solutions for 3d UI on the iPhone.

  6. [...] Wrangling GUIs with Unity iPhone from GTProductions [...]

Name (required)

Mail (will not be published) (required)

Website

Comment