Module 2 · Examples

Object Control, Interaction + User Interface

Coverage

Unity-specific skills you will need, practice, and demonstrate:

◆GameObjects & components

  • Finding GameObjects and accessing components
  • Accessing variables of components
  • Setting a component reference in the editor

⧉Prefabs

  • Create a prefab in the editor
  • Create/delete objects at run time

↻Transform

  • Set positions
  • Set rotation/orientation
  • Object collisions and responses

▣User interface

  • World position from the mouse
  • Referencing components from variables
  • Game world vs UI coordinate systems
  • UI RectTransform
  • Event vs polling

⬇Packages

  • Create and import packages

Concepts you will explore and understand

  • Direct object control: driving an object
  • Frame update time vs wall-clock real time
  • System and component re-use
  • Approximating object bounds with colliders
  • Coordinate systems for gameplay and user interactions
  • Event driven vs polling user interactions

Examples for Module 2

2.1

Object Navigation

All in GreenArrowBehavior.cs
Run behavior The green arrow follows the mouse; press Space to switch to keyboard navigation with W A S D. Note the color change between mouse and keyboard mode.

Organization

  • Created a new folder in Assets, Scripts, to organize the scripts.
  • Subfolders in Scripts (for now): Hero (supports GreenUp).

GreenArrowBehavior

  • W S: move forward/back along transform.up; A D: rotate.
  • Initialization of color in Start(), and checking for errors with Debug.Assert.
  • Debug.Log: prints the current mode; output shows in the Console panel (Window › Panels › Console).
  • Notice the options for getting the reference to the Camera on the Main Camera:
    • mTheCamera: set in the editor
    • Camera.main
    • GameObject.Find("Main Camera").GetComponent<Camera>()
  • How to get a component on this object: GetComponent<SpriteRenderer>().color

Public variables

Public variables show up in the Inspector, and you can change them at runtime through the editor!

  • mTheCamera: reference to the Camera component on the Main Camera (must be set in the editor)!
  • Mouse vs keyboard control can be toggled.
  • Travel speed and rotation speed can be controlled.
Watch outChanges made at runtime in the editor will not be saved!

Speed: distance per unit time

Update() is called about 60 times per second!

  • mHeroSpeed: units per second. Unity's Time.smoothDeltaTime converts the frame update period to wall-clock time.
    • Remember, the total height of the world is 200 units. Verify that traveling from top to bottom takes about 10 seconds.
  • mHeroRotateSpeed: 45 degrees/second, so a full 360-degree rotation takes about 8 seconds.

Mouse position

The pixel position must be translated into camera space.

  • Make sure to zero out the z-component.
  • The actual position must be transformed by the Camera (Main Camera). Alternate ways to access the Main Camera: Camera.main, and GameObject.Find().

Event vs state

KindExampleTrue when…
EventSpace bar: wasPressedThisFrameOnly once, in the frame the key goes down
StateWASD: isPressedAs long as the key is held

Navigation

Move toward the object's own y-direction, which is transform.up:

newPosition = oldPosition + (Speed * Time.smoothDeltaTime * transform.up);

Rotate left/right: rotation with respect to the z-axis, or transform.forward:

transform.Rotate(transform.forward, RotateSpeed * Time.smoothDeltaTime);
Learned
  • Source code (script) organization is important! Strategy: organize using a folder structure.
  • Unity's function-calling sequence: Awake → Start → Update (every frame). Don't write a constructor for a MonoBehaviour; Unity creates the component, so do your setup in Awake() or Start().
  • Setting a component reference in the editor (mTheCamera)
  • Locating objects: GameObject.Find() finds any object by name, and returns null if not found.
  • Unity components: you can always call gameObject.GetComponent<ComponentClassName>() to access a component on an object.
    • gameObject is a variable that references the hosting GameObject.
    • Returns null if a component of that data type is not found.
  • Public variables show up in the Inspector window: convenient! But configuring them in the editor while the game is running will not be saved.
    • [SerializeField]: note that mHeroRotateSpeed is private and yet visible in the editor.
  • Frame rate vs real-time wall clock: Time.smoothDeltaTime
  • Accessing the mouse position (via the camera!)
  • Navigating an object with the keyboard
    • Moving forward: the transform.up/forward/right variables. These directions update as you change the object's orientation.
    • Rotating: transform.Rotate changes the transform.up (forward/right) directions!
