Like any other game out there, providing an interface to relay instructions and messages to player is a must. This is accomplished by message box pop ups, sometimes accompanied by one or two buttons, depending on the nature of the message. In conventional programming, one only has to create a showMessageBox() function, or a class that encapsulates the function, and call it from anywhere in the game. Presto manifesto, you’ve got a message box.
Confirm prompt from Final Fantasy 8
Unfortunately, such task is a bit different in Unity, which is the engine I’m using to develop Gnome More War. Unity supports static classes and functions, which makes for the global methods that I can access anytime and from any scene. The main problem however is the message box user interface.
For those unfamiliar with Unity, it sets up the project by using scenes, and a scene is populated with game objects. Game objects are complex entities that can encapsulate sprites, scripts, audio, etc. Basically, if I want something displayed on the screen, it must be contained in a game object that must be loaded in the scene first. I’m not going to go through the basics of Unity here, as I’m assuming that the reader has at least intermediate experience with the engine. If you’d like to get familiarized with Unity, I suggest you start with these tutorial videos:
https://unity3d.com/learn
https://www.youtube.com/user/Brackeys
For those already familiar with Unity, you already know of the two simplest ways of getting a message box to pop up: 1. Create a message box object in the scene, hidden by default, and simply set it to active when needed, or 2. Create a message box prefab, instantiate it in a scene that needs it, destroy if no longer needed. There are more ways to it, but the simplest approach is basically to have a copy or instance of a message box game object for every scene. It may be fine if you’ve only got one scene, or if your message box will not appear in any other scene except one, but that’s not the case for my game.
I wanted a single instance of my message box object to be accessible from any of my game’s scenes. I wanted a simple, one liner code that commands my message box to show itself and display whatever message I tell it to.
So I wrote my MessageBox class. I’m not going to go into the details of the class, as I would have to explain its components and its ties to the rest of my game…which can get us off topic real fast. Just imagine that I have a class ready for use, with all the attached components. What’s important is how I managed to make this class (and the game object it is attached to) global and accessible from any scene. I used some concept known as a Grid.
Take note that this isn’t something I invented— it’s a long standing concept used in Unity to create singleton-like Monobehaviors.
/*
Grid - is a standard utility for all projects. It is the only practical way to have 'game manager MonoBehaviours' in Unity:
Grid.scoring.GiveBonus()
Grid.soundEffects.Explosion(13)
Grid.ai.GuessHand()
It's that simple.
How to use:
(0) This file Grid.cs itself is NOT attached to anything - it just sits there in your project
(i) Of course your project must have a "preload" scene. Every Unity project must have a "preload" scene.
(ii) In "preload" make a Gameobject "__holder". Mark "__holder" as DontDestroyOnLoad
(iii) Put your "manager" scripts, for example, Scoring.cs, Maps.cs, PayPal.cs on "__holder"
(v) Just fill-in the example code below for each of your scripts.
That's it.
You now access your "singleton-like MonoBehaviours" from anywhere like this ..
Grid.payPal.CheckBalance();
Grid.scoring.SaveToCloud();
That's all there is to it.
*/
using UnityEngine;
static class Grid
{
public static UIDialogBox dialogBox;
static Grid()
{
GameObject g;
g = safeFind("_holder");
dialogBox = (UIDialogBox)SafeComponent( g, "UIDialogBox" );
}
/* The following trivial routines just help this script work in a tidy manner. Note that when Grid wakes up, it automatically checks everything is in place. */
// If desired, you can type Grid.SayHello()
// anywhere in the project to confirm it is working
public static void SayHello()
{
Debug.Log("Confirming to developer that the Grid is working fine.");
}
private static GameObject safeFind(string s)
{
GameObject g = GameObject.Find(s);
if ( g == null ) BigProblem("The " +s+ " game object is not in this scene. You're stuffed.");
return g;
}
private static Component SafeComponent(GameObject g, string s)
{
Component c = g.GetComponent(s);
if ( c == null ) BigProblem("The " +s+ " component is not there. You're stuffed.");
return c;
}
private static void BigProblem(string error)
{
for (int i=10;i>0;--i) Debug.LogError(" >>>>>>>>>>>> Cannot proceed... " +error);
for (int i=10;i>0;--i) Debug.LogError(" !!! Is it possible you just forgot to launch from scene zero, the preload scene.");
Debug.Break();
}
}
//////////////////////////////////////////////////////////////////////////////
The code above doesn’t have to be attached to any game object in order to work. You will only need to keep it in your project. This Grid class will provide us easy access to any game objects we want to make global throughout the game. One example is me using it to have a message box object that can easily be called from anywhere:
Grid.messageBox.ShowDialog(MessageBox.TYPE.TYPE_CLOSE, "Warning", "Coming soon...");
Message box called from the “War Room” scene
I said earlier that I wanted only a single instance of my message box object, as opposed to multiple instances across every scene. In order to make this possible I actually need one instance of it existing in one of my scenes.
When developing games with Unity, it is common to have a preload scene— this is where you put all singly initialized objects, and is loaded before any of the gameplay scenes. Our Grid should be located in this scene.
In my case, my preload scene is the splash scene. It’s the first scene of the game, and it doesn’t really do anything else besides show the studio logo and move to the next screen after a few seconds. I created a game object _holder in this scene. This is a persistent game object that will exist across all scenes. In Unity, when a new scene is loaded, all objects of the current scene will be destroyed. Our _holder game object will normally be destroyed once we leave the splash scene. To prevent this, we must mark it with DontDestroyOnLoad().
using UnityEngine;
using System.Collections;
public class DontDestroyOnLoad : MonoBehaviour
{
void Awake() {
DontDestroyOnLoad(transform.gameObject);
}
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
}
}
Just attach the above script to the _holder game object and it is guaranteed that this game object will never be destroyed when a new scene is loaded.
My _holder game object will contain all the global game objects I have in my game; that includes the message box.
“dialog_prompt” is my message box game object
In order to have access to this game object from a script, I have to attach my message box script somewhere. If we follow the existing Grid class, each of these children are accessed as components. There are many ways to go about this, but I chose to attach the scripts to my _holder game object.
“UIDialogBox” is my message box script
The code of UIDialogBox encapsulates the necessary methods for showing and hiding my message box. Since Grid is a static class, it will be initialized on the first call to it. Recall this constructor:
static Grid()
{
GameObject g;
g = safeFind("_holder");
dialogBox = (UIDialogBox)SafeComponent( g, "UIDialogBox" );
}
So once you tried to access Grid from any other scripts in your project (for the first time):
Grid.dialogBox.ShowDialog(UIDialogBox.TYPE.TYPE_YES_NO, "Confirm", "Unlock map, are you sure?");
The static constructor will kick in. If you analyse the constructor, you’ll see that it will look for a game object named “_holder” in the current scene. It will be found as long as it exists in the scene, and we can be sure that it will be there since we marked it with DontDestroyOnLoad(). Once a reference is established, the component of name “UIDialogBox”, which is the attached script you see above, will be referenced. The calling script will now have access to the public static object dialogBox.
It’s not overly complicated, and it makes sense. It is also not limited to just message boxes. I myself have included some in-game menus, a loading screen, and more, into the Grid. Just remember that the core idea is to have a single instance of a game object that you plan on calling from many scenes in your project.
You may be wondering what all of this has to do with a “grid”. The concept can be likened to a power grid of sorts, as we are turning persistent game objects on and off (show and hide). In reality, you can name your Grid class whatever you want.
I’d like to give credit to my source— as I have been doing my research on instantiating a prefab from a static function, I came across this:
http://answers.unity3d.com/questions/551297/how-can-i-instantiate-a-prefab-from-a-static-funct.html
Cheers!
0 comments