Lesson 5 - GetComponent, Instantiate and Destroy

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

What this lesson covers

The lesson, slide by slide

1. GetComponent, Instantiate, Destroy

Title

Unity - Lesson 5

Find, create, copy, remove - four verbs, and the timing that catches everyone.

2. What you will be able to do

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.

  1. Say which of these four calls finds, creates, copies and removes - without hesitating
  2. Explain why new Rigidbody() is not how you make a component
  3. Predict what new GameObject("Coin") gives you, and what it does not
  4. Choose correctly between Destroy(this) and Destroy(gameObject)
  5. Explain why the object is still alive on the line after you destroy it

3. What do you already believe?

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 null
  • new Rigidbody() - Unity refuses. A component cannot exist without a GameObject
  • new GameObject("Coin") - creates an empty object called Coin, with only a Transform. Nothing to do with any prefab named Coin
  • Destroy(this) - removes this component, leaving the object behind

If the last two surprised you, you are exactly where the diagnostic put you - and they are the two that produce the most confusing bugs.

4. GetComponent: the lookup

Section

Part 1

5. It searches; it never creates

Concept

Figure (svg): Four boxes showing what each call does: GetComponent finds, AddComponent creates, Instantiate copies, Destroy removes

Four verbs. Choosing the wrong one is most of what goes wrong here.

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

6. The family of lookups

Concept

Five variants, and knowing which to reach for saves an afternoon.

callsearchesreturns
GetComponent<T>()this object onlythe first T, or null
GetComponents<T>()this object onlyan array of every T
GetComponentInChildren<T>()this object and its childrenthe first T found
GetComponentInParent<T>()this object and its parentsthe first T found
TryGetComponent<T>(out T c)this object onlya 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

7. Predict: the missing component

Prediction

A cube with no AudioSource. Your script calls GetComponent<AudioSource>().Play();

Predict first

What happens?

  • An AudioSource is created and plays
  • Nothing happens, silently
  • A NullReferenceException on that line
  • A compile error

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.

8. Cache it once, in Awake

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.

approach10,000 readsrelative
cached field0.06 ms1x
GetComponent each time1.21 msabout 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.

9. Why is the lookup slower at all?

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.

10. Trap: new Rigidbody()

Trap

The trap

It is a class. C# makes objects with new. So this should work.

void Start()
{
    Rigidbody body = new Rigidbody();      // no
    body.mass = 2f;
}
  • For a MonoBehaviour, Unity logs: You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed.
  • The object exists as a C# instance and is attached to nothing
  • Unity never calls Awake, Start or Update on it
  • It is not in the scene, so nothing can see it, hit it or render it

The fix

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;
}
wantcall
one that is already thereGetComponent<T>()
a new one, hereAddComponent<T>()
one that is there, or a new one if notcheck for null, then AddComponent
a plain data holder with no GameObjecta plain C# class - not a MonoBehaviour

Unity Scripting API - GameObject.AddComponent AddComponent

11. Complete the get-or-add

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.

12. Which lookup?

Discrimination

Sort each situation by the call that solves it.

Sort into buckets

GetComponent, or a variant?

GetComponent / GetComponents (this object)
The Rigidbody on this same object; Every Collider on this object, because there are three
InChildren or InParent (up or down the tree)
The Mesh Renderer on the imported model under this object; The Player script on the object I am parented to
plain
Everything you need is on the same GameObject. Use the plural form when there can be more than one - GetComponent silently returns only the first, which is a quiet source of 'why is only one of them reacting?'.
tree
The component lives on a different object in the same hierarchy. InChildren searches downward including this object; InParent searches upward including this object. Both stop at the first match.

13. A phone book, not a phone

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.

14. Read the exception it produces

Notation

The error you will see most often in Unity. Every part of it tells you something.

Annotate

  • 'Not set to an instance' means you used a variable that holds nothing. In Unity that is nearly always an unassigned Inspector slot or a GetComponent that found nothing.
  • The method tells you WHEN. FixedUpdate means it is happening every physics step, so the Console will fill up - and it also rules out a startup-order problem.
  • The file and line tell you WHERE. Click the line in the Console and Unity opens it. The variable on that line is the null one - start there, not at the top of the file.

Two questions fix almost all of them: is there an empty slot in the Inspector? and did a GetComponent find nothing?

15. Two of these are true about GetComponent

Two truths and a lie

One is false.

Eliminate the wrong options

Which claim is false?

  • A. GetComponent<T>() returns only the first T when several are attached.
  • B. GetComponent<T>() searches this object and its children.
  • C. GetComponent works with interfaces your components implement.

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.

16. Break the caching rule

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:

  • Added later. If another script does AddComponent on you at runtime, a reference cached in Awake was null and stays null.
  • Destroyed later. Destroy(GetComponent<X>()) leaves your cached field pointing at a destroyed component; using it throws.
  • On another object. Caching a reference to something you did not create is fine, but it goes stale when that object is destroyed - so long-lived references need a null check anyway.

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.

