Hour 8. Scripting, Part 2

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

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”).

Anatomy of a Method

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>
}
Method Name

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.

Return Type

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.

Parameter List

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.

Method Signature

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.

Method Block

The method block is where the code of the method goes. Every time a method is used, the code inside the method block executes.

Try it Yourself

Identifying Method Parts

Take a moment to review the different parts of a method and then consider the following method:

int TakeDamage(int damageAmount)
{
    int health = 100;
    return health - damageAmount;
}

Can you identify the following pieces?

1. What is the method’s name?

2. What variable type does the method return?

3. What are the method’s parameters? How many are there?

4. What code is in the method’s block?

Writing Methods

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.

Using Methods

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.

Try it Yourself

Calling Methods

Let’s work further with the TakeDamageFromFireball method described in the previous section. This exercise shows how to call the various forms of the method. (You can find the solution for this exercise as FireBallScript in the book assets for Hour 8.) Follow these steps:

1. Create a new project or scene. Create a C# script called FireBallScript and enter the three TakeDamageFromFireball methods described earlier. These should go inside the class definition, at the same level of indent as the Start() and Update() methods, but outside those two methods.

2. In the Start method, call the first TakeDamageFromFireball() method by typing the following:

int x = TakeDamageFromFireball();
print ("Player health: " + x);

3. Attach the script to the Main Camera and run the scene. Notice the output in the Console. Now call the second TakeDamageFromFireball() method in Start() by typing the following (placing it below the first bit of code you typed; no need to remove it):

int y = TakeDamageFromFireball(25);
print ("Player health: " + y);

4. Again, run the scene and note the output in the Console. Finally, call the last TakeDamageFromFireball() method in Start() by typing the following:

int z = TakeDamageFromFireball(30, 50);
print ("Player health: " + z);

5. Run the scene and note the final output. Notice how the three methods behave a little differently from one another. Also notice that you called each one specifically, and the correct version of the TakeDamageFromFireball() method was used based on the parameters you passed in.

Input

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.

Input Basics

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.

Images

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

Images

Input Scripting

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.

Try it Yourself

Reading in User Input

Follow these steps to work with the vertical and horizontal axes and get a better idea of how to use player input:

1. Create a new project or scene. Add a script named PlayerInput to the project and attach the script to the Main Camera.

2. Add the following code to the Update method in the PlayerInput script:

float hVal = Input.GetAxis(“Horizontal”);
float vVal = Input.GetAxis(“Vertical”);
if(hVal != 0)
    print(“Horizontal movement selected: “ + hVal);
if(vVal != 0)
    print(“Vertical movement selected: “ + vVal);

This code must be in Update so that it continuously reads the input.

3. Save the script and run the scene. Notice what happens on the Console when you press the arrow keys. Now try out the W, A, S, and D keys. If you don’t see anything, click in the Game window with the mouse and try again.

Specific Key Input

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);
Images

FIGURE 8.2
The automatic pop-up in Visual Studio.

Try it Yourself

Reading in Specific Key Presses

Follow these steps to write a script that determines whether a specific key is pressed:

1. Create a new project or scene. Add to the project a script named PlayerInput (or modify the existing one) and attach it to the Main Camera.

2. Add the following code to the Update method in the PlayerInput script:

if(Input.GetKey(KeyCode.M))
    print("The 'M' key is pressed down");
if(Input.GetKeyDown(KeyCode.O))
   print("The 'O' key was pressed");

3. Save the script and run the scene. Notice what happens when you press the M key versus what happens when you press the O key. In particular, note that the M key causes output the entire time it is held, while the O key outputs only when it is first pressed.

Mouse Input

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.

Try it Yourself

Reading Mouse Movement

In this exercise, you’ll read in mouse movement and output the results to the Console:

1. Create a new project or scene. Add to the project a script named PlayerInput (or modify the existing one) and attach it to the Main Camera.

2. Add the following code to the Update method in the PlayerInput script:

float mxVal = Input.GetAxis(“Mouse X”);
float myVal = Input.GetAxis(“Mouse Y”);
if(mxVal != 0)
    print(“Mouse X movement selected: “ + mxVal);
if(myVal != 0)
    print(“Mouse Y movement selected: “ + myVal);

3. Save the script and run the scene. Read through the Console to see the output when you move the mouse around.

Accessing Local Components

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.

Using 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.

Accessing the Transform

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.

Try it Yourself

Transforming an Object

Follow these steps to see the previous code in action by applying it to an object in a scene:

1. Create a new project or scene. Add a cube to the scene and position it at (0, -1, 0).

2. Create a new script and name it CubeScript. Place the script on the cube. In Visual Studio, enter the following code to the Update method:

transform.Translate(.05f, 0f, 0f);
transform.Rotate(0f, 0f, 1f);
transform.localScale = new Vector3(1.5f, 1.5f, 1.5f);

3. Save the script and run the scene. You may need to move to Scene view to see the full motion. Notice that the effects of the Translate() and Rotate() methods are cumulative, and the variable localScale is not; localScale does not keep growing.

Accessing Other Objects

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.

Finding Other Objects

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.

Images

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.

Images

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).

Images

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.

Modifying Object Components

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);

Try it Yourself

Transforming a Target Object

Follow these steps to modify a target object by using scripts:

1. Create a new project or scene. Add a cube to the scene and position it at (0, -1, 0).

2. Create a new script and name it TargetCubeScript. Place the script on the Main Camera. In Visual Studio, enter the following code in TargetCubeScript:

// This is the game object you want to access
private GameObject target;
// This is here for reference
void Start()
{
     target = GameObject.Find(“Cube”);
}
void Update()
{
     target.transform.Translate(.05f, 0f, 0f);
     target.transform.Rotate(0f, 0f, 1f);
     target.transform.localScale = new Vector3(1.5f, 1.5f, 1.5f);
}

3. Save the script and run the scene. Notice that the cube moves around even though the script is applied to the Main Camera.

Summary

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&A

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.

Workshop

Take some time to work through the questions here to ensure that you have a firm grasp of the material.

Quiz

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?

Answers

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.

Exercise

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.

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

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