Level editor – toggling editor, GUI, and selecting additional tiles

Now that we have the basic functionality in, it wouldn't be that enjoyable if all we could do was added and remove walls. We also want to be able to spawn collectibles and change the player's starting location. Let's work on that next:

  1. Back in MonoDevelop in the LevelEditor class, we're going to want to first add in an OnGUI function to display the types of things we can create:
    void OnGUI()
    {
      GUILayout.BeginArea(new Rect(Screen.width - 110, 20, 100, 800));
      foreach(Transform item in tiles)
      {
        if (GUILayout.Button (item.name))
        {
          toCreate = item;
          
        }
      }
      GUILayout.EndArea();
    }
  2. The OnGUI function is called for rendering and handling GUI events using IMGUI which is a code-driven GUI system. While it was the way to create GUIs before Unity 4.6, it currently is primarily used by programmers for tools development and custom inspectors for scripts or new editors to extend Unity itself.

The anatomy of a IMGUI control

The most important concept to grasp first is the GUILayout.Button function. This is what we refer to as a GUI control, and there are many others that we will be using in the future. So, to clear up any confusion, let's talk about how all of these work now.

Creating any GUILayout control consists of the following:

ControlType(Content)

The parts of the preceding line of code are explained in the following sections:

ControlType

The ControlType function is a function that exists in Unity's GUI and GUILayout class, and is the name of the element that you want to create in the world. In the preceding code, we used GUILayout.Button, but there are many more.

Content

The argument for the control is the actual content that we want to display with the ControlType we are using. Right now, we're just passing in a string to display, but we can also display images and other content as well, including other controls. We will talk about other pieces of content that we can add in later.

GUI.Button

One of the most common UI elements is the Button control. This function is used to render a clickable object. If you look at the code we just wrote, you'll note that the button is cased inside of an if statement. This is because if the button is clicked on, then the function will return true when the button is released. If this is true, we will set toCreate to whatever object has that name.

GUILayout

By default, the GUILayout class will just put the buttons up at the top-left side, but we may also want our objects to be grouped together So, I specify an area that I want the menu to be in using the BeginArea function. Anything I place before I call the EndArea function will be inside that area, and GUILayout will attempt to place it in a pleasing way for me.

If you want to have precise control on where and how things are drawn, you can make use of the GUI class. However, if you do not want to manually specify a position and are okay with Unity automatically modifying the size and position of controls, you can use the GUILayout class that adds a parameter for a position.

Note

