Software Archive
Read-only legacy content
17060 Discussions

Unity Tip: Creating A World Controller For Your RealSense Application

MartyG
Honored Contributor III
388 Views

Hi everyone,

When one writes an application, they tend to make the functions of it relatively isolated from each other, limiting the effect of those functions' variables to a specific script.  You can however make your application more efficient by enabling the status of variables to be shared with other scripts, and read those statuses from a single central location - a "World Controller".

Global monitors are typically used by large and complex programs ranging from Windows to 'World of Warcraft' to keep track of what the most important data in an application is doing, perform analysis on that data and take corrective actions if required to prevent a failure from becoming visible to the user.

Let's use a driving game as an example.  If a computer-controlled car happened to leave the road and become stuck somewhere, the World Controller could recognize that the car has departed from its allowed boundaries and teleport it back onto the road at the closest available location.

In this guide, we will show how to make Unity variables shareable and construct a simple World Controller interface to monitor them.  We will be writing code in the C# programming language.

STEP ONE

The first step is to choose a variable in your project that you want to use as your initial test for getting the sharing of variables up and running.  Once you have successfully set up such a sharing the first time then you will have a template that you can apply to making other project variables shareable.

As an example in this guide, I will use the speed of a passenger train in my RealSense-powered game project as the variable to be shared.

In the original version of the train's speed routine, the movement values were written in the format that many developers use to position-move a Unity object - putting numeric values for direction axes X, Y and Z in a bracket within a 'transform.Translate' positional movement instruction.

transform.Translate(0, 5, 0);

This statement instructs Unity to move the object (our train in this case) horizontally along the Y axis in a straight line at a speed of 5 coordinate points per processing update frame.  So if the script used a "fire once and stop running" Start() type function, the object would move 5 coordinate points along that axis and stop.  If the script used an infinitely-looping Update() type function however, the object would continuously move 5 coordinate points every second until the script stopped running.

The drawback with using absolute numeric values such as '5' though is that it is very inflexible: you cannot change that specific value whilst the program is running, and you cannot share it with other scripts.  If we create a variable to represent that value and store our number within it, then it becomes much easier to alter that value and transport it elsewhere in the application.

STEP TWO

The process for defining a new variable in a C# script usually begins at the line directly below the opening 'public class' statement (the "header" section of the script).  Example:

using UnityEngine;
using System.Collections;

