What You’ll Learn in This Hour:
► How to write methods
► How to capture user input
► How to work with local components
► How to work with game objects
In Hour 7, “Scripting, Part 1,” you learned about the basics of scripting in Unity. In this hour, you’ll use what you have learned to complete more meaningful tasks. First, you’ll examine what methods are, how they work, and how to write them. Then you’ll get some hands-on practice with user input. After that, you’ll examine how to access components from scripts. You’ll wrap up the hour by learning how to access other game objects and their components with code.
Methods, often called functions, are modules of code that can be called and used independently of each other. Each method generally represents a single task or purpose, and often many methods can work together to achieve complex goals. Consider the two methods you have seen so far: Start
and Update
. Each represents a single and concise purpose. The Start
method contains all the code that is run for an object when the scene first begins. The Update
method contains the code that is run every update frame of the scene (which is different from a render frame, or “frames per second”).
Before working with methods, you should look at the different parts that compose them. The following is the general format of a method:
<return type> <name> (<parameter list>) { <Inside the method’s block> }
Every method must have a unique name (sort of see the “Method Signature” section below). Though the rules that govern proper names are determined by the language used, good general guidelines for method names include the following:
► Make a method name descriptive. It should be an action or a verb.
► Spaces are not allowed.
► Avoid using special characters (for example, !
, @
, *
, %
, $
) in method names. Different languages allow different characters. By not using any, you avoid risking problems.
Method names are important because they allow you to both identify and use them.
Every method has the ability to return a variable back to whatever code called it. The type of this variable is called the return type. If a method returns an integer (a whole number), the return type is an int
. Likewise, if a method returns a true
or a false
, the return type is bool
. If a method doesn’t return any value, it still has a return type. In that instance, the return type is void
(meaning nothing). Any method that returns a value will do so with the keyword return
.
Just as methods can pass a variable back to whatever code called it, the calling code can pass variables in. These variables are called parameters. The variables sent into the method are identified in the parameter list section of the method. For example, a method named Attack
that takes an integer called enemyID
would look like this:
void Attack(int enemyID) {}
As you can see, when specifying a parameter, you must provide both the variable type and the name. Multiple parameters are separated with commas.
The combination of a method’s return type, name, and parameters list is often referred to as a method’s signature. Earlier in this hour I mentioned that a method must have a unique name, but that isn’t exactly true. What is true is that a method must have a unique signature. Therefore, consider these two methods:
void MyMethod() {} void MyMethod(int number) {}
Even though these two methods have the same name, they have different parameter lists and thus are different. This practice of having different methods with the same name can also be called overloading a method.
The method block is where the code of the method goes. Every time a method is used, the code inside the method block executes.
When you understand the components of a method, writing them is easy. Before you begin writing methods yourself, take a moment to answer three main questions:
► What specific task will the method achieve?
► Does the method need any outside data to achieve its task?
► Does the method need to give back any data?
Answering these questions will help you determine a method’s name, parameters, and return data.
Consider this example: A player has been hit with a fireball. You need to write a method to simulate this by removing 5 health points. You know what the specific task of this method is. You also know that the task doesn’t need any data (because you know it takes 5 points) and should probably return the new health value back. You could write the method like this:
int TakeDamageFromFireball() { int playerHealth = 100; return playerHealth - 5; }
As you can see in this method, the player’s health is 100, and 5 is deducted from it. The result (which is 95) is passed back. Obviously, this can be improved. For starters, what if you want the fireball to do more than 5 points of damage? You would then need to know exactly how much damage a fireball is supposed to do at any given time. You would need a variable, or in this case a parameter. Your new method could be written as follows:
int TakeDamageFromFireball(int damage) { int playerHealth = 100; return playerHealth - damage; }
Now you can see that the damage is read in from the method and applied to the health. Another place where this can be improved is with the health itself. Currently, players can never lose because their health will always refresh back to 100 before having damage deducted. It would be better to store the player’s health elsewhere so that its value is persistent. You could then read it in and remove the damage appropriately. Your method could then look like this:
int TakeDamageFromFireball(int damage, int playerHealth) { return playerHealth - damage; }
By examining your needs, you can build better, more robust methods for your game.
Once a method is written, all that is left is to use it. Using a method is often referred to as calling or invoking the method. To call a method, you just need to write the method’s name followed by parentheses and any parameters. So, if you were trying to use a method named SomeMethod
, you would write the following:
SomeMethod();
If SomeMethod()
requires an integer parameter, you call it like this:
// Method call with a value of 5 SomeMethod(5); // Method call passing in a variable int x = 5; SomeMethod(x); // do not write “int x” here.
Note that when you call a method, you do not need to supply the variable type with the variable you are passing in. If SomeMethod()
returns a value, you want to catch it in a variable. The code could look something like this (with a Boolean return type assumed; in reality, it could be anything):
bool result = SomeMethod();
Using this basic syntax is all there is to calling methods.
Without player input, video games would just be video. Player input can come in many different varieties. Inputs can be physical—for example, gamepads, joysticks, keyboards, and mice. There are capacitive controllers such as the relatively new touch screens in modern mobile devices. There are also motion devices like the Wii Remote, the PlayStation Move, and the Microsoft Kinect. Rarer is the audio input that uses microphones and a player’s voice to control a game. In this section, you’ll learn all about writing code to allow the player to interact with your game by using physical devices.
With Unity (as with most other game engines), you can detect specific key presses in code to make it interactive. However, doing so makes it difficult to allow players to remap the controls to their preference, so it is a good idea to avoid doing that. Thankfully, Unity has a simple system for generically mapping controls. With Unity, you look for a specific axis to know whether a player intends a certain action. Then, when the player runs the game, they can choose to make different controls mean different axes.
You can view, edit, and add different axes by using the Input Manager. To access the Input Manager, click Edit > Project Settings > Input Manager. In the Input Manager, you can see the various axes associated with different input actions. By default, there are 18 input axes, but you can add your own if you want. Figure 8.1 shows the default Input Manager with the horizontal axis expanded.
FIGURE 8.1
The Input Manager.
While the horizontal axis doesn’t directly control anything (you will write scripts to do that later), it represents that player going sideways. Table 8.1 describes the properties of an axis.
TABLE 8.1 Axis Properties
Once your axes are set up in the Input Manager, working with them in code is simple. To access any of the player’s input, you will be using the Input
object. More specifically, you will be using the GetAxis
method of the Input
object. GetAxis()
reads the name of the axis as a string and returns the value of that axis. So, if you want to get the value of the horizontal axis, you type the following:
float hVal = Input.GetAxis(“Horizontal”);
In the case of the horizontal axis, if the player is pressing the left arrow key (or the A key), GetAxis()
returns a negative number. If the player is pressing the right arrow key (or the D key), the method returns a positive value.
Although you generally want to deal with the generic axes for input, sometimes you want to determine whether a specific key has been pressed. To do so, you will again be using the Input
object. This time, however, you will use the GetKey
method, which reads in a special code that corresponds to a specific key. It then returns true
if the key is currently down or false
if the key is not currently down. To determine whether the K key is currently pressed, you type the following:
bool isKeyDown = Input.GetKey(KeyCode.K);
FIGURE 8.2
The automatic pop-up in Visual Studio.
Besides capturing key presses, you want to capture mouse input from the user. There are two components to mouse input: mouse button presses and mouse movement. Determining whether mouse buttons are pressed is much like detecting key presses, covered earlier this hour. In this section, you will again be using the Input
object. This time you’ll use the GetMouseButtonDown
method, which takes an integer between 0 and 2 to dictate which mouse button you are asking about. The method returns a Boolean value indicating whether the button is pressed. The code to get the mouse button presses looks like this:
bool isButtonDown; isButtonDown = Input.GetMouseButtonDown(0); // left mouse button isButtonDown = Input.GetMouseButtonDown(1); // right mouse button isButtonDown = Input.GetMouseButtonDown(2); // center mouse button
Mouse movement is only along two axes: x and y. To get the mouse movement, you use the GetAxis
method of the input object. You can use the names Mouse X and Mouse Y to get the movement along the x axis and the y axis, respectively. The code to read in the mouse movement would look like this:
float value; value = Input.GetAxis(“Mouse X”); // x axis movement value = Input.GetAxis(“Mouse Y”); // y axis movement
Unlike button presses, mouse movement is measured by the amount the mouse has moved since the last frame only. Basically, holding a key causes a value to increase until it maxes out at -1 or 1 (depending on whether it is positive or negative). The mouse movement, however, generally has smaller numbers because it is measured and reset each frame.
As you have seen numerous times in the Inspector view, objects are composed of various components. There is always a transform component, and there may optionally be any number of other components, such as a Renderer, Light, and Camera. Scripts are also components, and together these components give a game object its behavior.
GetComponent
You can interact with components at runtime through scripts. The first thing you must do is get a reference to the component you want to work with. You should do this in Start()
and save the result in a variable. This way, you won’t have to waste time repeating this relatively slow operation.
The GetComponent<Type>()
method has a slightly different syntax than you’ve seen to this point, using chevrons to specify the type you are looking for (for example, Light, Camera, script name).
GetComponent()
returns the first component of the specified type that is attached to the same game object as your script. As mentioned earlier in this hour, you should then assign this component to a local variable so you can access it later. Here’s how you do this:
Light lightComponent; // A variable to store the light component. Start () { lightComponent = GetComponent<Light> (); lightComponent.type = LightType.Directional; }
Once you have a reference to a component, you can then easily modify its properties through code. You do so by typing the name of the variable storing the reference followed by a dot followed by whatever property you want to modify. In the example above, you change the type
property of the light component to be Directional
.
The component you’ll most commonly work with is the transform component. By editing it, you can make objects move around the screen. Remember that an object’s transform is made up of its translation (or position), its rotation, and its scale. Although you can modify those directly, it is easier to use some built-in options called the Translate()
method, the Rotate()
method, and the localScale
variable, as shown here:
// Moves the object along the positive x axis. // The ‘0f’ means 0 is a float (floating point number). It is the way Unity reads floats transform.Translate(0.05f, 0f, 0f); // Rotates the object along the z axis transform.Rotate(0f, 0f, 1f); // Scales the object to double its size in all directions transform.localScale = new Vector3(2f, 2f, 2f);
Because Translate()
and Rotate()
are methods, if the preceding code were put in Update()
, the object would continually move along the positive x axis while being rotated along the z axis.
Many times, you want a script to be able to find and manipulate other objects and their components. Doing so is simply a matter of finding the object you want and calling on the appropriate component. There are a few basic ways to find objects that aren’t local to the script or to the object the script is attached to.
The first and easiest way to find other objects to work with is to use the editor. By creating a public variable on the class level of type GameObject
, you can simply drag the object you want onto the script component in the Inspector view. The code to set this up looks like this:
// This is here for reference public class SomeClassScript : MonoBehaviour { // This is the game object you want to access public GameObject objectYouWant; // This is here for reference void Start() {} }
After you have attached the script to a game object, you see a property in the Inspector called Object You Want (see Figure 8.3). Just drag any game object you want onto this property to have access to it in the script.
FIGURE 8.3
The new Object You Want property in the Inspector.
Another way to find a game object is by using one of the Find methods. As a rule of thumb, if you want a designer to be able to connect the object, or if it’s optional, then connect via the Inspector. If disconnecting would break the game, then use a Find method. There are three major ways to find using a script: by name, by tag, and by type.
One option is to search by the object’s name. The object’s name is what it is called inside the Hierarchy view. If you were looking for an object named Cube
, the code would look like this:
// This is here for reference public class SomeClassScript : MonoBehaviour { // This is the game object you want to access private GameObject target; // Note this doesn’t need to be public if using find. // This is here for reference void Start() { target = GameObject.Find(“Cube”); } }
The shortcoming of this method is that it just returns the first item it finds with the given name. If you have multiple Cube
objects, you won’t know which one you are getting.
Another way to find an object is by its tag. An object’s tag is much like its layer (discussed earlier in this hour). The only difference is semantics. The layer is used for broad categories of interaction, whereas the tag is used for basic identification. You create tags using the Tag Manager (click Edit > Project Settings > Tags & Layers). Figure 8.4 shows how to add a new tag to the Tag Manager.
FIGURE 8.4
Adding a new tag.
Once a tag is created, simply apply it to an object by using the Tag drop-down list in the Inspector view (see Figure 8.5).
FIGURE 8.5
Selecting a tag.
After a tag is added to an object, you can find it by using the FindWithTag()
method:
// This is here for reference public class SomeClassScript : MonoBehaviour { // This is the game object you want to access private GameObject target; // This is here for reference void Start() { target = GameObject.FindWithTag(“MyNewTag”); } }
There are additional Find()
methods available, but the ones covered here should work for you in most situations.
Once you have a reference to another object, working with the components of that object is almost exactly the same as using local components. The only difference is that now, instead of simply writing the component name, you need to write the object variable and a period in front of it, like so:
// This accesses the local component, not what you want transform.Translate(0, 0, 0); // This accesses the target object, what you want targetObject.transform.Translate(0, 0, 0);
In this hour, you have explored more scripting in Unity. You have learned all about methods and looked at some ways to write your own. You have also worked with player inputs from the keyboard and mouse. You learned about modifying object components with code, and you finished the hour by learning how to find and interact with other game objects via scripts.
Q. How do I know how many methods a task requires?
A. A method should perform a single, concise function. You don’t want to have too few methods with a lot of code because each method would then have to do more than one thing. You also don’t want to have too many small methods because that defeats the purpose of modularizing blocks of code. As long as each process has its own specific method, you have enough methods.
Q. Why didn’t we learn more about gamepads in this hour?
A. The problem with gamepads is that all are unique. In addition, different operating systems treat them differently. The reason they aren’t covered in detail in this hour is that they are too varied, and discussing some of them wouldn’t allow for a consistent reader experience. (In addition, not everyone has a gamepad.)
Q. Is every component editable by script?
A. Yes, at least all of the built-in ones are.
Take some time to work through the questions here to ensure that you have a firm grasp of the material.
1. True or False: Methods can also be referred to as functions.
2. True or False: Not every method has a return type.
3. Why is it a bad thing to map player interactions to specific buttons?
4. In the Try It Yourself exercises in the sections on local and target components, the cube was translated along the positive x axis and rotated along the z axis. This caused the cube to move around in a big circle. Why?
1. True
2. False. Every method has a return type. If a method returns nothing, the type is void
.
3. If you map player interactions to specific buttons, the players will have a much harder time remapping the controls to meet their preferences. If you map controls to generic axes, players can easily change which buttons map to those axes.
4. Transformations happen on the local coordinate system (refer to Hour 2, “Game Objects”). Therefore, the cube did move along the positive x axis. The direction that axis was facing relative to the camera, however, kept changing.
It is a good idea to combine each hour’s lessons together to see them interact in a more realistic way. In this exercise, you’ll write scripts to allow the player directional control over a game object. You can find the solution to this exercise in the book assets for Hour 8 if needed.
1. Create a new project or scene. Add a cube to the scene and position it at (0, 0, -5).
2. Create a new folder called Scripts and create a new script called CubeControlScript. Attach the script to the cube.
3. Try to add the following functionality to the script. If you get lost, check the book assets for Hour 8 for help:
► Whenever the player presses the left or right arrow key, move the cube along the x axis negatively or positively, respectively. Whenever the player presses the down or up arrow key, move the cube along the z axis negatively or positively, respectively.
► When the player moves the mouse along the y axis, rotate the camera about the x axis. When the player moves the mouse along the x axis, rotate the cube about the y axis.
► When the player presses the M key, have the cube double in size. When the player presses the M key again, have the cube go back to its original size. The M key should act as a toggle switching between the two scale sizes.
18.118.140.108