◀ 4.34.44.5 ▶

4.4 — Attack: Close-Range Contact Damage

The enemy can find you, and now it can reach you. "Touch costs health" sounds like the easiest line in the unit, but the callback that detects the touch runs sixty times a second — and so does the player's death rule from 2.2. Today you pick one of three hazard models and rate-limit it to something a human can survive.

In this lesson you will be able to answer“how do I stop a contact hit hurting me sixty times a second?”Outline only
Break It Down~10 minOutline only

When the enemy reaches me it should cost me health — and it should not cost me the whole bar in a third of a second. "Touching hurts" is the whole feature, and there is nothing in it yet that a computer can act on.

First, before any code: cut that into parts small enough that you could look each one up on its own.

  1. Decide when a hit is allowed to landwhat does this part do?Read: the hazard-model table below, and the callback rows of this lesson's reference table
  2. Confirm the thing you touched is the playerwhat does this part do?Read: the CompareTag(string tag) row of this lesson's reference table, and the coin check you wrote in 2.1
  3. Rate-limit the hitwhat does this part do?Read: the Time.time and cooldown rows of this lesson's reference table, and your own firing gate from 3.3
  4. Deliver the damage through the method you already wrotewhat does this part do?Read: the GetComponent<T>() row and the TakeDamage(int amount) row of this lesson's reference table, and the health method you wrote in 2.2
  5. Decide what does not happen to the enemywhat does this part do?Read: your own projectile callback from 3.4, and the Destroy(Object obj) row that lesson priced

Four of these five parts are lessons you have already finished. A tag check is 2.1, the method and its sign are 2.2, the gate is 3.3, and the reach across to another script is 3.4. The genuinely new one is the first: until today every hazard you built stood still, and a hazard that moves changes which callback is correct. That decision is what everything below it hangs on.

The test: could you look this part up on its own, right now, in one search? "Make contact hurt" cannot be looked up. "Compare the clock against the moment of the last hit" can — and you wrote that comparison in 3.3.

Your turn

Pick two of the parts above and say in one line what each one actually does.

The reading prices three models for when a hit lands and three ways to reach the player's health. Name the two you chose, and say what each one costs you — in your own words, not the table's.

The enemy's animator from 4.2 has an Attack trigger waiting for it. Which of your own fields has to be true before that trigger fires so the picture and the damage agree — and what does the player see when the animation plays and no damage lands?

In the Editor ~20 min
  1. Scene view → save the Overworld before you touch anything. You are about to make contact cost health, and a saved scene is the only way back to a fair fight.
  2. Inspector → the Assets/Prefabs/MobileEnemy root, and read the collider from 4.1: is Is Trigger ticked? Whatever the checkbox says is the family of callback you are allowed to write, and the Console never tells you when the two disagree.
  3. Hierarchy → Player → Inspector → PlayerController. Find the health field from 2.2 and the method that changes it, and read the arithmetic line: which sign hurts? Write it down before you pass a number.
  4. Hierarchy → the four MobileEnemy instances, and Project window → Assets/Scripts/EnemyController.cs. The colliders are already there from 4.1; everything today is code in the file you already have.
  5. Game view → press Play, stand against an enemy and hold still with the Console open. Nothing happens — no callback exists yet. That is the "before" you are about to change; the finished version must not hold it for more than a frame.

The three models you met in 2.2 are the same three, but this hazard moves — so the third appears twice, once per collider the enemy could carry from 4.1. Three models, four rows, and the price column decides it:

Hazard modelWhat firesWhat the player feelsWhat it costs you
A solid enemy, touched once OnCollisionEnter2D(Collision2D collision) You bump into it and lose health once per bump. Damage lands on the frame the two shapes meet and never again while they stay in contact — one hit per pass, the same counting as 2.1's coin. The cost is the exploit: an enemy can pin you against a wall and you take nothing further, so the safest place in the fight becomes the inside of the enemy. A solid collider is also a wall, so it shoves the player.
A trigger enemy, passed through once OnTriggerEnter2D(Collider2D other) It walks through you and one hit lands as it passes. A trigger is a sensor, not a wall: it reports what already happened and never shoves anybody — the coin-and-spike distinction from 2.2. Buying that costs you the enemy's solidity: it passes through walls unless you handle that separately, and a chaser that catches you and stops on top of you deals damage exactly once and then nothing at all, which is the wrong answer for a hunter.
A drain while contact lasts, with a cooldown
solid flavour
OnCollisionStay2D(Collision2D collision) It holds you and your health drains at a steady, readable rate while you stay there. Nothing here limits the rate: your whole bar is gone in about a third of a second, because 2.2's death rule is only a comparison against zero. This is the one model that needs the cooldown from 3.3 — the whole of today's work. It is the solid enemy's shove, applied continuously.
A drain while contact lasts, with a cooldown
trigger flavour
OnTriggerStay2D(Collider2D other) It overlaps you and drains health at that same steady rate while it stays inside you. Same every-frame cost and the same fix as its solid twin; the trigger buys what it bought in the row above — nothing is shoved, and walls stop mattering. Is Trigger has to be ticked to match this callback name, or the Console stays empty.