public class MovingTrain : MonoBehaviour {

VARIABLE DEFINITION GOES HERE

The initial setup of a variable is typically the word 'public', followed by a term that describes the type of data that the variable will be storing.  Types of variable include:

public int - stores 'integer' numbers, which are typically whole numbers without a decimal point.

public float - stores 'float' numbers, which are typically numbers with a decimal-point value such as '1.5'.  In C#, decimal numbers have a letter 'f' to represent float placed directly after the number, so that Unity recognizes it as a decimal number and knows how to process it.  So '1.5' becomes '1.5f'.

public string - stores alphanumeric terms such as the name of something or someone.  For example, if you created a variable to store the name of the player, then you might call the variable 'Player_1_Name' and store the word "Harry" within that variable.  When the program accessed the 'Player_1_Name' variable, it would display the word "Harry" on the screen as plain-text.

public bool - stores logical "Boolean" statements, such as whether something is 'true' or 'false'.

In my own project, the train speed value is an integer number, so the 'int' type of variable was used, storing the value in a variable named 'TrainSpeed' that was defined directly beneath the opening 'public class' statement of the script.

using UnityEngine;
using System.Collections;

public class MovingTrain : MonoBehaviour {

public int TrainSpeed;

It is always best to give the variables names that describe what they do at a glance, especially as the variables are destined to be displayed in our World Controller monitoring system.

STEP THREE

Having created the initial definition for the variable, we now need to tell Unity what will be stored inside the variable by default when the program first starts running.  To do this, we can create a run-once Start() function directly beneath the line where the variable was first defined.

The basic format for specifying the contents of a variable within the Start() function is:

[VARIABLE NAME] = [CONTENTS];

in the case of the train speed variable, we want the TrainSpeed variable to store our speed value of '5' when the program first runs.

using UnityEngine;
using System.Collections;

public class MovingTrain : MonoBehaviour {

public int TrainSpeed;

public void Start() {

TrainSpeed = 5;

}

Important note: you will see in the script above that the void Start() function has the word 'public' in front of it, so that it reads as 'public void Start()'.  The word 'public' tells Unity that the contents of the Start() function are able to be shared "publicly" with other scripts.  If it just said 'void Start()' then all of the code contained within the opening and closing brackets of the Start() function would be treated by Unity as being private, and not accessible by other scripts.

If you optionally want to be able to change the value of a variable whilst the program is running then you can create custom functions within your script - functions that contain non-standard names that you have defined yourself, instead of the usual Start(), Update(), etc.  These are treated by Unity as being fire-once scripts, in the same way that Start() functions are.  You can then send an activation call to that specific function in the script, ignoring the other functions, to change the value stored in the variable.

In our train script, a custom function called 'ReduceTrainSpeed()' was defined that contained an instruction to make the TrainSpeed variable equal to '4' instead of the default '5', causing the train to slow down a little.

using UnityEngine;
using System.Collections;

public class MovingTrain : MonoBehaviour {

public int TrainSpeed;

public void Start() {

TrainSpeed = 5;

}

public void ReduceTrainSpeed() {

TrainSpeed = 4;
   
}

STEP FOUR

You may want your variable to only be updated when an activation signal is sent to a custom function such as the one above.  A common example would be sending an activation to a function that tells the script to add '5' to a value representing a game score when the player does something that scores points.  If the variable was within a constantly-looping Update() function then the score would keep increasing upwards from that point on even if the player was not currently doing anything that awarded points.

We would therefore place the instruction that uses the variable within the Start() function, directly beneath our definition of what the variable should contain.  In the case of our train speed script example, the position-changing equation would be inserted.

using UnityEngine;
using System.Collections;

public class MovingTrain : MonoBehaviour {

public int TrainSpeed;

public void Start() {

TrainSpeed = 5;

transform.Translate(0, 5, 0);

}

At present, we have not told Unity what to do with the variable, and so it cannot affect the program at all.  What we need to do is replace the value in the instruction with the variable that represents it.  So in our example, we want the speed value '5' to be represented by the variable 'TrainSpeed':

transform.Translate(0, TrainSpeed, 0);

When the program runs, Unity will look at the Start() function first and see that the TrainSpeed variable is equal to the numeric value '5'.  Because the TrainSpeed variable is quoted in the position-calculating equation, Unity will substitute the value stored in TrainSpeed into the equation in place of the variable name.  So whilst we may see the instruction as:

transform.Translate(0, TrainSpeed, 0);

Unity sees it as:

transform.Translate(0, 5, 0);

If you want your variable to be continuously updated instead of updated only when an activation signal is sent to a custom function, place the instruction inside an Update() function instead of inside the 'Start()' block of code:

using UnityEngine;
using System.Collections;

public class MovingTrain : MonoBehaviour {

public int TrainSpeed;

public void Start() {

TrainSpeed = 5;

}

public void Update() {

transform.Translate(0, 5, 0);

}
}

Again, as with the Start() function, ensure that the Update function uses the format 'public void Update()' so that its contents are accessible by other scripts.

STEP FIVE

With the variable set up and working correctly, we are now ready to begin constructing our World Controller script to monitor the status of the variable, display that status in a single list of variables in the Inspector panel, and optionally take action based on the current status of the variable.

Find an empty space within your Unity project's central construction stage (e.g beneath the surface of a game world) and create a simple object such as a Sphere that the World Controller's script will be stored within.

1.jpg

2.jpg

STEP SIX

Create a new C# script file for the World Controller within the new object.  Give it a descriptive name as as "WorldController".

3.jpg

STEP SEVEN

Highlight the new object to display its settings in the right-hand Inspector panel of Unity.  Locate the newly created script file in the Inspector and right-click on the small gear-wheel icon beside the script name to bring up its menu, and select the 'Edit Script' option.

4.jpg

STEP EIGHT

The script will open in Unity's script editor program, showing a default block of code that Unity places in every new C# script.

5.jpg

Edit out the gray // notes, as these are not required by the script and are just for the developer's information.

6.jpg

Add the word 'public' in front of the Start() and Update() functions.

7.jpg

STEP NINE

Use the Enter / Return key to open up some empty space beneath the opening 'public class' statement.  Type the forward slash symbol - / - twice to define a section whose contents will be regarded by Unity as being information only and not code to be acted upon.  Place a descriptive name after the // for the variable that you are going to be accessing.

using UnityEngine;
using System.Collections;

public class WorldController : MonoBehaviour {

// TRAIN 1 SPEED





	public void Start () {
	
	}
	

