Scene GUI to design a grid based level in Unity

While working on Get Off My Rock, it became apparent that producing a lot of levels very quickly, in order to test and iterate, would be extremely important. It then fell to me to work out how best to do that.

I’m just going to preface this by saying there are many ways to solve this problem, and mine is just one. I found it extremely hard to find decent documentation on OnSceneGUI, so maybe this’ll help you be a little less stuck than I was! Shout out to The Knights of Unity for this article http://blog.theknightsofunity.com/rendering-custom-gui-scene-view/ which was basically the only thing I managed to find, and was super helpful as a jumping off point.

You can also download the unity package file here to have a play around yourself: GridTutorial

The first thing we need is an empty game object in our scene. I’m going to call mine ‘Grid Generator’. We can apply our reference script to this so our editor know what object it wants to use.

Create Empty

Next up we’re going to add a script to it

Grid GUI is pretty simple. All it’s going to do is hold our references to certain prefabs, like our tile and our player prefab, so that we can use them in our editor script.


public class GridGui : MonoBehaviour {
public GameObject tile;
public int x, z;
}

The next script we’re going to write is a little more interesting. It’s going to be an EDITOR SCRIPT!

The only thing we need to worry about for these, is that we stick them in an Editor Folder. Unity won’t recognise an editor script unless it’s in the editor folder of the project.

Our class starts with us telling unity we’re making a custom editor, using our gridgui script. We also want to define the variables that we’ll be using. xString and zString to intake the string


[CustomEditor(typeof(GridGui))]

public class GridGUIEditor : Editor {



GameObject tile;
public int x, z;
string xString, zString;



Now that we have this code we can add our tile prefab to the object. Just a simple 1x1x1 scale cube is what I’ll be using, since the grid will be one unit per grid space.

We can then drag our prefab into the grid generator inspector window.

The next thing we’ll need is OnSceneGUI. This function is called every time the scene view is updated. From moving the mouse to changing selections. It’ll also be what displays the UI in the scene. Until stated otherwise, all the code snippets I give will be within this void:


void OnSceneGUI()
 {

}

The main class we’ll be dealing with for our UI is the GUILayout. We’ll be using them in tandem with Handles, which let us draw in the scene. We can also use handles to draw widgets and other fun 3d stuff in the scene space.
The code’s a bit ugly to look at (that’s the nature of using GUI layout to make the end product pretty) but it does the job. Basically we’re going to set up a rectangle, and stick our lovely text inputs and buttons inside.

Here’s the function on it’s own. I know it’s a lot, we’ll break it down to be more bite sized further down.


GridGui script = (GridGui)target;

tile = script.tile;
x = script.x;
z = script.z;

if (xString != "")
{
xString = x.ToString();
}
if (zString != "")
{
zString = z.ToString();
}

Handles.BeginGUI();
GUILayout.BeginArea(new Rect(20, 20, 150, 60));
var rect = EditorGUILayout.BeginVertical();
GUI.Box(rect, GUIContent.none);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Label("GenerateWorld");
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Label("X");
xString = GUILayout.TextField(xString);
if (xString != "")
{
x = int.Parse(xString);
script.x = x;
}
else
{
xString = "";
}
GUILayout.Label("Z");
zString = GUILayout.TextField(zString);
if (zString != "")
{
z = int.Parse(zString);
script.z = z;
}
else
{
zString = "";
}
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("Generate"))
{
GenerateGrid();
}
GUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
GUILayout.EndArea();
Handles.EndGUI();

 

That’s a lot. Let’s break that down a little more.

This chunk here deals with target, which is part of the editor class and refers to the object being selected.  We cast it as GridGui because that’s where the variables that we want to deal with are kept, not the entire selected object:


GridGui script = (GridGui)target;

tile = script.tile;
x = script.x;
z = script.z;

 

Next we need to read from the input field. The first bit takes x and z and turns it into a string for us to use later in the script. The reason we do it up here is because the script runs on every event and we need this done before the rest of the code executes. The second bit we take the text field text and turn it into an int we can use:


if (xString != "")
{
xString = x.ToString();
}
if (zString != "")
{
zString = z.ToString();
}

//-------Our big GUI Layout Chink for above--------
//

xString = GUILayout.TextField(xString);
if (xString != "")
{
x = int.Parse(xString);
script.x = x;
}
else
{
xString = "";
}

 

After that we declare the space we want to use for our editor, and start building our GUI. I would highly suggest doing some reading on the GUILayout. There are way more options to play with than I explore here.


Handles.BeginGUI();
GUILayout.BeginArea(new Rect(20, 20, 150, 60));
var rect = EditorGUILayout.BeginVertical();
GUI.Box(rect, GUIContent.none);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Label("GenerateWorld");
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();

//code etc

NEW FUNCTION TIME!!

This function is what’s called by our button, and it generates our grid. First we’re going to set up our Vector3 called NewPlace because we’re going to put each element of the grid somewhere else. After that we’re going to create an object called WorldTiles. It’s going to be an empty game object just to function as a folder that we can use to hide all the tiles in so our hierarchy isn’t ugly. We also register it for undo so that we can get rid of it if we change our minds.


void GenerateGrid()
{

Vector3 newPlace = new Vector3();
GameObject WorldTiles = new GameObject();
WorldTiles.name = "WorldTiles";
Undo.RegisterCreatedObjectUndo(WorldTiles, "Created grid parent");

}

This set of 2 loops will iterate through both axis of the grid and place a prefab tile at that location. We then make it a child of the WorldTiles object, and make it Undoable as well.


for (int i = 0; i < x; i++)
{
for (int j = 0; j < z; j++)
{

//Instantiate objects in a grid and register them for undo
GameObject obj;
newPlace.Set(i, 0, j);
obj = (GameObject)PrefabUtility.InstantiatePrefab(tile);
obj.transform.position = newPlace;
obj.name = "Tile" + " " + i.ToString() + "," + j.ToString();
obj.transform.parent = WorldTiles.transform;
Undo.RegisterCreatedObjectUndo(obj, "Created tile");
}
}

 

We’re good to go! Just type the dimensions you want your grid to be and click that button!

 

Obviously this can be achieved with less work with other methods, but this leaves the designers with the knowledge that they can look in the upper left for their tools, so when they use our more advance tools, they know where to look and what to expect, instead of using multiple methods for the same thing.

Speaking of more advanced methods, I’ll be covering how to use SceneGUI to add objects to your grid in another post later!

Leave a Reply

Your email address will not be published. Required fields are marked *