Chapter 3. Scripting in Unity

In order for your game to work, you need to define what actually happens in your game. Unity provides you with the foundations of what you need, such as rendering graphics, getting input from the player, and playing audio; it’s up to you to add the features that are unique to your game.

To make this happen, you write scripts that get added to your game’s objects. In this chapter, we’ll introduce you to Unity’s scripting system, which uses the C# programming language.

Languages in Unity

You have a choice of languages when programming in Unity. Unity officially supports two different languages: C# and “JavaScript.”

We put JavaScript in quotes because it’s not actually the JavaScript language that you might be familiar with from the wider world. Instead, it’s a language that looks like JavaScript, but has multiple differences from its namesake. It’s different enough that it’s often called “UnityScript,” by both users of Unity and sometimes the Unity team themselves.

We don’t use Unity’s JavaScript in this book for a couple of reasons. The first is that Unity’s reference material tends to show C# examples more than JavaScript, and we get the feeling that the use of C# is preferred by Unity’s developers.

Secondly, when you use C# in Unity, it’s the same language you’ll find anywhere else, whereas Unity’s version of JavaScript is very specific to Unity. This means that it’s easier to find help about the language.

A Crash Course in C#

When writing scripts for Unity games, you write in a language called C#. We’re not going to explain the fundamentals of programming in this book (we don’t have the space!), but we’ll highlight some main points to keep in mind.

Note

A great general reference on the C# language is C# in a Nutshell, by Joseph and Ben Albahari (O’Reilly, 2015).

To give you a quick introduction, we’ll take a chunk of C# code, and highlight some important elements:

using UnityEngine; 1

namespace MyGame { 2

    [RequireComponent(typeof(SpriteRenderer))] 3
    class Alien : MonoBehaviour { 4

        public bool appearsPeaceful; 5

        private int cowsAbducted;

        public void GreetHumans() {
            Debug.Log("Hello, humans!");

            if (appearsPeaceful == false) {
                cowsAbducted += 1;
            }
        }
    }
}
1

The using keyword indicates to the user which packages you’d like to use. The UnityEngine package contains the core Unity types.

2

C# lets you put your types in namespaces, which means that you can avoid naming collisions.

3

Attributes are placed between square brackets, and let you add additional information about a type or method.

4

Classes are defined using the class keyword, and you specify the superclass after a colon. When you make a class a subclass of MonoBehaviour, it can be used as a script component.

5

Variables attached to classes are called fields.

Mono and Unity

Unity’s scripting system is powered by the Mono framework. Mono is an open source implementation of Microsoft’s .NET Framework, which means that in addition to the libraries that come with Unity, you also have the complete set of libraries that come with .NET.

A common misconception is that Unity is built on top of Mono. Unity is not built on Mono; it merely uses Mono as its scripting engine. Unity supports scripting, through Mono, using both the C# language and the UnityScript language (what Unity calls “JavaScript;” see Languages in Unity).

The versions of C# and the .NET Framework available in Unity are older than the most current versions. At the time of writing in early 2017, the version of the C# language available is 4, while the version of the .NET Framework available is 3.5. The reason for this is that Unity uses its own fork of the Mono project, which diverged from the mainline branch several years ago. This has meant that Unity can add features that are specific to their uses, which are primarily mobile-oriented compiler features.

Unity is in the middle of updating its compiler tools to make the latest versions of the C# language and the .NET Framework available to users. Until that happens, your code will be a few versions behind.

For this reason, if you’re looking for C# code or advice around the web, you should search for Unity-specific code most of the time. Similarly, when you’re coding C# for Unity, you’re going to be using a combination of Mono’s API (for generic things that most platforms provide) and Unity’s API (for game engine-specific things).

MonoDevelop

MonoDevelop is the development environment that’s included with Unity. MonoDevelop’s main role is to be the text editor that you write your scripts with; however, it contains some useful features that can make your life easier when programming.

When you double-click on any script file in your project, Unity will open the editor that’s currently configured. By default, this will be MonoDevelop, though you can configure it to be any other text editor you like.

Unity will automatically update the project in MonoDevelop with the scripts in your project, and will compile your code when you return to Unity. This means that all you need to do to edit your scripts is to save your changes, and return to the editor.

There are several features in MonoDevelop that can save you a lot of time.

Code completion

In MonoDevelop, press Ctrl-Space (on both PC and Mac). Mono​Develop will display a pop-up window that offers a list of suggestions for what to type next; for example, if you’re halfway through typing a class name, MonoDevelop will offer to complete it. Press the up and down arrows to select from the list, and press Enter to accept the suggestion.

