Megabite #13 - Advanced GUI Features (Textures and Dynamic Generation)

In Megabite #11, I discussed how to do the basic layout features for both GUI and GUILayout.  In this release, what I'll be discussing is a bit more in-depth (You will want to already have a basic understanding of the previous article.)

The first thing I want to talk about is GUILayout.Window, which is a very cool feature, especially if you are planning on having a draggable elements such as an inventory system or perhaps a status window.  The command is simple enough, but the setup for a GUI window is a bit more complex than to just add your information into OnGUI.  First, you will want to declare a Rect variable and give it a name:

var myWindow : Rect;

function Awake() {
   myWindow = Rect(0,0,300,100);   //This will default our window to the upper-left corner, and set the minimum size as 300 wide and 200 tall.
}

Now we can set up our window command within OnGUI:

function OnGUI {
myWindow = GUILayout.Window(0, myWindow, MyGUIFunction, "My first draggable window!");
}

At this point, we have our OnGUI function gather information from 2 places - our variable and also a function we will now create.  The "0" in the commad above symbolized an ID number for the window, which is also found in this new function.  Because all of our regular GUI stuff will be actually located within this fuction, you may find this method to be a much cleaner way of laying out your regular GUI elements to begin with.

function MyGUIFunction (id: int) {
   GUILayout.BeginHorizontal("box");
      GUILayout.Label("This box is draggable");
   GUILayout.EndHorizontal();
   GUI.DragWindow();
}

Running your game with all of these pieces of code within a .js file should give you a draggable window.  The initial placement of the window can be manipulated in the section where you declare your Rect variable, and the window should resize based on the content as long as you are using GUILayout as opposed to simply GUI.  Of course, both of these are options an much of how you use this type of setup will be largely based on your own choices.

Next, I wanted to discuss the addition of textures into your GUI.  This could be useful for a lot of reasons, and you can use a standard picture or sprite-type image, or if you use Unity Pro you can actually add a render texture which is quite nice - basically the results of a camera can be sent into a texture format.  If you want a "video" element within your GUI (like a spinning 3d item, etc.), that's the way to go.

Because access to Unity Basic is more universal, I'll cover that end of things.  First, you'll want to declare a texture variable:

var myPicture : Texture;

When you do that, you'll end up with a drag/droppable section in the editor inspector.  You can import any picture you like into Unity and simply assign the image to the inspector.  Now you can use the GUI.Box or GUILayout.Box elements to display your texture as opposed to showing text.  if using the same code from above, just do it like this:

function MyGUIFunction (id: int) {
   GUILayout.BeginHorizontal("box");
      GUILayout.Box(myPicture);
   GUILayout.EndHorizontal();
   GUI.DragWindow();
}

Now you've got a fancy picture within your draggable window.  If you managed a render texture, you've actually got a video feed, which is also awesome. :)

Now, we're going to talk about dynamic generation.  With the clean, functional layout we have here, it's easy to use a FOR loop to actually make your window display information from an Array in a very dynamic way.  In this example, we're going to replace our function after declaring an array.  For now, we'll fill that array with a few strings for testing purposes.

var myArray = new Array("One", "Two", "Three", "F0ur", "Five");

function MyGUIFunction (id: int) {
   GUILayout.BeginHorizontal("box");
     for (var i=0; i < myArray.Length; i++) {
       GUILayout.Box(myArray[i]);
     }
   GUILayout.EndHorizontal();
   GUI.DragWindow();
}

What will happen here is that our draggable window should have 5 boxes - one for each element in our array.  You may want to eventually add spacing and such depending on what you're going for, but this should give you an idea of what is possible.

Overall, there are a lot of options within the GUI command subset.  GUI elements are difficult to master, but they are not only a handy thing to learn but also an important part of any game for navigation, inventory, statistics, status readouts, and thousands of other uses.

The above concludes the actual "lesson" portion, but as an added bonus, I'll paste a few pieces of GUI code functions below for some extra examples:  (as of today, this is code included in "Stellar", and can be seen in-action here.)

