The three calls you will type more than any others: which one finds a component, which one creates an object, which one removes it - and why Destroy does not take effect until the end of the frame.
Subject: Unity Game Engine · 60 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Unity - Lesson 5
Find, create, copy, remove - four verbs, and the timing that catches everyone.
Objectives
The diagnostic marked this Fragile and left one question blank. All three misconceptions it recorded are about what a call actually creates - so that is what this deck pins down.
new Rigidbody() is not how you make a componentnew GameObject("Coin") gives you, and what it does notDestroy(this) and Destroy(gameObject)Warm-up
From memory. This one is worth writing down, because two of the three answers surprise people.
Discussion prompt
Write what you think each of these does: GetComponent<Rigidbody>(), new Rigidbody(), new GameObject("Coin"), Destroy(this).
Hint: Two of the four do something other than what their name suggests to a C# programmer.
Answer:
GetComponent<Rigidbody>() - finds one already attached, or returns nullnew Rigidbody() - Unity refuses. A component cannot exist without a GameObjectnew GameObject("Coin") - creates an empty object called Coin, with only a Transform. Nothing to do with any prefab named CoinDestroy(this) - removes this component, leaving the object behindIf the last two surprised you, you are exactly where the diagnostic put you - and they are the two that produce the most confusing bugs.
Section
Part 1
Concept
Figure (svg): Four boxes showing what each call does: GetComponent finds, AddComponent creates, Instantiate copies, Destroy removes
GetComponent<T>() looks through the components already on this GameObject and returns the first T it finds - or null if there is none.
GetComponent — A search over the components attached to one GameObject. It returns null when there is no match, which is the API telling you the answer rather than failing.
Unity Scripting API - Component.GetComponent GetComponent
Concept
Five variants, and knowing which to reach for saves an afternoon.
| call | searches | returns |
|---|---|---|
GetComponent<T>() | this object only | the first T, or null |
GetComponents<T>() | this object only | an array of every T |
GetComponentInChildren<T>() | this object and its children | the first T found |
GetComponentInParent<T>() | this object and its parents | the first T found |
TryGetComponent<T>(out T c) | this object only | a bool, and the component via out |
GetComponentInChildren is the usual fix for 'my script is on the parent but the Renderer is on the model' - a very common shape once you start using imported models.
Unity Scripting API - Component.TryGetComponent TryGetComponent
Prediction
A cube with no AudioSource. Your script calls GetComponent<AudioSource>().Play();
Predict first
What happens?
Correct: A NullReferenceException at runtime.
Why: GetComponent returns null, and calling .Play() on null throws. It compiles perfectly, because the compiler cannot know what will be attached at runtime - which is why this is a runtime error and why null-checking a lookup is the normal way to write it.
Worked example
A lookup costs something. Doing it once and keeping the reference costs nothing after that.
public class Mover : MonoBehaviour
{
Rigidbody body; // the cached reference
void Awake()
{
body = GetComponent<Rigidbody>(); // one lookup, ever
}
void FixedUpdate()
{
body.AddForce(Vector3.forward); // just a field read
}
}Awake, not Start
Why: The component is on this object, so it exists as soon as the object does. Caching in Awake also means anything reading you in Start finds you ready.
| approach | 10,000 reads | relative |
|---|---|---|
| cached field | 0.06 ms | 1x |
GetComponent each time | 1.21 ms | about 20x |
Neither number is alarming on its own. The reason to care is that this is per object, per frame, forever - and it costs one line in Awake to avoid.
Explain it to yourself
Discussion prompt
Explain, in your own words, why GetComponent costs more than reading a field - and why the difference grows with the number of components on the object.
Answer:
A field read is one memory access: the reference is already there. GetComponent has to ask the object for its component list and check each entry's type until it finds a match.
So the cost scales with how many components are attached and where the one you want sits. It is a search, and searching for the same thing every frame when the answer never changes is the definition of avoidable work.
Trap
It is a class. C# makes objects with new. So this should work.
void Start()
{
Rigidbody body = new Rigidbody(); // no
body.mass = 2f;
}A component only exists on a GameObject. The call that creates one also attaches it.
void Start()
{
Rigidbody body = gameObject.AddComponent<Rigidbody>();
body.mass = 2f;
}| want | call |
|---|---|
| one that is already there | GetComponent<T>() |
| a new one, here | AddComponent<T>() |
| one that is there, or a new one if not | check for null, then AddComponent |
| a plain data holder with no GameObject | a plain C# class - not a MonoBehaviour |
Unity Scripting API - GameObject.AddComponent AddComponent
Fill the middle
A common helper: use the Rigidbody if there is one, otherwise make one.
Fill in the blanks
Rigidbody EnsureBody()
GetComponent}<Rigidbody>();
if (body == null)
body = gameObject.AddComponent<Rigidbody>();
return body;
}
Why: The null check is not defensive padding - it is how you branch on the API's actual answer, because returning null is GetComponent's way of saying 'there is none'. Note that AddComponent is called on gameObject rather than on the component: you are adding to the object, not to the script.
Discrimination
Sort each situation by the call that solves it.
Sort into buckets
GetComponent, or a variant?
Intuition
GetComponent is looking a number up in a book. AddComponent is getting a new line installed. Reading a cached field is dialling a number you already wrote on your hand.
Nobody looks a number up every single time they dial it - but nobody memorises a number they will use once either. That is the whole caching decision: used repeatedly, cache; used once, just look it up.
And the number can go dead. If the component is destroyed, your cached reference points at nothing - which is why body == null is worth checking on anything long-lived.
Notation
The error you will see most often in Unity. Every part of it tells you something.
Annotate
Two questions fix almost all of them: is there an empty slot in the Inspector? and did a GetComponent find nothing?
Two truths and a lie
One is false.
Eliminate the wrong options
Which claim is false?
Survives elimination: B
Why: The plain form searches this GameObject and nothing else. Children need GetComponentInChildren, parents need GetComponentInParent - and both of those include the object itself, which surprises people the other way round.
Counterexample
Discussion prompt
'Always cache your components in Awake.' Find a case where that is wrong or dangerous.
Hint: What if the component is not there yet in Awake - or stops being there later?
Answer:
Destroy(GetComponent<X>()) leaves your cached field pointing at a destroyed component; using it throws.So the honest rule is: cache what is definitely there for as long as you are. For anything else, TryGetComponent at the point of use is clearer and safe.
Definition probe
Some things in Unity can exist on their own. Components cannot. Sort them.
Sort into buckets
Must it be attached to a GameObject to exist?
new is refused for these.Section
Part 2
Concept
Instantiate(original) makes a new GameObject that is a copy of original - every component, every value, every child. Usually original is a prefab asset, but it can be any object.
GameObject clone = Instantiate(coinPrefab, position, Quaternion.identity);
GameObject child = Instantiate(coinPrefab, parentTransform); // parented
Enemy enemy = Instantiate(enemyPrefab).GetComponent<Enemy>();| overload | use when |
|---|---|
Instantiate(prefab) | position does not matter yet |
Instantiate(prefab, pos, rot) | the usual case - spawn it somewhere |
Instantiate(prefab, parent) | it belongs under something, e.g. UI under a Canvas |
Instantiate(prefab, pos, rot, parent) | both at once |
Unity Scripting API - Object.Instantiate Object.Instantiate
Prediction
Your project contains a prefab called Coin. Your code runs GameObject g = new GameObject("Coin");
Predict first
What is in the scene now?
Correct: An empty GameObject named Coin, with only a Transform.
Why: The string is a NAME, not a lookup. Unity does not search your project for a matching prefab - it makes a blank object and labels it. This is the misconception the diagnostic recorded, and it is a nasty one because the Hierarchy then shows an object called Coin that does nothing at all.
Concept
They look similar in the Hierarchy and are completely different in what they give you.
| call | you get | components |
|---|---|---|
new GameObject("name") | an empty object | Transform only |
GameObject.CreatePrimitive(PrimitiveType.Cube) | a cube | Transform, MeshFilter, MeshRenderer, BoxCollider |
Instantiate(prefab) | a copy of the prefab | everything the prefab had |
So the only one that gives you your object, with your scripts and your tuned values, is Instantiate from a prefab reference. The other two are building blocks.
Worked example
Instantiate returns the clone, which is how you set it up on the next line.
void SpawnEnemy(Vector3 where, int health)
{
GameObject clone = Instantiate(enemyPrefab, where, Quaternion.identity);
clone.name = "Enemy (spawned)";
Enemy enemy = clone.GetComponent<Enemy>();
enemy.health = health; // safe: Awake has already run
}Line 7 is safe because of lesson 4
Why: Instantiate does not return until the clone's Awake has run, so the component exists and has done its own setup. Its Start has NOT run yet - it runs before the clone's first Update.
| moment | clone state |
|---|---|
during Instantiate | created, Awake runs on every component |
| the line after | fully constructed; safe to GetComponent and configure |
| before its first Update | Start runs - so Start sees your configured values |
| after you stop Play | gone; nothing spawned at runtime is saved |
Error analysis
A spawner that works in the editor and produces strange enemies.
Annotate
This is new-gameobject-clones-prefab in the wild: the name matched a prefab, so it looked right, and nothing errored.
Matching
Match the pairs
What does each produce?
Why: Two create, one copies, one finds. The pair worth keeping straight is the first two: both put a new object in the scene, and only one of them gives you an object that does anything.
Trade off
A machine gun fires 10 bullets a second for a minute. Weigh the two approaches.
Comparison matrix
| Instantiate + Destroy each bullet | Object pool (reuse) | |
|---|---|---|
| Objects created | 600 | about 20 |
| Code complexity | trivial | a pool class to write |
| Garbage collected | yes - possible hitches | no |
| Right choice for a first game | yes | not yet |
Pooling is deactivating instead of destroying, and reactivating instead of instantiating - which is the one legitimate use of SetActive as a stand-in for Destroy from lesson 1. Reach for it when the profiler says to, not before.
Section
Part 3
Concept
Figure (svg): A frame timeline showing Destroy being called mid-frame and the object surviving until the end of that frame
Destroy(obj) marks the object for removal at the end of the current frame. Until then it is still there, still in the Hierarchy, still returned by searches.
So the line after a Destroy still runs, and still sees the object. Next frame, it is gone and the reference compares equal to null.
Unity Scripting API - Object.Destroy Object.Destroy
Worked example
The lab destroys a clone and asks the same question twice: right now, and one frame later.
watched = clone;
Destroy(clone);
Debug.Log($"immediately: watched == null is {watched == null}"); // False
// one frame later, in Update:
Debug.Log($"next frame: watched == null is {watched == null}"); // True| moment | watched == null | in the Hierarchy? |
|---|---|---|
| before Destroy | False | yes |
| immediately after Destroy | False | yes |
| rest of this frame | False | yes |
| next frame | True | no |
This is why 'it works, then crashes' happens
Why: Code after a Destroy runs fine during testing, because the object is still alive for the rest of that frame. The crash arrives when a different code path touches it next frame.
Prediction
Destroy(enemy); enemy.transform.position = Vector3.zero;
Predict first
What happens on the second line?
Correct: It works. The object is alive until the end of the frame.
Why: It moves an object that is about to stop existing, which is harmless and pointless. The danger is not this line - it is that the same code shape sometimes runs a frame later, and then it throws. The habit that avoids the whole category: after Destroy, return.
Concept
Unity overloads == for its Object type. A destroyed object compares equal to null even though the C# reference is not really null.
GameObject dead = /* destroyed last frame */;
bool a = dead == null; // true - Unity's overload
bool b = ReferenceEquals(dead, null); // false - the reference is still there
bool c = dead?.name != null; // careful: ?. skips Unity's overload| you write | you get | use it? |
|---|---|---|
obj == null | true for destroyed objects | yes - this is the idiom |
obj != null | false for destroyed objects | yes |
obj?.Method() | bypasses the overload - can hit a destroyed object | avoid on Unity objects |
obj ?? fallback | same problem | avoid on Unity objects |
This is one of the few places where Unity genuinely bends C#. The reason is helpful - it lets you write if (target == null) and have it mean 'gone or never assigned' - but the null-conditional operators do not know about it.
Trap
this is the thing. Destroy it and the coin is gone.
void OnTriggerEnter(Collider other)
{
score += 1;
Destroy(this); // removes the Coin SCRIPT
}this is the component. gameObject is the container. Pick the one you mean.
void OnTriggerEnter(Collider other)
{
score += 1;
Destroy(gameObject); // removes the whole coin
}| call | removes | leaves behind |
|---|---|---|
Destroy(this) | this component | the object and all its other components |
Destroy(gameObject) | the whole object | nothing |
Destroy(GetComponent<X>()) | component X | everything else |
Destroy(gameObject, 3f) | the whole object, in 3 seconds | nothing, after the delay |
Discrimination
Sort each intent by which argument Destroy should get.
Sort into buckets
Which one?
Ranking
Destroy(enemy) is called during Update on frame 100. Put the events in order.
Put in order
Earliest first.
Why: The gap between the first and third entries is the whole lesson: everything else in that frame continues as if the object were alive, because as far as the scene is concerned it is. OnDisable before OnDestroy is the same pairing from lesson 4 - so anything you unsubscribed in OnDisable is already handled by the time OnDestroy runs.
Concept
There is a version that deletes on the spot. It exists for editor scripts, and using it at runtime is how you get a half-torn-down object in the middle of a physics step.
| call | when it happens | use in |
|---|---|---|
Destroy(obj) | end of frame | game code - always |
Destroy(obj, delay) | after delay seconds | game code - timed cleanup |
DestroyImmediate(obj) | right now | editor scripts only |
The lab's editor scripts use DestroyImmediate to clean up temporary objects while building a prefab - which is exactly the situation it is for.
Pattern
You Instantiate a prefab called Coin three times and never set a name.
Predict first
What appears in the Hierarchy?
Correct: Coin(Clone), three times.
Why: Unity appends (Clone) to an instantiated object's name and does not number them. That is why searching for a spawned object by name is fragile, and why the habit of setting clone.name straight after Instantiate is worth having - a Hierarchy of twenty identical entries is no help at all during a bug hunt.
Invariant
Step through four moments and watch which number is stable.
Step through it
Which count never changes, and what does that tell you about Instantiate?
The asset count is always 1. However many clones exist, there is one prefab - which is why editing it still reaches all of them, and why destroying a clone never touches the asset.
Faded example
Fill the blanks so this spawns an enemy, names it, and configures it safely.
Fill in the blanks
public GameObject enemyPrefab;
void SpawnEnemy(Vector3 where, int health)
Instantiate}(enemyPrefab, where, Quaternion.identity);
clone.name = "Enemy (spawned)";
Enemy enemy = clone.GetComponent<Enemy>();
enemy.health = health;
}
Why: GetComponent rather than AddComponent, because the clone already carries every component the prefab had - adding a second Enemy component would give the object two scripts fighting over the same job. And this is safe on the line after Instantiate because Awake has already run on the clone by then.
Missing information
Discussion prompt
A designer asks you to 'spawn an enemy when the player enters the room'. List everything you need to know or have before you can write the line, and where each comes from.
Answer:
The last one is the one people forget, and it is the difference between a spawner and a memory leak with a nice name.
Cost model
One Update method, three lines, three very different costs.
Annotate
The rule underneath: searching is what costs, not doing. Move every search out of the per-frame path into Awake or Start, and the frame gets quiet.
Section
Build
Concept
A bench that times the two ways of reading a component, two cubes that destroy different things, and a spawner that measures the end-of-frame delay.
Files: unity-labs/Assets/NaruhodoLabs/Lesson05_Components/.
Step zero
Discussion prompt
How would you prove, from inside the game, that Destroy does not take effect immediately? Describe the checks and when each must run.
Answer:
reference == null on the line immediately after Destroy - expect FalseTime.frameCount at that momentTime.frameCount has advanced by one - expect TrueThe frame stamp is what makes it a proof rather than a coincidence: it shows the change happened at a frame boundary, not after some vague delay.
Worked example
Same 10,000 reads, two ways of reaching the component.
Stopwatch clock = Stopwatch.StartNew();
for (int i = 0; i < 10000; i++) sink += cachedBody.mass; // field read
double cachedMs = clock.Elapsed.TotalMilliseconds;
clock.Restart();
for (int i = 0; i < 10000; i++) sink += GetComponent<Rigidbody>().mass;
double lookupMs = clock.Elapsed.TotalMilliseconds;The sink variable is not decoration
Why: Summing into a variable that is later printed stops the compiler removing a loop whose result nobody uses - a real hazard when timing anything.
| run | cached (ms) | lookup (ms) | ratio |
|---|---|---|---|
| typical desktop | 0.06 | 1.21 | about 20x |
| your machine | ? | ? | expect 5x to 40x |
Worked example
Two identical cubes, three seconds, one word different.
if (target == Target.ThisComponentOnly)
Destroy(this); // the cube stays, stops spinning
else
Destroy(gameObject); // the whole cube goes| cube | call | Game view | Hierarchy |
|---|---|---|---|
| A | Destroy(this) | still there, stops spinning | still listed |
| B | Destroy(gameObject) | gone | removed from the list |
Watch the Hierarchy while it happens. The Game view cannot distinguish 'stopped' from 'gone' - the Hierarchy can, and it is the honest view.
Hypothesis
The spawner destroys the first clone in the same frame it spawns it, then checks clone == null twice.
Predict first
What pair of values will the Console print?
Correct: False, then True.
Why: False immediately after the Destroy call, because removal is scheduled for the end of the frame; True on the next frame, once the removal has happened. If you predicted True first, that is the DestroyImmediate model - a reasonable expectation, and exactly the one the lab is built to correct.
Real world
Discussion prompt
A bullet script does: on hit, spawn an explosion, tell the target it was hit, play a sound, then Destroy(gameObject). One of those four sometimes fails silently. Which, and why?
Hint: Which of them depends on the bullet still existing after the bullet is gone?
Answer:
The sound. If it is played through an AudioSource on the bullet, the bullet is destroyed at the end of the frame and the AudioSource goes with it - so the sound is cut off almost immediately.
The standard fixes: play it through AudioSource.PlayClipAtPoint, which spawns a temporary object that outlives the bullet, or put the AudioSource on the explosion you just spawned.
The general lesson: anything that must outlive the object cannot live on the object.
Explain it
Discussion prompt
A teammate says 'I called Destroy and the object is still there on the next line - is that a bug?'. Answer in two sentences.
Answer:
Model answer: 'Destroy marks the object for removal at the end of the frame, so everything else in that frame still sees it - that is by design, so half-destroyed objects never appear mid-frame.'
'Just return after calling it, and stop touching the object; if you genuinely need it gone this instant, that is DestroyImmediate, and it is for editor scripts.'
Concept
Six experiments from the lab notes.
| change | what happens |
|---|---|
Use new Rigidbody() instead of AddComponent | Unity refuses; for a MonoBehaviour it logs the 'new keyword' error |
Use Destroy(this) on a bullet | the bullet freezes in mid-air forever |
| Touch the clone on the line after Destroy | works this frame; throws when the same shape runs a frame later |
| Drag a scene object into the prefab slot | it still spawns - copies of that scene object, overrides included |
| Remove the Rigidbody from the bench | the lookup returns null and the lab stops with a FAIL |
| Spawn 500 projectiles with no fuse | the Hierarchy fills and the frame rate sags - nothing cleans up for you |
Elimination
A collected coin disappears from the Game view but the score goes up twice.
Eliminate the wrong options
Which explanation fits best?
Survives elimination: A
Why: The object survives until the end of the frame, so a second overlap in that same frame still finds a live coin with a live script and adds again. The standard guard is a bool - collected - checked at the top of the handler, which is a pattern worth internalising for anything that destroys itself in a callback. Choice C is the other real cause and is worth checking second.
Pattern
Five questions that pick the right call every time.
GetComponent<T>() - and cache it in Awake if you will use it again.AddComponent<T>(). Never new.Instantiate(prefab, position, rotation) from a prefab asset.Destroy(gameObject). Only this behaviour? Destroy(this).Plus the guard that catches the rest: null-check anything a lookup returned before you use it, or use TryGetComponent and let the shape of the code do it for you.
Check
Answer before you click.
Check your understanding
What does GetComponent<Rigidbody>() do on an object that has no Rigidbody?
Answer: B
Why: GetComponent is a search over the components already attached. No match means null - which is the API answering the question, not failing. The exception you eventually see comes from the next line, when you use that null.
Check
The one the diagnostic named directly.
Check your understanding
Inside a MonoBehaviour called Coin, what is the difference between Destroy(this) and Destroy(gameObject)?
this refers to the GameObjectDestroy(this) removes the Coin component and leaves the object; Destroy(gameObject) removes the whole object (correct)Destroy(this) removes the object immediately; Destroy(gameObject) removes it at the end of the frameDestroy(this) is not allowed and will not compileAnswer: B
Why: this is the component - one part bolted to the object. gameObject is the container and everything on it. Destroying the component leaves a visible, solid object that no longer runs your script, which is why the bug looks like 'my code stopped working' rather than 'my object is still here'.
Socratic
Discussion prompt
Why would Unity defer destruction to the end of the frame instead of doing it immediately?
Answer:
Because a frame is full of code that is already holding references. If an object vanished mid-frame, another script's Update could be halfway through using it - and every list of enemies, every physics contact, every UI element would have to be re-checked between every line.
Deferring gives one clean moment where nothing is mid-computation. The cost is the surprise you just learned about; the benefit is that Unity never hands you a half-destroyed object.
Check
The last one.
Check your understanding
Your project has a prefab named Coin. What does GameObject g = new GameObject("Coin"); produce?
Answer: B
Why: The string argument is just a name. Unity does not search the project for a matching asset - it creates a blank object and labels it, so the Hierarchy shows something called Coin that has no mesh, no collider and no script.
new GameObject() is valid and normal - it is how empty parents and runtime containers are made. It just does not do what the name suggests here.Estimation
200 objects each call GetComponent once per frame at 60 fps.
Predict first
How many lookups per minute?
Correct: About 720,000 per minute.
Why: 200 x 60 x 60 = 720,000 searches for an answer that never changes. Each one is cheap; the total is the kind of thing that shows up as a mysterious few milliseconds in the profiler, and it is removed by 200 lines of caching in Awake.
Concept
These four calls are the vocabulary of every gameplay script you will write. Lesson 8's collision callbacks hand you a Collider, and the first thing you do with it is GetComponent.
| you now have | what it enables |
|---|---|
GetComponent + caching | reaching any capability from any script |
AddComponent | building objects from code |
Instantiate | spawning - bullets, enemies, effects |
Destroy and its timing | cleanup that does not produce phantom bugs |
Analogy
Match the pairs
Match each Unity call to its closest cousin elsewhere.
Why: The DOM parallel is close enough to be genuinely useful, deferred removal included - a framework that batches DOM updates has exactly the same 'it is still there for now' behaviour, and produces exactly the same class of bug when you read straight after writing.
Connect it up
Draw it
Draw four boxes: FIND, CREATE, COPY, REMOVE. Put the Unity call in each, and underneath write the one mistake people make with it.
Exit ticket
Predict first
Which is still shakiest?
Correct: Whichever you picked - run that part of the lab and read the Console line for it.
Why: This topic was Fragile with one unanswered question, so a gap here is what the diagnostic predicted rather than a surprise. Each of the four has its own experiment in the lab scene, and each one takes under a minute to watch.
Recap
You can say what each of the four calls produces, and when.
GetComponent<T>() finds - and returns null when there is nothing to find. Cache it in AwakeAddComponent<T>() creates - new is not how components are madeInstantiate(prefab, pos, rot) copies a whole object; new GameObject("x") makes an empty one with that nameDestroy(this) removes the component; Destroy(gameObject) removes the objectLab: unity-labs/Assets/NaruhodoLabs/Lesson05_Components/SETUP.md. Watch the Hierarchy during the two fuses, then go to lesson 6.
Want this taught 1-on-1? Alexander tutors Unity Game Engine — $55/session, free consultation.