Refactoring

When you press Alt-Enter (Option-Enter on a Mac), MonoDevelop will offer to perform certain tasks that edit your source code. These tasks include things like adding or removing braces around if statements, automatically filling in the case labels for switch statements, or splitting a variable’s declaration and assignment into two lines.

Building

Unity will automatically rebuild your code when you return to the editor. However, if you press Command-B (F7 on a PC), all of your code will be built in MonoDevelop. The files that result from this won’t be used in this game, but doing this means that you’re able to verify that there are no compilation errors in your code before you return to Unity.

Game Objects, Components, and Scripts

Unity scenes are composed of game objects. On their own, they’re invisible objects, and have nothing but a name. Their behavior is defined by their components.

Components are the building blocks of your game, and anything you see in the Inspector is a component. Each component has a different responsibility; for example, Mesh Renderers display 3D meshes, while Audio Sources play sound to the user. Scripts that you write are components as well.

To create a script:

  1. Create the script asset. Open the Assets menu, and choose Create → Script → C# Script.

  2. Name the script asset. A new script file will appear in the folder you had selected in the Project panel, ready for you to name.

  3. Double-click the script asset. The script will open in the script editor, which defaults to MonoDevelop. Most of your scripts will start off looking like this:

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    
    public class AlienSpaceship : MonoBehaviour { 1
    
        // Use this for initialization
        void Start () { 2
    
        }
    
        // Update is called once per frame
        void Update () { 3
    
        }
    }
    1

    The name of the class, in this case AlienSpaceship, must be the same as the asset filename.

    2

    The Start function is called before the Update function is called for the first time, and is where you might put code that initializes variables, loads stored preferences, or sets up other scripts and GameObjects.

    3

    The Update function is called every frame, and is an opportunity to include code that responds to input, triggers another script, or moves things around—anything that needs to happen.

Note

You may be familiar with constructors from other programming environments. In Unity, you don’t construct your MonoBehaviour subclasses yourself, because the construction of objects is performed by Unity itself, and does not necessarily take place when you think it might.

Script assets in Unity do not actually do anything—none of their code is executed—until they are attached to a GameObject (seen in Figure 3-1). There are two main ways to attach a script to a GameObject:

  1. Dragging the script asset onto the GameObject. This can be done with either the Inspector or the Hierarchy panel.

  2. Using the Component menu. You will find all the scripts that are in the project under Component → Scripts.

mgdu 0301
Figure 3-1. The Inspector for a GameObject, showing a script called “PlayerMovement” added as a component

Since scripts are primarily exposed in the Unity editor through being attached, as components, to GameObjects, Unity allows you to expose properties in your script as editable values in the Inspector. To do this, you create a public variable in your script. Everything specified as public will be visible in the editor; you can also set variables to private, though.

public class AlienSpaceship : MonoBehaviour {
    public string shipName;

    // "Ship Name" will appear in the Inspector
    // as an editable field
}

The Inspector

When your script is added as a component to a game object, it appears in the Inspector when that object is selected. Unity will automatically display all variables that are public, in the order they appear in your code.

private variables that have the [SerializeField] attribute will also appear. This is useful for when you want a field to be visible in the Inspector, but not accessible to other scripts.

Note

The Unity editor will display the variable name by capitalizing the first letter of each word, and placing a space before existing capital letters. For example, the variable shipName is displayed as “Ship Name” in the editor.

Components

Scripts are able to access the different components that are present on a GameObject. To do this, you use the GetComponent method.

// gets the Animator component on this object, if it exists
var animator = GetComponent<Animator>();

You can also call GetComponent on other objects, to get components attached to them.

You can also get the components that are attached to parent or child objects, using the GetComponentInChildren or GetComponentInParent methods.

Important Methods

Your MonoBehaviours have several methods that are particularly important to Unity. These methods are called at different times during the component’s life cycle, and are opportunities to run the right behavior at the right moment. This section lists the methods in the order that they’re run.

Awake and OnEnable

Awake is run immediately after an object is instantiated in the scene, and is the first opportunity you have to run code in your script. Awake is called once in the object’s lifetime.

By contrast, OnEnable is called each time an object becomes enabled.

Start

The Start method is called immediately before the first call to an object’s Update method.

Start Versus Awake

You might wonder why there are two opportunities for setting up an object: Awake and Start. After all, doesn’t that just mean that you’ll pick one of them at random?