17. Which of these need a GameObject?

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?

Needs a GameObject
A Rigidbody; Your MonoBehaviour script
Exists on its own
A Vector3; A plain C# class you wrote (not a MonoBehaviour); A ScriptableObject asset
needs
It derives from Component, so it lives attached to a GameObject and is created with AddComponent. This is why new is refused for these.
free
It is plain data or a plain object. Vector3 is a struct you make freely; a plain class is a normal C# object; a ScriptableObject is an asset that holds data without being in a scene at all - which makes it the right home for configuration a dozen objects share.

18. Instantiate: the copy

Section

Part 2

19. It copies a whole object

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>();
overloaduse 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

20. Predict: new GameObject("Coin")

Prediction

Your project contains a prefab called Coin. Your code runs GameObject g = new GameObject("Coin");

Predict first

What is in the scene now?

  • A copy of the Coin prefab
  • An empty GameObject named Coin, with only a Transform
  • Nothing - it is not in the scene until you add it
  • A compile error

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.

21. Three ways an object appears in a scene

Concept

They look similar in the Hierarchy and are completely different in what they give you.

callyou getcomponents
new GameObject("name")an empty objectTransform only
GameObject.CreatePrimitive(PrimitiveType.Cube)a cubeTransform, MeshFilter, MeshRenderer, BoxCollider
Instantiate(prefab)a copy of the prefabeverything 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.

22. Spawning, and configuring what came back

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.

momentclone state
during Instantiatecreated, Awake runs on every component
the line afterfully constructed; safe to GetComponent and configure
before its first UpdateStart runs - so Start sees your configured values
after you stop Playgone; nothing spawned at runtime is saved

23. Find the spawn bug

Error analysis

A spawner that works in the editor and produces strange enemies.

Annotate

  • The bug. This creates five EMPTY objects called Enemy - no mesh, no collider, no Enemy script. They are invisible and inert, and the Hierarchy looks convincingly full.
  • The prefab reference is right there and unused. The fix is one line: Instantiate(enemyPrefab, SpawnPoint(i), Quaternion.identity).
  • Setting the position afterwards works but does extra work - Instantiate takes a position directly, which also avoids one frame at the origin for anything watching.

This is new-gameobject-clones-prefab in the wild: the name matched a prefab, so it looked right, and nothing errored.

24. Match the call to the result

Matching

Match the pairs

What does each produce?

  • c1. new GameObject("Coin")
  • c2. Instantiate(coinPrefab)
  • c3. gameObject.AddComponent<CoinSpin>()
  • c4. GetComponent<CoinSpin>()
  • r1. an empty object with a Transform
  • r2. a full copy of the prefab, in the scene
  • r3. a new component attached here
  • r4. the component already here, or null

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.

25. Spawn every time, or reuse?

Trade off

A machine gun fires 10 bullets a second for a minute. Weigh the two approaches.

Comparison matrix

Instantiate + Destroy each bulletObject pool (reuse)
Objects created600about 20
Code complexitytriviala pool class to write
Garbage collectedyes - possible hitchesno
Right choice for a first gameyesnot 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.

26. Destroy: the removal

Section

Part 3

27. Destroy schedules, it does not delete

Concept

Figure (svg): A frame timeline showing Destroy being called mid-frame and the object surviving until the end of that frame

Destroy schedules. It does not delete on the spot.

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

28. Measuring the delay

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
momentwatched == nullin the Hierarchy?
before DestroyFalseyes
immediately after DestroyFalseyes
rest of this frameFalseyes
next frameTrueno

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.

29. Predict: the line after Destroy

Prediction

Destroy(enemy); enemy.transform.position = Vector3.zero;

Predict first

What happens on the second line?

  • A NullReferenceException immediately
  • It works - the object is still alive this frame
  • It works but has no effect
  • The Destroy is cancelled

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.

30. Unity's == is not C#'s ==

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 writeyou getuse it?
obj == nulltrue for destroyed objectsyes - this is the idiom
obj != nullfalse for destroyed objectsyes
obj?.Method()bypasses the overload - can hit a destroyed objectavoid on Unity objects
obj ?? fallbacksame problemavoid 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.

31. Trap: Destroy(this) when you meant the object

Trap

The trap

this is the thing. Destroy it and the coin is gone.

void OnTriggerEnter(Collider other)
{
    score += 1;
    Destroy(this);          // removes the Coin SCRIPT
}
  • The coin stays on screen, solid and visible, forever
  • Its collider is still there, so the trigger can fire again - except the script is gone, so nothing happens
  • No error appears anywhere
  • You go looking for a bug in the scoring code, which is fine

The fix

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
}
callremovesleaves behind
Destroy(this)this componentthe object and all its other components
Destroy(gameObject)the whole objectnothing
Destroy(GetComponent<X>())component Xeverything else
Destroy(gameObject, 3f)the whole object, in 3 secondsnothing, after the delay

32. this or gameObject?

Discrimination

Sort each intent by which argument Destroy should get.

Sort into buckets

Which one?

Destroy(gameObject)
A collected coin disappears; A bullet hits a wall
Destroy(this)
A power-up wears off, but the player stays; A one-shot tutorial hint script has finished its job
obj
The whole thing should leave the scene. If you would be surprised to still see it, this is the one you want.
comp
Only a behaviour should stop, and the object carries on. A worn-off power-up and a finished tutorial script are both 'stop doing this, keep being that'.

33. Order the destruction

Ranking

Destroy(enemy) is called during Update on frame 100. Put the events in order.

Put in order

Earliest first.

  1. Destroy(enemy) is called
  2. The rest of frame 100 runs, and enemy != null
  3. OnDisable runs on the enemy's components
  4. OnDestroy runs; the object is removed
  5. Frame 101: enemy == null is true

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.

34. DestroyImmediate, and why not to

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.

callwhen it happensuse in
Destroy(obj)end of framegame code - always
Destroy(obj, delay)after delay secondsgame code - timed cleanup
DestroyImmediate(obj)right noweditor 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.

35. Predict the clone's name

Pattern

You Instantiate a prefab called Coin three times and never set a name.

Predict first

What appears in the Hierarchy?

  • Coin, Coin, Coin
  • Coin(Clone), Coin(Clone), Coin(Clone)
  • Coin(1), Coin(2), Coin(3)
  • Clone, Clone, Clone

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.

36. Count the objects through a spawn and a kill

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?

  1. before
  2. after three spawns
  3. Destroy called
  4. next frame

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.

37. Complete the spawner

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.

38. What do you need before you can spawn?

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:

  • A reference to the prefab asset - a public GameObject field, assigned in the Inspector by dragging from the Project window
  • A position - usually an empty GameObject placed in the room as a spawn point, so the designer can move it without touching code
  • A rotation - Quaternion.identity unless the enemy should face something
  • A parent, or not - spawning under a container object keeps the Hierarchy readable during play
  • A cleanup rule - who destroys it, and when. Without an answer, the scene fills up

The last one is the one people forget, and it is the difference between a spawner and a memory leak with a nice name.

39. Mark where the time goes

Cost model

One Update method, three lines, three very different costs.

Annotate

  • A component search, 60 times a second, for an answer that never changes. Cheap individually; free if cached in Awake.
  • Far worse: GameObject.Find searches every active object in the scene BY NAME, every frame. This is the one that shows up in the profiler. Cache it in Start.
  • Fine. Reading transform and doing arithmetic is cheap, and this line is doing actual work rather than looking things up.

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.

40. Build it in Unity

Section

Build

41. What you are about to 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.

ComponentAccessLab
Cached field vs GetComponent, over 10,000 reads.
SelfDestruct
Destroy(this) and Destroy(gameObject), side by side.
SpawnAndDestroyTiming
Instantiate, then the deferred-Destroy proof.

Files: unity-labs/Assets/NaruhodoLabs/Lesson05_Components/.

42. Plan the timing experiment

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:

  1. Keep a reference to the object you are about to destroy
  2. Check reference == null on the line immediately after Destroy - expect False
  3. Record Time.frameCount at that moment
  4. Check again when Time.frameCount has advanced by one - expect True

The 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.

43. Step 1: time the two access styles

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.

runcached (ms)lookup (ms)ratio
typical desktop0.061.21about 20x
your machine??expect 5x to 40x

44. Step 2: two fuses, one difference

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
cubecallGame viewHierarchy
ADestroy(this)still there, stops spinningstill listed
BDestroy(gameObject)goneremoved 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.

45. Commit before you run step 3

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?

  • False then True
  • True then True
  • False then False
  • True then False

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.

46. Where this shows up in a real game

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.

47. Explain the deferred Destroy

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.'

48. Now break it, on purpose

Concept

Six experiments from the lab notes.

changewhat happens
Use new Rigidbody() instead of AddComponentUnity refuses; for a MonoBehaviour it logs the 'new keyword' error
Use Destroy(this) on a bulletthe bullet freezes in mid-air forever
Touch the clone on the line after Destroyworks this frame; throws when the same shape runs a frame later
Drag a scene object into the prefab slotit still spawns - copies of that scene object, overrides included
Remove the Rigidbody from the benchthe lookup returns null and the lab stops with a FAIL
Spawn 500 projectiles with no fusethe Hierarchy fills and the frame rate sags - nothing cleans up for you

49. One symptom, four suspects

Elimination

A collected coin disappears from the Game view but the score goes up twice.

Eliminate the wrong options

Which explanation fits best?

  • A. The trigger fired twice before end-of-frame destruction removed the coin.
  • B. Destroy(this) was called instead of Destroy(gameObject).
  • C. The coin prefab has two colliders.
  • D. The score variable is not static.

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.

50. The procedure: choosing the call

Pattern

Five questions that pick the right call every time.

  1. Is it already attached? GetComponent<T>() - and cache it in Awake if you will use it again.
  2. Do I need a new component here? AddComponent<T>(). Never new.
  3. Do I need a whole new object? Instantiate(prefab, position, rotation) from a prefab asset.
  4. Do I want the object gone? Destroy(gameObject). Only this behaviour? Destroy(this).
  5. Did I just destroy something? Return. Do not touch it again in this frame.

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.

51. Check 1: what GetComponent does

Check

Answer before you click.

Check your understanding

What does GetComponent<Rigidbody>() do on an object that has no Rigidbody?

  • A. Creates a Rigidbody and returns it
  • B. Returns null (correct)
  • C. Throws a MissingComponentException
  • D. Returns a default Rigidbody with mass 1

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.

Why A tempts people
That is AddComponent. GetComponent never creates anything, which is exactly why the get-or-add pattern needs two calls.
Why C tempts people
Some older Unity APIs throw a MissingComponent error, but GetComponent itself returns null - and that difference is why a null check works.
Why D tempts people
There is no such thing as a default component floating free. A component only exists attached to a GameObject.

52. Check 2: the two Destroys

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)?

  • A. They are the same - this refers to the GameObject
  • B. Destroy(this) removes the Coin component and leaves the object; Destroy(gameObject) removes the whole object (correct)
  • C. Destroy(this) removes the object immediately; Destroy(gameObject) removes it at the end of the frame
  • D. Destroy(this) is not allowed and will not compile

Answer: 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'.

Why A tempts people
This is the misconception the diagnostic recorded. They are two different objects: one is a part, one is the whole, and lesson 1's component model is what separates them.
Why C tempts people
Both are deferred to the end of the frame. Timing is not the difference; what gets removed is.
Why D tempts people
It compiles fine, which is what makes it dangerous - it is a legitimate call that means something you did not intend.

53. One question to sit with

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.

54. Check 3: the empty object

Check

The last one.

Check your understanding

Your project has a prefab named Coin. What does GameObject g = new GameObject("Coin"); produce?

  • A. A copy of the Coin prefab, with all its components
  • B. An empty GameObject named Coin, with only a Transform (correct)
  • C. A reference to the prefab asset itself
  • D. Null, because a GameObject cannot be created with new

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.

Why A tempts people
That is Instantiate with a prefab reference. Matching names mean nothing to Unity.
Why C tempts people
You cannot get a reference to an asset by naming it in a constructor. Assign the prefab in the Inspector, or load it deliberately from a Resources folder.
Why D tempts people
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.

55. A feel for the cost

Estimation

200 objects each call GetComponent once per frame at 60 fps.

Predict first

How many lookups per minute?

  • 12,000
  • 720,000
  • 7,200,000
  • 72,000,000

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.

56. What this unlocks

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 havewhat it enables
GetComponent + cachingreaching any capability from any script
AddComponentbuilding objects from code
Instantiatespawning - bullets, enemies, effects
Destroy and its timingcleanup that does not produce phantom bugs

57. Connect it to something you know

Analogy

Match the pairs

Match each Unity call to its closest cousin elsewhere.

  • u1. GetComponent<T>()
  • u2. AddComponent<T>()
  • u3. Instantiate(prefab)
  • u4. Destroy(obj)
  • o1. querySelector - find something already in the tree
  • o2. createElement plus appendChild, in one call
  • o3. cloneNode(true) on a template
  • o4. marking a node for removal on the next flush

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.

58. Draw the four verbs

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.

59. Before you close this

Exit ticket

Predict first

Which is still shakiest?

  • GetComponent returning null
  • Why new does not work for components
  • Destroy(this) vs Destroy(gameObject)
  • The end-of-frame delay

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.

60. What you can now do

Recap

You can say what each of the four calls produces, and when.

Lab: unity-labs/Assets/NaruhodoLabs/Lesson05_Components/SETUP.md. Watch the Hierarchy during the two fuses, then go to lesson 6.

Sources

  1. Unity Scripting API - Component.GetComponent
  2. Unity Scripting API - GameObject.AddComponent
  3. Unity Scripting API - Object.Instantiate
  4. Unity Scripting API - Object.Destroy
  5. Unity Scripting API - Component.TryGetComponent
  6. Naruhodo Unity Labs - Lesson 05 setup notes — unity-labs/Assets/NaruhodoLabs/Lesson05_Components/SETUP.md

Want this taught 1-on-1? Alexander tutors Unity Game Engine — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108