For more information on IMGUI check out http://docs.unity3d.com/Manual/GUIScriptingGuide.html.

  1. Next, inside our GameController class, add the following code to our Update function (create the function as well if it doesn't exist in your current implementation, such as the example code):
    void Update()
    {
      if(Input.GetKeyDown("f2"))
      {
        this.gameObject.GetComponent<LevelEditor>().enabled = 
        true;
      }
    }

    Now, if we move back to the game and press the F2 key, you'll see that a menu pops up, which we can then select items from. This works fine for the walls and the collectibles, but there's a bit of an issue with the player and the collectibles. Take a look at the following screenshot:

    GUILayout

    As you can see, we are spawning more players that all respond to player input, and the number of collectibles on our screen are not reflected properly in our text. We will solve both of these issues now. We will first create a new object named PlayerSpawner, which will act as the place where the player will start when the game starts, and make it such that we can only have one of them.

  2. In Project Browser, select Create | New Material. Rename it to PlayerSpawn by clicking on the name of the material in the project browser, typing in the new name and then pressing Enter.
  3. With the PlayerSpawn object selected, set the Rendering Mode to Transparent so that we can make the material semitransparent. Then, change the Main Color property to a red color with a low alpha value.

    If all goes well, it should look like the following screenshot:

    GUILayout
  4. Now, let's create a cube to act as the visual representation of our level by going to GameObject | 3D Object | Cube. Once the object is created, give it a name, PlayerSpawn. Switch to the Scene view if you haven't so that you can see the newly created object.
  5. Under the Mesh Renderer component, set the Materials | Element 0 property to our newly created PlayerSpawn material. Take a look at the following screenshot:
    GUILayout
  6. Next, go to the Scripts folder and create a new C# script named PlayerStart. Once that's finished, open your IDE and use the following code for the file:
    using UnityEngine;
    using System.Collections;
    
    public class PlayerStart : MonoBehaviour 
    {
      //A reference to our player prefab
      public Transform player;
    
      //Have we spawned yet?
      public static bool spawned = false;
      
      public static PlayerStart _instance;
      
      // Use this for initialization
      void Start () 
      {
        // If another PlayerStart exists, this will replace it
        if(_instance != null)
          Destroy(_instance.gameObject);
        
        _instance = this;
    
        // Have we spawned yet? If not, spawn the player
        if(!spawned)
        {
          SpawnPlayer();
          spawned = true;
        }
      }
      
      void SpawnPlayer()
      {
        Transform newObject = Instantiate(player, 
                                    this.transform.position,
                                    Quaternion.identity) as Transform;
        
        newObject.name = "Player";
      }
    
    }
  7. Back in the editor, attach our new component to the PlayerStart object in Hierarchy. Then, back in Inspector, set the Player variable to our Player prefab.
  8. Finally, in the Box Collider component, check the Is Trigger property.
    GUILayout
  9. Now, drag the PlayerStart object from Hierarchy to the Prefabs folder to make it a prefab. Then, delete the object from Hierarchy.
  10. Next, select the GameController object and assign the PlayerStart prefab where you used to see the player in Tiles | Element 1. Save your scene and play the game. Take a look at the following screenshot:
    GUILayout

    We can now select the PlayerStart object from the button and place it wherever we want, and there will always just be one. Also, once we have levels saving/loading, the code will properly spawn the player wherever the PlayerStart object is placed!

  11. Now, to update the number of orbs we have in the level, we need to open GameController and add in a new function, as follows:
    public void UpdateOrbTotals(bool reset = false)
    {
        if (reset)
        orbsCollected = 0;
    
        GameObject[] orbs;
        orbs = GameObject.FindGameObjectsWithTag("Orb");
    
        orbsTotal = orbs.Length;
      
        scoreText.text = "Orbs: " + orbsCollected + "/" + orbsTotal;
    }
  12. Now that we have this function written, we need to call it every time we do something to modify our level. Go to the LevelEditor class and add the following line to the end of our Start function:
    GameController._instance.UpdateOrbTotals(true);
  13. Then, inside the Update function, we'll need to add the following lines in bold:
        void Update()
        {
            // Left click - Create object
            if (Input.GetMouseButton(0) && GUIUtility.hotControl == 0)
            {
                Vector3 mousePos = Input.mousePosition;
    
    /*
    Set the position in the z axis to the
    opposite of the camera's so that the position is on the
    world so ScreenToWorldPoint will give us valid
    values.
    */
                mousePos.z = Camera.main.transform.position.z * -1;
    
                Vector3 pos = Camera.main.ScreenToWorldPoint(mousePos);
    
                // Deal with the mouse being not exactly on a 
                // block
                int posX = Mathf.FloorToInt(pos.x + .5f);
                int posY = Mathf.FloorToInt(pos.y + .5f);
    
                Collider[] hitColliders = Physics.OverlapSphere(pos, 0.45f);
                int i = 0;
                while (i < hitColliders.Length)
                {
                    if (toCreate.name != hitColliders[i].gameObject.name)
                    {
                        DestroyImmediate(hitColliders[i].gameObject);
                    }
                    else
                    {
                        // Already exists, no need to create 
                        // another
                        return;
                    }
                    i++;
                }
    
                CreateBlock(tiles.IndexOf(toCreate) + 1, posX, posY);
                
                GameController._instance.UpdateOrbTotals();
            }
    
            // Right clicking - Delete object
            if (Input.GetMouseButton(1) && GUIUtility.hotControl == 0)
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    
                RaycastHit hit = new RaycastHit();
    
                Physics.Raycast(ray, out hit, 100);
    
                // If we hit something other than the player, we 
                // want to destroy it!
                if ((hit.collider != null) && (hit.collider.name != "Player"))
                {
                    Destroy(hit.collider.gameObject);
                }
    
                GameController._instance.UpdateOrbTotals();
            }
    
        }
  14. Save the file, save your project, and start the game. Press F2 to open our menu and then draw. Take a look at the following screenshot:
    GUILayout

As you can see, we're now able to draw over the other object and place everything that we want for our level!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset