4. Scripting GameObjects

The GameObject class

Manual: Important classes - GameObject

Creating a new script

  • There are two ways to do it:
    a) Inspector > Add Component > New Script > Create and Add
    b) Project > Right click > Create > C# script, then drag and drop to a GameObject

The Script Class

  • C# is object-oriented: the script is a new Class
  • Inside the class, we can implement Unity's default methods
    • e.g., Awake()
  • We can also add our own methods
    • e.g., DoStuffThatWeWant()
  • We can also add new fields: variables inside the class

Start and update

  • A new script includes two methods by default: Start and Update
  • Start() is called automatically only once
    • It's used for setting things up when we start using the GameObject
  • Update() is called every frame
    • See FPS in Play mode > Stats to check how often it's called!

Two ways to initialize

  • There are two functions for initializing a script class
  • Awake()
    • Called first
    • Called even if the script component is not enabled!
    • Other GameObjects in the scene don't necessarily yet exist when this is called
    • Also, you can't access serialized values from inspector here!
  • Start()
    • Called second, right before the first Update
    • Only called if the script component IS enabled
    • Other GameObjects in the scene already exist
    • Serialized variables exist as well

Update() function

  • There are three functions for updating a script class
  • Update()
    • Frequency of update calls varies depending on framerate
    • Most things can be updated here
    • Because of framerate-dependence it is indeterministic
      • (same input doesn't always produce same output)

FixedUpdate() and LateUpdate()

  • FixedUpdate()
    • By default, called every 0.2 seconds (50 FPS)
    • Used mainly for physics calculations
      • Will slow down under heavy load!
      • Not dependent on framerate: deterministic
        • (same input always produces same output)
    • Note: Can't be used for checking ButtonDown input
  • LateUpdate()
    • Called every frame after Update().
    • Good for something that has to happen after all game objects have Updated

Time and Deltatime

  • Important classes: Time
  • Time.time
    • The time passed since starting the game, in seconds
  • Time.deltaTime
    • Deltatime is the time spent between update calls, in seconds
      • Relates to FPS, or frames per second
      • deltatime = 1 / FPS
    • Can be used for accounting for framerate in movement
      Vector3 velocity = new Vector3(speed * Time.deltaTime, 0.0f, 0.0f);
      transform.position += velocity;
      
    • Beware lag spikes, though: what would velocity be if deltatime was equal to one second? Three seconds?

Accessing fields in Inspector

  • Every time we change our code, we have to wait for Unity to compile scripts.
  • It's especially annoying when fine-tuning variable values
  • Luckily, we can edit script variables right in the inspector
  • Manual: Variables and the Inspector
  • public variables show up in Inspector
  • ...as do the ones with a [SerializeField] attribute
  • Use public only when you need to edit the value from other scripts
    • [SerializeField] is safer (can't be edited from other scripts)

Extra: Other attributes

Referring to GameObjects

Accessing Children & Parents

  • Accessing a child:
  • Accessing a parent:
    • GameObject only has one direct parent
    • childGameObject.transform.parent

Creating and destroying GameObjects

  • Instantiate(): Create copies of GameObjects or Prefabs into the scene with
  • Destroy(): destroy GameObjects from the scene
    • Script Reference: Destroy
    • you can give an additional delay in seconds before destroying as a second argument
      Destroy(bullet, 2.0f);
      

Activating and deactivating GameObjects

  • Inspector: see the checkbox left to the GameObject's name
  • gameObject.SetActive(false);
    • will deactivate the object AND ITS CHILDREN.
  • myObject.activeSelf
    • false tells if this particular object has been deactivated
    • even if true, myObject can still be deactivated if a parent is deactivated
  • myObject.activeInHierarchy
    • "is myObject really active right now?"
    • false means this object has been deactivated by itself or by its parents

Exercise 1. Three, two, one...

  • Create a script on an empty GameObject
  • Make it instantiate a bullet Prefab every three seconds.
  • Create another GameObject, and make the script destroy it when three seconds have passed.

Accessing components

  • Access GameObject's components with GameObject.GetComponent
    OurComponentType ourComponent = ourGameObject.GetComponent<OurComponentType>();
    
  • For example, to get the Rigidbody component:
    Rigidbody rb = playerObject.GetComponent<Rigidbody>();
    
  • Dot notation not needed when getting a component of the GameObject the script class is part of:
    Rigidbody rb = GetComponent<Rigidbody>();
    

Checking if component exists

  • It's a good idea to check if a component exists before actully using it

    Rigidbody rb = GetComponent<Rigidbody>();
    if (rb != null)
    {
      // do stuff with rb
    }
    
    if (playerObject.TryGetComponent<RigidBody>(out RigidBody rb))
    {
      // do stuff with rb
    }
    

Enabling and disabling components

  • enable component:
    • component.enabled = true;
  • disable component:
    • component.enabled = false;
  • toggle:
    • component.enabled = !component.enabled
  • Note: Disabling a script component only disables calls to Awake, Start, Update, LateUpdate, FixedUpdate...
    • Most event-based callbacks don't get disabled!

Tags & Layers

  • Manual: Tags and layers
  • Edit > Project Settings > Tags and Layers
  • Here, you can set up
    • Tags
    • Layers
    • Sorting layers

Tags

  • Manual: Tags
  • Marker values that that you can use to identify objects in your Project
  • Example tags: EditorOnly, MainCamera, Player
  • GameObject can only have ONE tag!
    • Access it with myGameObject.tag
  • GameObject.FindWithtag("tagname");
  • GameObject.FindGameObjectsWithTag("tagname");

Tag-based collision

private void OnTriggerEnter2D(Collider2D other)
{
    if(other.gameObject.tag == "Collectible")
    {
        Destroy(other.gameObject);
    }

Layers

  • Manual: Layers
  • Layers allow you to separate GameObjects in you scene through UI or scripting
  • Some layers: Default, Ignore Raycast, Custom...
  • To make Camera ignore some layers:
    • Inspector > Camera > Culling Mask > Layers
  • To make Viewport ignore some layers:
    • Top right: Layers dropdown
  • Layers can be used for selective collision detection

Layer-based collision detection

  • To make Player and Enemies collide with walls, but not with each other:
    • Set Player layer to Player
    • Set Enemy layer to Enemies
    • Set Walls layer to Walls
    • Open Edit > Project preferences > Physics(2D) > Layer Collision Matrix
    • Disable collision between Enemies and Player:

Sorting layers

  • For sorting 2D sprites
    • "which goes on top of which"
    • act kind of like Photoshop layers

About script reusability

  • two extreme approaches to scripting GameObjects
    • a) One script per GameObject
      • can make files bloated
    • b) One script per functionality
      • possibly reusable code!
      • possibly more confusing
      • can take more time
  • My way: First put everything in one script until some functionality grows enough
    • Then, separate into its own script

Exercise 2. Available on switch

Create a Scene with following GameObjects:

  • Three light source GameObjects
    • render the light bulb as well (it can be a sphere for instance)
  • ⭐ A cube that acts as a light switch (turns on/off the lights, but bulbs are seen)
  • ⭐⭐ A cube that acts as a kill switch that destroys the lights
  • ⭐⭐⭐ A cube that acts as a create switch that creates new lights if they were destroyed

Spoiler: For a click response, you can use this method:

void OnMouseOver() {
  if (Input.GetMouseButtonDown(0) {
      // Do stuff
  }
}

Reading