Check your understanding
  1. In mouse mode, GreenArrowBehavior sets p.z = 0f after ScreenToWorldPoint(). What would you see if that line were removed?
    Show answer

    The arrow would disappear. ScreenToWorldPoint() of the mouse position returns a point at the camera's own z, -10. An object at z = -10 is closer to the camera than its Near plane (0.3 in front of it), so it isn't drawn. That's why the notes say zeroing z is very important.

  2. In keyboard mode, you hold A until the arrow points left, then hold W. Which way does it move? How is that different from 1.2?
    Show answer

    It moves left, the way the arrow points, because W adds transform.up, and rotating changes transform.up. In 1.2, W added to the y value, so the arrow always moved up the screen whatever its rotation.

  3. mHeroRotateSpeed is private with [SerializeField]. You can change it in the Inspector, so can another script, such as a GameManager, read or set it too?
    Show answer

    No. [SerializeField] only lets the editor show and save a private field. To C# code it's still private, so other scripts can't use it. They'd need it to be public, or a public method that returns or sets it.

2.2

Prefab

Create prefabs in the editor and instantiate them at runtime
Run behaviorPress and hold Space to spawn new eggs.

Prefab: “programming by example”

  • Drag an object (Egg) from the Hierarchy/Scene window into Assets!
  • Created a subfolder to keep the Assets folder organized.
NoteThe Resources folder name is not optional!! Resources.Load() depends on it!

EggBehavior

  • Spawns and travels in the transform.up direction.
  • Kills itself after a fixed number of updates (300 frames).

GreenArrowBehavior.cs::Update() Egg spawning

  • How to load and instantiate from the Resources folder.
  • After instantiating, you can change the new object's values (e.g., localPosition).
Learned
  • How to create and use prefabs in the editor:
    • Drag a GameObject into the Assets folder.
    • Drag a prefab back into the Scene window.
    • Warning: changing an instance of a prefab does not change the prefab.
  • Organization of prefabs: prefabs loaded with Resources.Load() must be in a Resources folder (under Assets).
  • How to instantiate a prefab at run time.
  • How to delete (Destroy) a GameObject.
    • Note: as in C++, you create, you delete. If you don't delete, it will be there forever!
Check your understanding
  1. EggBehavior moves at kEggSpeed = 40 units per second and destroys itself after kLifeTime = 300 updates. How far does an egg travel at 60 frames per second? At 30?
    Show answer

    At 60 fps, 300 updates take 5 seconds, so the egg travels 40 × 5 = 200 units. At 30 fps they take 10 seconds, so it travels 400 units. The speed is per second, but the lifetime is per frame, so the range depends on the frame rate. A lifetime in seconds (counted with Time.smoothDeltaTime or Time.time) would fix it.

  2. Suppose GreenUp could rotate. A new egg is placed at GreenUp's position, and EggBehavior moves it along transform.up. Would it fly the way GreenUp points?
    Show answer

    No. The spawn code sets only the egg's position, so the egg keeps the prefab's rotation and always flies straight up. To fire it the way GreenUp points, also set its direction, e.g., e.transform.up = transform.up; (or copy transform.rotation).

2.3

Game Manager + Simple UI Text

Run behaviorPress Space to spawn eggs, and see the egg count echoed on screen.

Create UI

  • Right-click in the Hierarchy window: UI (Canvas) › Text - TextMeshPro (or GameObject › UI (Canvas) › Text - TextMeshPro from the menu bar). Canvas and EventSystem are created automatically if they're not already there.
  • UI elements must be children of the Canvas; otherwise they won't be visible.
  • Zoom out to see the two spaces: Camera and UI.
  • UI elements are in pixel space (not camera space)! The size of UI space corresponds 1:1 to the Game window.
  • Use the Layers menu to show/hide UI (top menu bar of the Scene Window).