There’s actually a very good reason for it. When you start a scene, all objects in it run their Awake and Start methods. Critically, however, Unity makes sure that all objects have finished running their Awake methods before any Start methods are run.

This means that any work that’s done in an object’s Awake method is guaranteed to have been done by the time another object runs its Start method. This can be useful, such as in this example, where object A uses a field set up by object B:

// In a file called ObjectA.cs
class ObjectA : MonoBehaviour {

    // A variable for other scripts to access
    public Animator animator;

    void Awake() {
        animator = GetComponent<Animator>();
    }
}

// In a file called ObjectB.cs
class ObjectB : MonoBehaviour {

    // Connected to the ObjectA script
    public ObjectA someObject;

    void Awake() {
        // Check to see if someObject has set its 'animator'
        // variable
        bool hasAnimator = someObject.animator == null;

        // May print 'true' OR 'false', depending on which
        // one happens to run first
        Debug.Log("Awake: " + hasAnimator.ToString());
    }

    void Start() {
        // Check to see if someObject has set its 'animator'
        // variable
        bool hasAnimator = someObject.animator == null;

        // Will *always* print 'true'
        Debug.Log("Start: " + hasAnimator.ToString());
    }
}

In this example, the ObjectA script is on an object that also has an Animator component attached. (The Animator itself does nothing in this example, and could just as easily be any other kind of component.) The ObjectB script has been set up so that its someObject variable is connected to the object containing the ObjectA script.

When the scene begins, the ObjectB script will log twice—once in its Awake method, and once in its Start method. In both cases, it will try to figure out if its someObject variable’s animator field is not null, and print either “true” or “false.”

If you were to run this example, the first log message, which runs in ObjectB’s Awake method, would be either “true” or “false,” depending on which script’s Awake method ran first. (Without manually setting up an execution order in Unity, it’s impossible to know which runs first.)

However, the second log message, which runs in ObjectB’s Start method, is guaranteed to return “true.” This is because, when a scene starts up, all existing objects will run their Awake methods before any Start methods are run.

Update and LateUpdate

The Update method is run every single frame, as long as the component is enabled, and the object that the script is attached to is active.

Note

Update methods should do as little work as possible, because they’re run every single frame. If you do some long-running work in an Update method, you’ll slow down the rest of the game. If you need to do something that will take some time, you should use a coroutine (described in the following section).

Unity will call the Update method on all scripts that have one. Once that’s done, it will call LateUpdate method on all scripts that have one. Update and LateUpdate have a similar relationship to that of Awake and Start: no LateUpdate methods will be called until all Update methods have been run.

This is useful for when you want to do work that relies on some other object to have done work in Update. You can’t control which objects run their Update method first; however, when you write code that runs in LateUpdate, you’re guaranteed that any work in any object’s Update method will have completed.

Note

In addition to Update, the FixedUpdate method can be used. While Update is called once per frame, FixedUpdate is called a fixed number of times each second. This can be useful when working with physics, where you need to apply forces at regular intervals.

Coroutines

Most functions do their work and return immediately. However, sometimes you need something to take place over time. For example, if you want an object to slide from one point to another, you need that movement to happen over multiple frames.

A coroutine is a function that runs over multiple frames. In order to create a coroutine, first create a method that has a return type of IEnumerator:

IEnumerator MoveObject() {

}

Next, use the yield return statement to make the coroutine temporarily stop, allowing the rest of the game to carry on. For example, to make an object move forward by a certain amount every frame,1 you’d do this:

IEnumerator MoveObject() {
    // Loop forever
    while (true) {

        transform.Translate(0,1,0); // move 1 unit on the Y
                                    // axis every frame

        yield return null; // wait until the next frame

    }
}
Warning

If you include an infinite loop (such as the while (true) in the previous example), then you must yield during it. If you don’t, it will loop forever without giving the rest of your code a chance to do any other work. Because your game’s code runs inside Unity, you run the risk of causing Unity to freeze up if you enter an infinite loop. If that happens, you’ll need to force Unity to quit, and may lose unsaved work.

When you yield return from a coroutine, you temporarily pause the execution of the function. Unity will resume execution later; the specifics of when it resumes depends on what value you yield return with.

For example:

yield return null

waits until the next frame

yield return new WaitForSeconds(3)

waits three seconds

yield return new WaitUntil(() => this.someVariable == true)

waits until someVariable equals true; you can also use any expression that evaluates to a true or false variable

