Unity Basics 4. Scripting GameObjects
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
- it inherits an Unity class
MonoBehaviour
- it inherits an Unity class
- Inside the class, we can implement Unity’s default methods
- e.g.,
Awake()
- e.g.,
- We can also add our own methods
- e.g.,
DoStuffThatWeWant()
- e.g.,
- We can also add new fields: variables inside the class
Start and update
- A new script includes two methods by default:
Start
andUpdate
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
- Called every frame after
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?
- Deltatime is the time spent between update calls, in 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
[SerializeField]
is not the only handy attribute in Unity.[Header("Explainer for UI")]
- Great for team communication
- Script Reference: Header Attribute
[Range(x,x)]
- Adds a slider to inspector
- Script Reference: Range Attribute
- More about attributes in C# Docs
Referring to GameObjects
- The GameObject the script is attached to is usable as
gameObject
- For other GameObjects, there are two options:
- a) Fast solution
- Serialize a GameObject field (shows up in the Inspector)
- Drag & drop the wanted GameObject to the field in Inspector
- b) Find with code
- Script Reference: GameObject.Find
- Script Reference: GameObject.FindGameObjectsWithTag
- Note: these functions can’t find inactive GameObjects.
- a) Fast solution
Accessing Children & Parents
- Accessing a child:
- Unity stores the child-parent hierarchy of GameObjects under the Transform component
- Access by index number
- Script Reference: Transform.GetChild
parentGameObject.transform.GetChild(indexNumber).gameObject
- Access by name
- Script Reference: Transform.Find
parentGameObject.transform.Find("childName").gameObject
- 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- Script Reference: Instantiate
GameObject newObject = Instantiate(bullet, transform.position, transform.rotation);
- Script Reference: Instantiate
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
- “is
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 }
- Another way is to use the GameObject.TryGetComponent method
if (playerObject.TryGetComponent<RigidBody>(out RigidBody rb)) { // do stuff with rb }
- Does the syntax look strange? It uses the out parameter.
- Another way is to use the GameObject.TryGetComponent method
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
- Access it with
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
- a) One script per GameObject
- 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
}
}