function MakeWindow (id : int) {
 for (i = 0; i < mats.Length; i++) {
 GUILayout.BeginHorizontal ("box");
 GUILayout.Box(camArray[i], GUILayout.ExpandWidth(false));
 GUILayout.Box("" + mats[i], GUILayout.ExpandHeight(true));
 GUILayout.Box("x " + gotItems[i], GUILayout.ExpandWidth(false));
 GUILayout.EndHorizontal ();
 }
 GUI.DragWindow ();
}
function MakeCraft (id : int) {
 GUILayout.BeginHorizontal ();
 GUILayout.BeginVertical("box");
 GUILayout.Box("Available Upgrades:", GUILayout.ExpandHeight(false));
 scrollPosition = GUILayout.BeginScrollView (scrollPosition, GUILayout.Width (250), GUILayout.Height (450));
 if (gotItems[0] >= 10 && gotItems[1] >= 1 && enginePower <=1150) {
 GUILayout.BeginHorizontal ("box");
 GUILayout.Box("Engine Power");
 if (GUILayout.Button("Upgrade")) {
 gotItems[0] = gotItems[0] - 10;
 gotItems[1] = gotItems[1] - 1;
 enginePower += 50;
 }
 GUILayout.EndHorizontal ();
 }

 if (gotItems[0] >= 10 && gotItems[2] >= 1 && rotSpeed <= 450) {
 GUILayout.BeginHorizontal ("box");
 GUILayout.Box("Rotational Speed");
 if (GUILayout.Button("Upgrade")) {
 gotItems[0] = gotItems[0] - 10;
 gotItems[2] = gotItems[2] - 1;
 rotSpeed += 50;
 }
 GUILayout.EndHorizontal ();
 }

 if (gotItems[0] >= 10 && gotItems[1] >= 1 && gotItems[2] >= 1 && fireRate >= 0.3) {
 GUILayout.BeginHorizontal ("box");
 GUILayout.Box("Firing Rate");
 if (GUILayout.Button("Upgrade")) {
 gotItems[0] = gotItems[0] - 10;
 gotItems[1] = gotItems[1] - 1;
 gotItems[2] = gotItems[2] - 1;
 fireRate -= 0.1;
 }
 GUILayout.EndHorizontal ();
 }

 if (gotItems[1] >= 5 && gotItems[2] >= 5 && gotItems[3] >= 2 && bulletCost >= 8) {
 GUILayout.BeginHorizontal ("box");
 GUILayout.Box("Firing Cost");
 if (GUILayout.Button("Upgrade")) {
 gotItems[1] = gotItems[1] - 5;
 gotItems[2] = gotItems[2] - 5;
 gotItems[3] = gotItems[3] - 2;
 bulletCost -= 2;
 }
 GUILayout.EndHorizontal ();
 }

 if (gotItems[3] >= 5 && gotItems[4] >= 2 && boostPower <= 14) {
 GUILayout.BeginHorizontal ("box");
 GUILayout.Box("AfterBurners");
 if (GUILayout.Button("Upgrade")) {
 gotItems[3] = gotItems[3] - 5;
 gotItems[4] = gotItems[4] - 2;
 boostPower += 1;
 }
 GUILayout.EndHorizontal ();
 }

 if (gotItems[0] >= 30 && gotItems[1] >= 5 && gotItems[2] >= 5 && gotItems[3] >= 5 && gotItems[4] >= 5 && radar == false) {
 GUILayout.BeginHorizontal ("box");
 GUILayout.Box("Install Radar");
 if (GUILayout.Button("Install")) {
 gotItems[0] = gotItems[0] - 30;
 gotItems[1] = gotItems[1] - 5;
 gotItems[2] = gotItems[2] - 5;
 gotItems[3] = gotItems[3] - 5;
 gotItems[4] = gotItems[4] - 5;
 radar = true;
 }
 GUILayout.EndHorizontal ();
 }

 if (gotItems[0] >= 20 && gotItems[1] >= 5 && gotItems[2] >= 5 && gotItems[3] >= 5 && bounce == false) {
 GUILayout.BeginHorizontal ("box");
 GUILayout.Box("Bouncing Bullets");
 if (GUILayout.Button("Install")) {
 gotItems[0] = gotItems[0] - 20;
 gotItems[1] = gotItems[1] - 5;
 gotItems[2] = gotItems[2] - 5;
 gotItems[3] = gotItems[3] - 5;
 bounce = true;
 }
 GUILayout.EndHorizontal ();
 }

 GUILayout.EndScrollView ();
 GUILayout.EndVertical();
 GUILayout.BeginVertical ("box"); 
 GUILayout.Box("Current Stats:", GUILayout.ExpandHeight(false));
 //GUILayout.Box("Stats:", GUILayout.ExpandHeight(true));
 GUILayout.Label("Engine Power: " + enginePower);
 GUILayout.Label("Rotational Speed: " + rotSpeed);
 GUILayout.Label("Firing Rate: " + fireRate + " Sec. Delay");
 GUILayout.Label("Firing cost: " + bulletCost + " Energy");
 GUILayout.Label("Afterburners: " + boostPower + "x Speed");
 if (GUILayout.Button("Open Loot Inventory")) {
 showInv = true;
 }
 if (GUILayout.Button("Show Recipie Book")) {
 showRecipies = true;
 cwindowRect = Rect(cwindowRect.x,cwindowRect.y,700,200);
 }
 GUILayout.EndVertical ();
 if (showRecipies == true) {
 GUILayout.BeginVertical ("box");
 //Recipie List
 scrollPosition2 = GUILayout.BeginScrollView (scrollPosition2, GUILayout.Width (250), GUILayout.Height (450));
 GUILayout.Box("Engine Power:\n10 Scrap Metal\n1 Plasmoid");
 GUILayout.Box("Rotational Speed:\n10 Scrap Metal\n1 Nebulite");
 GUILayout.Box("Firing Rate:\n10 Scrap Metal\n1 Plasmoid\n1 Nebulite");
 GUILayout.Box("Firing Cost:\n5 Plasmoids\n5 Nebulite\n2 Meteor Rocks");
 GUILayout.Box("Afterburners:\n5 Meteor Rocks\n2 Stellar Shards");

 GUILayout.Box("Install Radar:\n30 Scrap Metal\n5 Plasmoids\n5 Nebulite\n5 Meteor Rocks\n5 Stellar Shards");
 GUILayout.Box("Bouncing Bullets:\n20 Scrap Metal\n5 Plasmoids\n5 Nebulite\n5 Meteor Rocks");

 GUILayout.EndScrollView ();
 if (GUILayout.Button("Close Recipie Book")) {
 showRecipies = false;
 cwindowRect = Rect(cwindowRect.x,cwindowRect.y,500,200);
 } 

 GUILayout.EndVertical (); 
 }
 GUILayout.EndHorizontal ();
 GUI.DragWindow ();
}