3.4 — Projectile Damage, Tuning & Playtest
Your turret shoots, and every bullet passes clean through you. Close that loop, then set the four numbers that decide whether the fight is tense or unfair.
My bullets should actually hurt the player, and the fight should be hard without being unfair.
First, before any code: cut that into parts small enough that you could look each one up on its own.
- Give the bullet a bodywhat does this part do?Read: 2.1 — Coins: Triggers, Tags & Your First Stat (the collider-and-trigger recipe), and the Rigidbody2D row of this lesson's API table
- Detect the hit, and confirm what was hitwhat does this part do?Read: the OnTriggerEnter2D(Collider2D other), OnCollisionEnter2D(Collision2D collision) and other.CompareTag("Player") rows of this lesson's API table
- Apply the damage through the health method you already wrotewhat does this part do?Read: 2.2 — Spikes: Physics, Damage & Death (the method you wrote there), and the GetComponent<T>() row of this lesson's API table
- Remove the bulletwhat does this part do?Read: 3.2 — Projectiles: Instantiate(), Fire, Destroy(), and the Destroy(Object obj) row of this lesson's API table
Three of these four parts are lessons you have already finished, and the fourth is the one genuinely new idea: until now, every script you wrote changed its own values. A bullet has to reach into a different object and ask that object to change its own number, which is a question about access, not about arithmetic.
The split is finished when every part names something you could point at in the editor: a component on a prefab, a callback, a method on a script, a call. If a part needs the word "and" twice to describe it, it is two parts.
Your turn
Pick two of the parts above and say in one line what each one actually does.
Trigger or solid — which did you choose for the bullet, and what does that choice cost you? Name the checkbox and the callback name that have to agree, and what the Console does when they do not.
The projectile prefab is tagged Enemy, the same tag the turret that fired it carries. What will your callback actually test for, and on which object does it ask?
-
Project window →
Assets/Prefabs/Turret_Projectile, then Inspector → a Transform and a Sprite Renderer and nothing else. Note the sprite's Scale: a collider sized from the import bounds will not match it. - Inspector → Add Component → Circle Collider 2D (or Box Collider 2D), then Inspector → Circle Collider 2D → Radius: drag until the green outline covers the art in the Scene view.
- Inspector → Is Trigger decides your callback: ticked, the bullet reports the overlap and passes into you; unticked, it is solid and shoves a Dynamic player. Then Inspector → Add Component → Rigidbody 2D if your setup needs one — Body Type = Dynamic, Gravity Scale = 0, because this world has no "down".
-
Project window →
Assets/Prefabs/Player, then Inspector → Tag: the exact spelling your callback will test, on the object carrying the collider, with the body from 2.2. The pair rule runs this project — a collider on both objects, a body on at least one. -
Once the code half is done — Hierarchy → a Turret
instance, duplicate it (Ctrl+D) so the Overworld carries three, then
Inspector → Turret Controller → each instance's own range and cooldown
(both in
TurretController.cs), the projectile's own speed (Projectile.cs) and damage (today's script). An instance's values override the prefab's, which is how three copies of one prefab become three enemies. - Game view → press Play and stand in front of a turret: health falls by exactly your damage knob, the bullet vanishes, and at zero your 2.2 death rule fires. Console silent? Check the collider before the code, and the tag spelling before the collider.
| API | What it is | What you get | What it costs |
|---|---|---|---|
OnTriggerEnter2D(Collider2D other) or
OnCollisionEnter2D(Collision2D collision) — one, matching your
checkbox |
A callback. Unity calls it; you never do. | The hit report, on the frame the two shapes meet. | Set Is Trigger against the name and it never runs: a misspelt callback is silence, not an error. |
other.CompareTag("Player") |
Identity check by tag. | One check, and you know what you hit. | Exact spelling, and the tag must exist. Ask it of the object carrying the collider, never of the script the callback lives on. |
T GetComponent<T>() |
A component lookup on the object you call it on. | The PlayerController that owns the health. |
A search that returns null when the type is not there, and the plain
version does not look at children. |
Calling a public method on another component —
player.TakeDamage(amount); |
A call on a reference to another script, named whatever you named yours in 2.2. | Hands the problem to the object that owns the data: the bullet only asks. | 2.2's method is private, so nothing outside PlayerController
can see it yet, and everything public becomes a promise to every future caller. |
Destroy(Object obj) |
Removes an object at the end of the frame. | The cleanup 3.2 used on a timer, now on a hit. | It is final and deferred: the object lives out the frame, so a hit can be counted twice unless the callback stops itself running again. |
- The hit: which callback receives the message, and which property carries the tag for that one? Why is the tag check the first filter? Which lookup reaches the
PlayerControlleron the object you hit — plain,InParent, orInChildren— and what guard protects the call when it hands backnull? - The damage: which method do you call on the player, and which sign is the number you pass? Your 2.2 method is
private: what has to change before another script can call it, and what type must your damage knob be to match it? Health lives onPlayerController— the emptyStatManager.csinAssets/Scriptshas no health and no method to change one. - The cleanup: which object do you destroy, and does that line come before the damage or after it? 3.2 ended with this same call on a timer — why does one hit still need both a lifetime and a kill on contact?
Predict Before Building
- The bullet asks the player to change its own health number rather than changing it from outside. Which object owns that number, and what would the bullet have to know about the player that it does not need to know today?
- Tick Is Trigger and the bullet reports; leave it off and it shoves. Which did you choose, and what does the player feel in each case? Name the checkbox and the callback that have to agree.
- The projectile prefab is tagged
Enemy, like the turret and the mobile enemy. Given that, how will a bullet ever know it hit the player — whose tag will you actually test, and on which object?
- Swap seats and run a silent playtest, 2.4 rules: you watch, they play, nobody speaks. Did they notice the turret before it fired, or was the first hit a surprise? Did they try to dodge? Then name your fifth knob — a muzzle offset, a lifetime, a spread — declare it, and fix the top thing the playtest showed you.
Checkpoint — before moving on, you must be able to:
- Explain what the projectile prefab was missing and why no message ever fired, and defend your choice of trigger versus solid by naming the callback it requires
- Explain how a bullet reaches the player's health method, why the lookup can hand back
null, and what the guard does about it - Name the four difficulty knobs, the file each one lives in, and your value for each
- Run a silent playtest and make one evidence-based change, then commit and push
Reference Docs
Need a deeper explanation? These pages cover the same concepts:
- Unit 3: The Turret — the complete turret system
- Triggers vs Collisions — the hit detection reference
- Unity + C# Quick Reference — triggers, tags,
GetComponent<T>() - How do I…? index — find this lesson by the question you were asking
Next: 4.1 — The Enemy: Patrol, where Unit 4 begins and something starts hunting you.
Saved on this device only — the Dashboard tracks your progress.