Socratic: Choosing a Hazard Model

  1. Which of the four rows in the table did you choose, and what does the player learn to do about it after two encounters?
  2. If you chose OnCollisionEnter2D(Collision2D collision) alone, what happens when a chasing enemy pins you against a wall? Is that a fair fight, or an exploit?
  3. You built a hazard once already, in 2.2. What is the difference between that hazard and this one — and does a moving hazard change which model is correct?
In the Code ~25 min

Whichever model you chose, the enemy still has to reach the player's health. Two routes are priced below — a [SerializeField] reference wired on each of the four enemies is a third:

APIWhat you getWhat it costs you
The callback family: void OnCollisionEnter2D(Collision2D collision), void OnCollisionStay2D(Collision2D collision), void OnTriggerEnter2D(Collider2D other), void OnTriggerStay2D(Collider2D other) — Unity calls them, you never do The four messages Unity sends when two shapes meet, overlap, or stay that way. Enter fires once per meeting; Stay fires on every physics step, sixty times a second. The parameter is the other object's collision or collider. Once per entry, and not again until the shapes separate and meet. A misspelled name is not an error, it is silence — and so is a callback whose family does not match the Is Trigger checkbox. Both are the hardest bugs in this unit to find.
bool CompareTag(string tag) — asked of the object that carries the collider: other.gameObject.CompareTag(string tag) or collision.gameObject.CompareTag(string tag) Asks the engine whether that object carries the tag, so one check tells you what kind of thing you touched. Exact spelling, and the tag must already exist in the project. It has to be asked of the object you touched, never of the script the callback lives on.
The lookup, then the call: T GetComponent<T>() or T GetComponentInParent<T>(), then TakeDamage(int amount) on what it hands back The component of that type on the object you call it on — here, the PlayerController that owns the health — and then the method you wrote in 2.2. Both are searches, and a search that finds nothing hands you null. The plain version does not look at children, so the component has to sit on the object you actually touched; the InParent form walks up the parent chain instead.
The declarations, all in the class body and never inside a method: [SerializeField] private int damage, two more fields for the cooldown, and float Time.time — the seconds since the scene loaded, counting up The damage knob a designer turns in the Inspector for how punishing one contact is, and the rate limit from 3.3 pointed at a callback instead of at Update(). The damage type has to match the parameter of the method you call: TakeDamage(int amount) takes an int, so a float knob will not compile without a conversion.
void Debug.Log(object message) Prints to the Console — the only feedback this game has until Unit 5. Text only, and it prints when you call it — the only way to prove a rate limit works with no UI: print the moment damage lands and count hits per second against your cooldown knob.
Architectural specification: the model, the gate and the call.
  • Which of the four rows did you choose, which callback name and parameter type does it give you, and does Is Trigger on the enemy's collider agree with that name? Write the name down before you open the file.
  • What serialized field lets a designer decide how much one hit costs, and what type must it match to work with your player's existing health rule? What default do you ship, given the player's maximum health from 2.3 and the four enemies in the scene — justify the ratio, not the number.
  • Which field records how often this enemy may hurt the player, and which records when it last did? Where do both live, and what value must the remembered moment start at so the first touch lands instantly? Then say whether your rate is per enemy or per player when two of them reach you at once, and whether that is the encounter you meant.
  • How do you confirm the thing you touched is the player, and what must you write for the parameter type that is not a collider directly? Which method on PlayerController do you then call, how do you get from the collided object to it, and what guard protects you when the object carries no PlayerController? Read your own method and name the sign that hurts — then say out loud what the other sign does instead.
  • The callback fires on any contact, not on "the enemy is attacking". Does your enemy deal damage while calmly patrolling too, and if that is wrong, where does the condition that stops it belong? 4.2's Attack trigger is waiting on the same decision — and compare this callback to the projectile's in 3.4: is anything destroyed here?
Do not reach for a manager. StatManager.cs is still an empty stub with no health in it and no method to change anything. Health lives on PlayerController, where you put it in 2.2, and that is what you call. The manager earns its place in 5.3, when a scene change finally gives you a reason.

Checkpoint — before moving on, you must be able to:

Reference Docs

Need a deeper explanation? These pages cover the same concepts:

Next: 4.5 — The Full State Machine: Patrol, Chase, Attack, where all of this becomes one machine you can draw.

0/6 answered

Answers save on this device as you type. At the end of class, click Download and upload the file to the Canvas assignment.

Saved on this device only — the Dashboard tracks your progress.