Unity 6.3 Scene view toolbar with the Layers dropdown open, showing visibility toggles for each layer, including UI, next to the Hierarchy window
Show or hide the UI layer.

UI TextMeshPro (TMP)

Always use TextMeshPro (UI (Canvas) › Text - TextMeshPro), not the old UI (Canvas) › Legacy › Text.

  • TextMeshPro is built into Unity's UI package, but you'll have to import the TMP Essential Resources (fonts and shaders).
  • The first time you create a TMP Text, you'll see the TMP Importer:
TMP Importer window with Import TMP Essentials and Import TMP Examples and Extras buttons
Click Import TMP Essentials. I don't import the Examples & Extras; I have no need for those.

In your script, e.g., in GameManager.cs:

using TMPro;                              // The library to use
public TMP_Text mEggCountEcho = null;     // The variable data type to use

GreenArrowBehavior

  • Includes an int to keep track of the number of eggs on screen.

EggBehavior

  • Static variable: refers to GreenArrowBehavior. What is a “static” variable?
  • Asks the GameManager (sTheGlobalBehavior) whether the egg is within the camera's bounds.
  • Tells GreenArrowBehavior when an egg is destroyed.

GameManager: central object that coordinates the entire game state

  • Hangs off an empty GameObject (GameManager); it can also hang off the MainCamera (always there).
  • Singleton pattern: one instance in the world, “well-known”. A static variable is a global variable!
  • Plays the important role of “central coordinator”:
    • connects GreenArrowBehavior to EggBehavior
    • central place for echoing the state of the game
  • Two public variables must be set in the editor before the game starts:
    • GreenArrowBehavior, and
    • the TMP_Text for echoing egg status (UI-Canvas › EggStatusEcho, a TMP_Text object).
  • IsInCameraBound(Vector3 pt): Determines if pt is inside the camera bound !