Note

To stop a coroutine, you use the yield break statement:

// stop this coroutine immediately
yield break;

Coroutines will also automatically stop when execution reaches the end of the method.

Once you have a coroutine function, you can start it. To start a coroutine, you don’t call it on its own; instead, you use it in conjunction with the StartCoroutine function:

StartCoroutine(MoveObject());

When you do this, the coroutine will start executing until it either reaches a yield break statement, or it reaches the end.

Note

In addition to the yield return examples we just looked at, you can also yield return on another coroutine. This means that the coroutine you’re yielding from will wait until the other coroutine ends.

It’s also possible to stop a coroutine from outside of it. To do this, keep a reference to the return value of the StartCoroutine method, and pass it to the StopCoroutine method:

Coroutine myCoroutine = StartCoroutine(MyCoroutine());

// ... later ...

StopCoroutine(myCoroutine);

Creating and Destroying Objects

There are two ways to create an object during gameplay. The first is by creating an empty GameObject, and attaching components to it by using code; the second involves duplicating another object (called instantiation). The second method is more popular because you can do everything in a single line of code, so we’ll discuss it first.

Note

When you create new objects in Play Mode, those objects will disappear when you stop the game. If you want them to stick around, follow these steps:

  1. Select the objects you want to save.

  2. Copy them, either by pressing Command-C (Ctrl-C on a PC), or opening the Edit menu and choosing Copy.

  3. Leave Play Mode. The objects will disappear from the scene.

  4. Paste, either by pressing Command-V (Ctrl-V on a PC), or opening the Edit menu and choosing Paste. The objects will reappear; you can now work with them in Edit Mode.

Instantiation

In Unity, instantiating an object means that it, along with all of its components, child objects, and their components, are copied. This is particularly powerful when the object you’re instantiating is a prefab. Prefabs are prebuilt objects that you save as assets. This means that you can create a single template of an object, and instantiate many copies of it across many different scenes.

To instantiate an object, you use the Instantiate method:

public GameObject myPrefab;

void Start() {
    // Create a new copy of myPrefab,
    // and position it at the same point as this object
    var newObject = (GameObject)Instantiate(myPrefab);

    newObject.transform.position = this.transform.position;
}
Warning

The Instantiate method’s return type is Object, not GameObject. You’ll need to do a cast in order to treat it as a GameObject.

Creating an Object from Scratch

The other way you can create objects is by building them up yourself through code. To do this, you use the new keyword to construct a new GameObject, and then call AddComponent on it to add new components.

// Create a new game object; it will appear as
// "My New GameObject" in the hierarchy
var newObject = new GameObject("My New GameObject");

// Add a new SpriteRenderer to it
var renderer = newObject.AddComponent<SpriteRenderer>();

// Tell the new SpriteRenderer to display a sprite
renderer.sprite = myAwesomeSprite;
Note

The AddComponent method takes as a generic parameter the type of component you want to add. You can specify any class here that’s a subclass of Component, and it will be added.

Destroying Objects

The Destroy method removes an object from the scene. Notice that we didn’t say game object, but object! Destroy is used for removing both game objects and components.

To remove a game object from the scene, call Destroy on it:

// Destroy the game object that this script is attached to
Destroy(this.gameObject);
Warning

Destroy works on both components and game objects.

If you call Destroy and pass in this, which means the current script component, you won’t remove the game object, but instead the script will end up removing itself from the game object it’s attached to. The game object will stick around, but will no longer have your script attached.

Attributes

An attribute is a piece of information that you can attach to a class, variable, or method. Unity defines several useful attributes that you can use, which change the behavior of the class or how it’s presented in the Editor.

RequireComponent

The RequireComponent attribute, when attached to a class, allows you to specify to Unity that the script requires that another type of component be present. This is useful when your script only makes sense when that kind of component is attached. For example, if your script only does one thing, such as changing the settings of an Animator, it makes sense that that class should require an Animator to be present.

To specify the type of component that your component requires, you provide the type of component as a parameter, like so:

[RequireComponent(typeof(Animator))]
class ClassThatRequiresAnAnimator : MonoBehaviour {
    // this class requires that an Animator also
    // be attached to the GameObject
}
Note

If you add a script that requires a certain component to a GameObject, and that GameObject doesn’t already have that component, Unity will automatically add one for you.

Header and Space

The Header attribute, when added to a field, causes Unity to draw a label above the field in the Inspector. Space works similarly, but adds empty space. Both are useful for visually organizing the contents of the Inspector.