	public void Update () {
	
	}
}

It is a good habit to get into to notate each of the variables that you add to the World controller, as it makes it much faster to find the code elating to a particular variable once you have a long list of variables being tracked in your Controller.

As we did earlier in the script containing the original train speed variable, we need to define a new variable name in the header of the World Controller script.  This variable will store a copy of the original variable's value within it, and it is this copied value that will be displayed in the list of values in the World Controller.

In our example, we create a new 'float' format variable in the header of the World Controller script and name it 'Train_1_Speed'.

using UnityEngine;
using System.Collections;

public class WorldController : MonoBehaviour {

// TRAIN 1 SPEED

public float Train_1_Speed;


public void Start () {
	
}
	

public void Update () {
	
}
}

STEP 10

Because the original variable is located in a different script file, and in a different object, we need to tell Unity how to find that object and then find the script to be accessed and the specific variable to be copied.  The most efficient way to do this in Unity is to create a "tag" name for the object that we will be looking for.

Highlight the object that contains the script with the variable that needs to be tracked by the World Controller.

Below is an image of the 'Train 1' in our game, and its attached passenger carriage.

8.jpg 

At the top of the right-hand Inspector panel is an option called 'Tag', with a drop-down menu beside it that is set to 'Untagged' by default.

9.jpg

Left-click on the menu to drop down a short list of pre-defined tag names, with an 'Ad Tag' option at the base of the list.  Left-click on this option to select it.

10.jpg

The list of tags will be displayed again, this time in the form of text boxes that can be edited.  Ignore these boxes and click on the '+' icon at the base of the list to create an empty slot for a new tag.

In the newly created text box, type a tag name that is descriptive of the object that is being tagged (e.g 'Train 1' in our case).

11.jpg

With the tag defined, highlight the object that is to be tagged, which will return the tag-name of the object to the default 'Untagged'.  Left click on it again to drop down the list of tags and select the tag name at the base of the list that you just defined.

The tag name of the object will now update.

12.jpg

STEP ELEVEN

Having tagged the object containing the variable to be read, we can now return to the World Controller script and set up an instruction to tell Unity how to use the tag to find that object.

Return to the header section of the World Controller script.  Directly beneath the variable we defined earlier that will store a copy of the accessed variable from the other script, we need to add a second variable that will tell Unity how to use the tag name to find the object that the other script is stored in.

Here, we introduce a new type of variable called 'GameObject'.  Instead of storing a number or an alphanumeric name like the types we looked at earlier, a GameObject type variable deals with the finding of objects within a project.

It is usually a good habit to give a unique name to variables that change often, since we do not want the program to accidentally change a variable to a wrong value because the same variable name is used elsewhere in the project.  This is not the case with GameObject variables though.  Because their contents are static - they just contain the location of the object, which never changes - you can use the same variable name for all references of a particular GameObject's location.  This makes for neater, more easily readable code.

In our example, we give our GameObject variable the name 'Train1'.

using UnityEngine;
using System.Collections;

public class WorldController : MonoBehaviour {

// TRAIN 1 SPEED

public float Train_1_Speed;

private GameObject Train1;


public void Start () {
	
}
	

public void Update () {
	
}
}

You will notice that we have used the term 'private' instead of 'public' with our tag variable.  This would seem to go against our previous advice about using the 'public' type for variables.  In the case of GameObject type variables though, we do not need them to be shareable as they are just a location address for objects, like an internet URL address.  There is nothing within those variables that we need to be able to change, and so they can be 'private'.

The main reason for making GameObject variables private though is that all public variables for an object are listed in the Inspector panel.  We want to be able to look down the list of variables monitored by the World Controller at a glance.  If the GameObject variables were public then they would be included in the list, along with the monitored variables (the data that is most important to us), and it would make the list much harder to read quickly.

Another deviation from the procedure that we followed with the original script containing the variable is that this time, we do not need to place in the Start() section a definition for what the value of the copied variable (Train1_Speed) is.  This is because we are obtaining that value from the original script via the variable share, and so defining a new value for that variable in the World Controller would render the World Controller pointless.

STEP TWELVE

Go down to the Start() function and create a definition for the contents of the private GameObject variable. Unity locates tagged objects using an instruction called 'FindWithTag'.

The precisely spelled tag name of the object that we want Unity to communicate with, including upper and lower case letters, is placed inside the quotation marks of the FindWithTag instruction.  Be careful that you do not make the easy mistake of placing the object's Hierarchy panel name in the quotations instead of that object's TAG name.

using UnityEngine;
using System.Collections;

public class WorldController : MonoBehaviour {

// TRAIN 1 SPEED

public float Train_1_Speed;

private GameObject Train1;


public void Start () {

Train1 = GameObject.FindWithTag ("Train 1");
	
}
	

public void Update () {
	
}
}

STEP THIRTEEN

Now that Unity knows how to locate the object containing the variable whose value we want to read and copy, we need to specify what Unity should do with that information once it has it.

In most cases, we want the World Controller to be able to monitor a variable's status in real time so that it can act on the very latest information available about that variable.  If it were in the Start() section then Unity would only check the variable once, copy its value into the World Controller's list of variable values and then never update it again for the rest of the time that the program was running.  

That means that we need to place our monitoring instruction in the infinitely-looping Update() section of the World controller script.

Things get a bit complicated here.  We have to define an instruction that draws information from multiple sources.  Let's look at our command for accessing the Train Speed variable from the script inside our train vehicle and break it down into parts.

Train_1_Speed = Train1.GetComponent<MovingTrain> ().TrainSpeed;

TRAIN_1_SPEED =

At the beginning of the instruction, we specify the name of the variable that we defined in the header of the World Controller script.  The value of the variable from the original script is copied into this variable.

TRAIN1.GETCOMPONENT

In the header section, we defined a variable called 'Train1' that would represent the location of our tagged object.  Then in the Start() section, we specified that the address stored inside 'Train1' was 'GameObject.FindWithTag("Train 1");

Like the speed variable that we defined in the original script, the contents of the 'Train1' variable are substituted into an instruction wherever that variable name is placed.  So Train1.GetComponent is actually seen by Unity in its non-shorthand form as:

GameObject.FindWithTag("Train 1").GetComponent

<MOVINGTRAIN>()

Directly after the above instruction in triangular brackets, we place the name of the script that the variable that we are seeking to read and copy is stored in.  The script inside our train that contained the speed variable was called MovingTrain, so this is the name that we placed in the brackets.

Always remember to include the circular open and closing brackets - ( ) - outside of the triangular end-bracket to let Unity know that it is a script that it is looking for and not an object or a variable.

.TRAINSPEED;

Finally after the target script's name, a full-stop followed by the name of the variable that we want to read and copy is specified, with a semi-colon on the end to complete the instruction.

So our script now looks like this:

using UnityEngine;
using System.Collections;

public class WorldController : MonoBehaviour {

// TRAIN 1 SPEED

public float Train_1_Speed;

private GameObject Train1;


public void Start () {

Train1 = GameObject.FindWithTag ("Train 1");
	
}
	

public void Update () {

Train_1_Speed = Train1.GetComponent<MovingTrain> ().TrainSpeed;
	
}
}

STEP FOURTEEN

Select the object that the World Controller script is contained within and look at its Inspector panel settings.  Inside the section of the Inspector containing the details of the World Controller script, a list will now have appeared that shows the variable that the world Controller script is tracking.  The default value will tend to be blank or a '0' depending on the type of the variable, as the Controller can only obtain the information required to fill int he list's fields once the program is running.

13.jpg

Once the program is run, the variable automatically updates to display the default value of that variable.  In the case of our train speed script, you will remember that this default was '5' - and this is indeed what is displayed in the Controller during the running of the program.  Success!

14.jpg

STEP FIFTEEN

Once the variable that is being tracked by the World Controller is updating correctly, you can add additional instructions to the Update() section beneath each variable to look at that value and trigger actions if that variable's status falls into a certain range.  In our project containing the train, for example, we created a test instruction that would print in Unity's debug console the message "The train is going real fast!" if the train speed variable's value was detected to have exceeded its initial value of '5'.

public void Update () {

Train_1_Speed = Train1.GetComponent<MovingTrain> ().TrainSpeed;

if (Train_1_Speed > 5) {
Debug.Log ("The train is gong real fast!");
}

}

At the time of writing this guide, our own World Controller tracked six variables, with many more yet to be added.  These included:

- Train speed

- Avatar walk speed (the avatar walks forwards when the real-life head is lifted in front of the camera, and can walk backwards when the player leans back in their chair).

- The current emotional state of the avatar.

This is calculated by storing the angles of certain facial parts of the avatar in the World Controller,.  the facial angles are determined by the emotions expressed on the player's face and picked up by the camera.  The controller then uses logic statements to make decisions on the emotional state of the avatar based on a combination of the status of more than one facial part.  

For example, if the mouth angle and eyebrow angle are both above a certain angle then this is deemed a 'happy' state, whilst if they are both below a certain angle then that is deemed an 'unhappy' emotional state.

- The speed at which the sun and moon were moving through the sky (this is normally static, but it can be accelerated during certain sequences such as fast day-to-night transition).

- The rate at which the color of the sky is changing from day to night and back again (again, this is normally a static value but can be changed during fast-transition animation sequences).

- Whether the avatar has been leaned so far forwards towards the ground by RealSense camera inputs that it is likely to become off-balance.

The angle of the waist and the head are checked to determine the probability of whether the avatar is likely to have its feet tip over if the player tries to lean it forwards any further without using the camera to place the avatar hands on the ground for support.  It could also be used to determine how likely the avatar is to fall over if it lands from a jump as its weight compels the waist to fall forwards before the player has a chance to correct themselves and stand up straight again after their feet have landed on the ground.

Here is an image of our World Controller at the time of writing with live-updating statuses.

16.jpg

If you have any questions about this guide, please feel free to ask them in the comments below.  Good luck!

0 Kudos
1 Reply
MartyG
Honored Contributor III
388 Views

I added "Motivation" to my game's World Controller as a factor in the emotion system, as motivation influences how effectively a computer-controlled character's AI carries out a task.  It can also potentially influence factors affecting the player's own avatar, such as how quickly it walks (less motivation = a slower pace)..

This required an interesting debate about the workings of motivation in a real person and how that could be simulated in the game.  In the first version of this system, I created a complex mechanism of rising and falling numbers that responded to events and facial expressions detected by the RealSense camera, with various number ranges representing Low, Neutral and High motivation.

I realized though that this would make the characters' emotions rather false.  There was also potential for the player to "game" the numbers by repeatedly doing actions such as frequent smiling that kept the numbers in a beneficial range and so conferred to them in-game benefits that had not honestly been earned.

In reality, the emotions of the average person do not bounce wildly up and down from one extreme to another.  Instead, they fall into a particular zone and then stay there until something significant happens to change their emotional state for a certain time until something else happens to either enhance or decrease their mood.

The two principal characters in the game are the squirrel-guy that has often featured in my guides (named Lannel) and his mother, Mrs Pharaoh.  I therefore defined Motivation variables in the header of the World Controller for the two of them, giving the variables the 'String' type so that they could be displayed as the plain-text words "Low", "Neutral" or "High" in the World Manager's list of variables.

// MOTIVATION

// Lannel

public string Motivation_Lannel;

// Mrs P

public string Motivation_Mrs_P;

At the very bottom of the World Controller script, outside of the infinitely-looping Update() function, I defined a set of six custom "fire once and stop" functions (Low, Neutral and High motivation), three each for Lannel and Mrs Pharaoh. .

// Lannel

public void LowMotivation_Lannel() {

Motivation_Lannel = "Low";

}

public void NeutralMotivation_Lannel() {

Motivation_Lannel = "Neutral";

}

public void HighMotivation_Lannel() {

Motivation_Lannel = "High";

}

// Mrs P

public void LowMotivation_MrsP() {

Motivation_Lannel = "Low";

}

public void NeutralMotivation_MrsP() {

Motivation_Lannel = "Neutral";

}

public void HighMotivation_MrsP() {

Motivation_Lannel = "High";

}

Placing Lannel or his mother in a particular state of Motivation would now be a simple matter of sending an activation signal to the appropriate motivation function from a script outside of the World Controller in response to a particular in-game event.

The Motivation variable is designed to be just one part of the set of equations governing the characters' emotions and how they are affected by them, just like a credit worthiness report in the real world is made up of a number of factors that are taken into consideration by lenders.  A Low, Neutral or High motivation might not have a significant impact on gameplay, but combined with other emotional variables such as "Happy" and "Sad", they can help to give the characters a deeper, richer personality that is easier for the player to relate to as a real person.

For example, if a computer-controlled character such as Mrs Pharaoh has neutral or low motivation then their AI will make them more prone to wandering away from a task such as cooking in the kitchen and sit down in the lounge to watch the TV for a bit or go off to her bedroom to read a book.  The current motivational state will also influence which tasks she selects from her internal "to-do list" to attempt next.  Certain tasks on that list will not be chosen because she is not motivated enough at that time for the energy that a certain task requires.

The final setup step was to go to the Start() section of the World Controller and set the Motivation variables to have a default status of "Neutral" when a new game is started.

// MOTIVATION START-OF-GAME DEFAULTS

Motivation_Lannel = "Neutral";
Motivation_Mrs_P = "Neutral";

When the game is run, we can see from the World Controller that, just as we programmed, the characters' Motivation level begins at the 'Neutral' stage.  Also, we see that Lannel is in danger of losing his balance and toppling forward if he does not do something to steady himself, as indicated by the tick that has appeared in the 'Avatar_Off_Balance' variable!

1.jpg

 

 

 

 

0 Kudos
Reply