Learned
  • GameManager: you need a central coordinator, and often enforce having only one.
    • static declaration in a class
    • Class variables persist for the entire process lifetime, and across levels (scenes).
  • UI: two objects are created automatically:
    • A Canvas: all UI elements must be children of it (otherwise they won't be visible).
    • An EventSystem: I drag it to be a child of the Canvas, for organization.
    • Default UI layer: to see or not to see.
  • Coordinate systems: you work with two separate groups of objects, GameObjects and UI, in two distinct coordinate systems!
    • GameObjects: arbitrary units
    • UI: pixels
  • Canvas and placement of GUI elements:
Check your understanding
  1. In EggBehavior, Destroy(transform.gameObject); comes before sGreenArrow.OneLessEgg();. Does OneLessEgg() still run, and does the egg count stay correct?
    Show answer

    Yes to both. Destroy() only marks the object for removal; Unity removes it at the end of the current frame. The rest of Update() keeps running, so OneLessEgg() is called and the count stays correct.

  2. The GameManager sets sTheGlobalBehavior in Start(). Another script's Start() calls GameManager.sTheGlobalBehavior.IsInCameraBound(...). Is that safe? What would make it safe?
    Show answer

    It isn't guaranteed. Unity doesn't promise which object's Start() runs first, so sTheGlobalBehavior may still be null. Setting it in Awake() instead makes it safe, because every Awake() runs before any Start(). Example 3.2 does exactly this. (Here it works because eggs only use it in Update().)

  3. EggStatusEcho is anchored to the Canvas's lower-left corner, about (134, 34) pixels in. You make the Game window much bigger. Where does the text appear?
    Show answer

    Still about (134, 34) pixels from the lower-left corner. A UI element's position is measured from its anchor, so it keeps its distance from that corner. It doesn't stretch or move toward the middle the way a world object would when the camera's view changes.

2.4

User Interface and Packages

Run behaviorMove the green arrow with W A S D (it no longer follows the mouse); press and hold Space to launch eggs at the interval set by the slider bar.

Package import

  • Assets › Import Package › Custom Package…: select SliderWithEcho.unitypackage (in the Our-Packages folder of the class notes).
    • Prefab: Resources/Prefabs/SliderWithEcho.prefab
    • Scripts/UI/SliderWithEcho.cs
  • To create your own package, do the reverse: Assets › Export Package… and select the appropriate components (remember to select the necessary scripts).

UI: using the imported package

  1. Your scene must first have a Canvas (create one with UI (Canvas) › Canvas).
  2. Drag the SliderWithEcho prefab into the Canvas and align its anchor to the lower-left.
  3. Rename it to SpawnRate.
  4. Configure it (select SpawnRate and look in the Inspector window):
    • mLabelText
    • SliderValue (a Text UI object): customize the text details, e.g., color
    • SliderBar (in the Slider component): set Min Value, Max Value and Init Value
When adjusting the position of the SliderWithEcho object, don't adjust the children's positions!

GreenArrowBehavior: references a SliderWithEcho

  • In the editor, drag the entire SliderWithEcho (SpawnRate) onto the mSpawnRate of the GreenArrowBehavior object.
  • The value() of SpawnRate is how many seconds to wait between spawning new eggs.
  • Time.time: the number of seconds elapsed since the beginning of the game.
Learned
  • Almost all Unity-specific:
    • Time.time: time (in seconds) since the game began
    • Packages: when importing/exporting a prefab, check all its dependent components
  • Note the spawn-rate check logic in GreenArrowBehavior::Update().

Alternate implementation

SpawnRate › SliderBar has a callback (event handler). You can define a function in GreenArrowBehavior and add it here.

ProsReceives value changes via events; no need to check in Update().
ConsImplicit references (via event service functions); it can be hard to figure out all the event handlers.

I usually poll UI events in the Update() function.

ExerciseImplement this alternative.
Check your understanding
  1. The SpawnRate slider is at 0.5. You hold Space for 3 seconds. About how many eggs appear?
    Show answer

    About 6. While Space is held, Update() spawns an egg only when more than 0.5 seconds have passed since the last one (Time.time - mEggSpawnAt > mSpawnRate.value()), so it's one egg roughly every half second.

  2. A classmate exports a package with only SliderWithEcho.prefab and forgets SliderWithEcho.cs. You import it into a new project. What goes wrong?
    Show answer

    The prefab's script component shows up as missing (“The referenced script… is missing”), so the slider moves but the echo text never updates and nothing can call value(). When exporting, include every script and asset the prefab depends on.

2.5

Cool Down Bar

A simple continuation of the previous example
Run behaviorMove with W A S D; press and hold Space to launch eggs at the slider-bar interval, and watch the cool-down bar.

Package import

  • Assets › Import Package › Custom Package…: select CoolDownBar.unitypackage (in the Our-Packages folder of the class notes).
    • Prefab: Resources/Prefabs/CoolDownBar.prefab
    • Scripts/UI/CoolDownBar.cs

UI: using the imported package

  • Remember: you must have a Canvas (create one with UI (Canvas) › Canvas).

GreenArrowBehavior: references a SliderWithEcho and a CoolDownBar

  • In the editor, connect the CoolDownBar UI to the instance variable.
  • Note: the re-spawn time restriction logic is now hidden.
LearnedThe importance and beauty of OO: CoolDownBar hides the spawn-rate logic from GreenArrowBehavior!
Check your understanding
  1. The slider is at 1. You fire an egg, then right away drag the slider to 0.2. When can you fire again?
    Show answer

    Almost immediately: about 0.2 seconds after the first shot. GreenArrowBehavior calls mCoolDown.SetCoolDownLength(mSpawnRate.value()) every frame, so the running cool-down uses the new, shorter length, and the bar jumps to match.

  2. You add a second GreenUp and connect both arrows to the same CoolDownBar. What happens when one of them fires?
    Show answer

    The other one has to wait too. Both arrows ask the same CoolDownBar object whether it's ReadyForNext(), so one cool-down is shared. For separate cool-downs, give each arrow its own CoolDownBar.

2.6

Collision + Color + Texture

No WebGL build: objects must be moved in the editor window
Run behavior Boring! It doesn't even do anything on its own!!
  • You must run, then move objects in the editor so the two overlap.
  • Move either object to collide the two.
  • Wiggle the Plane or GreenUp to see the Plane disappear and regenerate as an Egg.
  • GreenUp moves whatever it touches to the origin.

How Unity collision works: conceptual model, mechanics and cost

  • The GreenUp and the Plane want to collide.
  • Note: this is not physics, just collision detection. We'll look at Unity physics a little later.
  • In this example, the GreenUp has a Rigidbody and the Plane does not:
    • two GreenUps can collide
    • a GreenUp can collide with a Plane
    • two Planes cannot collide
ObjectSetup
GreenUpThe object that wants to handle the collision.
  • Add a BoxCollider2D (any Collider2D works) and check Is Trigger: trigger-related functions on this object will be called.
  • Add a Rigidbody2D (in Physics2D) with Body Type Kinematic.
Plane
  • Add a BoxCollider2D (or any Collider2D, e.g., a CapsuleCollider2D).
  • If the Plane has a script with properly defined OnTriggerEnter2D/Stay2D/Exit2D functions, these functions will also be called whether the collider's Is Trigger is enabled or otherwise.

Try

  • Toggle on/off the Plane's collider's Is Trigger (its script's OnTrigger…2D functions will be called in both cases).
  • When should Is Trigger be on? Only the “actively colliding objects” need it.
    Example My car is driving down the road, and I want it to collide with all the objects in the world. My car is the “actively colliding object”, so its Is Trigger should be on. The rest of the objects aren't actively colliding with anything, so I shouldn't switch on their Is Trigger. It's all about performance!

Scripts

  • GreenUp (Arrow.cs): OnTriggerEnter2D() and OnTriggerStay2D()
  • Plane (PlaneBehavior.cs): OnTriggerEnter2D() and OnTriggerStay2D() → call UpdateColor()
  • In Unity, the OnTrigger…2D() functions are distinct from the OnCollision…2D() functions, although both report when two objects intersect:
    • OnTriggerEnter2D/Stay2D/Exit2D(): tells you two objects have touched, and you decide what to do.
    • OnCollisionEnter2D/Stay2D/Exit2D(): defines what happens and how to respond after the two objects intersect. We'll learn about this in Module 5.
NoticeOnTriggerStay2D() is called while the two objects overlap, but only while the Rigidbody2D is awake. Once nothing moves, the Rigidbody2D falls asleep and the calls stop; moving either object wakes it, and the calls resume.

Texture/sprite loading

  • See Resources.Load() ↗.
  • Notice GetComponent<aComponent>(): look in the editor's Inspector window; every component can be accessed!
  • SpriteRenderer: color + sprite, adjustable in the UI or from a script.
Watch out!If ObjA collides with ObjB and ObjC at the same time (e.g., ObjB and ObjC perfectly overlap), ObjA.OnTriggerEnter2D() will be called twice!
Learned
  • How to enable and work with Unity 2D collisions, and receive the collision function calls to respond
  • How to change the color/transparency of an object at run time
  • How to change the texture of an object at run time
Check your understanding
  1. You remove the Rigidbody2D from GreenUp, keeping both colliders. You run and drag GreenUp onto the Plane. What happens?
    Show answer

    Nothing: no OnTrigger…2D() function is called on either object. A collision needs at least one Rigidbody2D, and GreenUp's was the only one.

  2. The Plane's collider has Is Trigger off; only GreenUp's is on. When they touch, does PlaneBehavior.OnTriggerEnter2D() run?
    Show answer

    Yes. When either collider in a touching pair is a trigger, Unity sends the trigger messages to the scripts on both objects. So the Plane fades even though its own Is Trigger is off.

✎

In-class self-exercise 2

Practice some of the above coverage

Once again, my solution is available at the end of the document.