For example, Figure 3-2 shows the Inspector’s rendering of the following code:

  public class Spaceship : MonoBehaviour {

      [Header("Spaceship Info")]

      public string name;

      public Color color;

      [Space]

      public int missileCount;
  }
mgdu 0302
Figure 3-2. The Inspector, showing header labels and spaces

SerializeField and HideInInspector

Normally, only public fields are displayed in the Inspector. However, making variables public means that other objects can directly access them, which means that it can be difficult for an object to have full control over its own data. However, if you make the variable private, Unity won’t display it in the Inspector.

To get around this, add the SerializeField attribute to private variables you want to appear in the Inspector.

If you want the opposite behavior (that is, the variable is public, but doesn’t appear in the Inspector), then you can use the HideInInspector attribute:

class Monster : MonoBehaviour {

    // Appears in Inspector, because it's public
    // Accessible from other scripts
    public int hitPoints;

    // Doesn't appear in Inspector, because it's private
    // Not accessible from other scripts
    private bool isAlive;

    // Appears in Inspector, because of SerializeField
    // Not accessible from other scripts
    [SerializeField]
    private int magicPoints;

    // Doesn't appear in Inspector, because of HideInInspector
    // Accessible from other scripts
    [HideInInspector]
    public bool isHostileToPlayer;
}

ExecuteInEditMode

By default, your scripts will only run their code in Play Mode; that is, the contents of your Update method will only run when the game is actually running.

However, it can sometimes be convenient to have code that runs all the time. For these cases, you can add the ExecuteInEditMode attribute to your class.

Note

The component life cycle performs differently in Edit Mode as compared to Play Mode. When in Edit Mode, Unity will only redraw itself when it has to, which generally means in response to user input events like mouse clicks. This means that the Update method will run only sporadically instead of continuously. Additionally, coroutines won’t behave the way you expect.

Moreover, you can’t call Destroy in Edit Mode, because Unity defers the actual removal of an object until the next frame. In Edit Mode, you should call DestroyImmediate instead, which removes an object right away.

For example, here’s a script that makes an object always face its target, even when not in Play Mode:

[ExecuteInEditMode]
class LookAtTarget : MonoBehaviour {

    public Transform target;

    void Update() {
        // Don't continue if we don't have a target
        if (target != null) {
            return;
        }

        // Rotate to look at target
        transform.LookAt(target);
    }

}

If you were to attach this script to an object, and set its target variable to another object, the first object would rotate to face its target in both Play Mode and Edit Mode.

Time in Scripts

The Time class is used to get information about the current time in your game. There are several variables available in the Time class (and we strongly recommend you look at the documentation for it!), but the most important and commonly used variable is deltaTime.

Time.deltaTime measures the amount of time since the last frame was rendered. It’s important to realize that this time can vary a lot. Doing this allows you to perform an action that’s updated every frame, but needs to take a certain amount of time.

In “Coroutines”, the example we used was one that moves the object one unit every frame. This is a bad idea, because the number of frames in a second can vary quite a lot. For example, if the camera is looking at a very simple part of your scene, the frames per second could be very high, whereas looking at more visually complex scenes could result in very low framerates.

Because you can’t be sure of the number of frames per second you’re running at, the best thing to do is to take into account Time.deltaTime. The easiest way to explain this is with an example:

IEnumerator MoveSmoothly() {
    while (true) {

        // move 1 unit per second
        var movement = 1.0f * Time.deltaTime;

        transform.Translate(0, movement, 0);

        yield return null;

    }
}

Logging to the Console

As we saw in “Awake and OnEnable”, it’s sometimes convenient to log some information to the Console, either for diagnostic purposes, or to warn you about some problem.

The Debug.Log function can be used to do this. There are three different levels of logging: info, warning, and error. There’s no functional difference between the three types, except warnings and errors are each more visible and prominent than the last.

In addition to Debug.Log, you can also use Debug.LogFormat, which allows you to embed values in the string that’s sent to the Console:

Debug.Log("This is an info message!");
Debug.LogWarning("This is a warning message!");
Debug.LogError("This is a warning message!");

Debug.LogFormat("This is an info message! 1 + 1 = {0}", 1+1);

Wrapping Up

Scripting in Unity is a critical skill, and becoming comfortable with both the C# language and the tools used for writing it will make building your game easier and more fun to do.

1 This is actually not a great idea, for reasons that are explained in “Time in Scripts”, but it’s the simplest example.

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

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