2.3 — Healing Pickups & the Max-Health Cap
You can gain wealth and lose health. Now you need a way to get health back — and healing without a limit is not a feature, it is an exploit. Today you build the potion, and discover that a value with a ceiling needs a rule, not just arithmetic.
I want a potion that heals me when I walk into it, and I want my health to stop at the maximum and never go over it — no matter how many potions I find.
First, before any code: cut that into parts small enough that you could look each one up on its own.
- Make the potion something the player can touchwhat does this part do?Read: 2.1 — Coins: Triggers, Tags & Your First Stat (the pickup recipe)
- Recognize the potion in the callbackwhat does this part do?Read: the
OnTriggerEnter2D(Collider2D other)row of this lesson's reference table - Change the health valuewhat does this part do?Read: 2.2 — Spikes: Physics, Damage & Death
- Hold the value under its ceilingwhat does this part do?Read: the
Mathf.Clamp(value, min, max),Mathf.Min(a, b),Mathf.Max(a, b)and selection rows of this lesson's reference table - Consume the potionwhat does this part do?Read: the
Destroy(Object)row of this lesson's reference table - Test it at the boundarywhat does this part do?Read: the
Mathf.Clamp(value, min, max)row of this lesson's reference table, and the full-health test at the end of the Build Lab
The split follows the life of one value: an object is created, touched, recognized, changes a number, and dies. Five of those six parts you have already built.
The split is finished when each part can be looked up on its own: every part above names either a component, an API call, or a method you have already written. 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.
In one line: what is the exploit that a potion without a ceiling creates?
Assets/Artwork: pick a
sprite you do have, tint it, and place the instances yourself. Building a mechanic
with borrowed art is called greyboxing, and it is what professionals do all
day: the mechanic is the work, the art is a placeholder.
- Hierarchy → right-click → 2D Object → Sprite. Name it Potion (right-click the object in the Hierarchy → Rename).
- Inspector → Sprite Renderer → Sprite. Drag in any sprite you have, then set Color to a tint that could not be mistaken for a coin at a glance.
- Inspector → Add Component → Circle Collider 2D, then Is Trigger ☑ ON and drag Radius to fit. A potion is a pickup like the coin, so it reports an overlap and pushes nothing.
-
Inspector → Tag → Add Tag… Create a tag called Potion,
then come back to the object and set Tag = Potion on the object that
carries the collider. The tag has to exist in the project before a script can spell it,
and the spelling has to be exact.
The top of the Potion's Inspector. Tagis above every component, not inside one — that is why this step is two trips to the same dropdown: Add Tag… writes the name into the project, and only then will it appear in the list. -
Project window → drag the Potion object from the Hierarchy
into
Assets/Prefabs. The object in the scene is now an instance; the file in the folder is the definition it was stamped from. - Hierarchy → drag at least two instances into the Overworld Scene view, somewhere you will walk: one to test against a damaged player, one to test against a full-health player. A mechanic nobody can walk into is not a mechanic.
There is no code in this section on purpose. Every piece of it you have already built once: a prefab with a trigger collider is 2.1, a method that changes health is 2.2. What is new is the decision — so read the API, then write the file yourself.
First, where the potion sits among the two things you have already built:
| Comparison | Analysis |
|---|---|
| Like the coin | A pickup: trigger collider, handled by OnTriggerEnter2D(Collider2D other), destroyed on contact so it cannot be taken twice. Almost all of your coin code is reusable. |
| Like the spike | It changes health — and health lives on PlayerController, exactly where your coin count lives. |
| Unlike both | It raises a value that now has a ceiling. Without a rule, ten potions means ten times the health, and the game stops being a game. |
Three tools can hold a number inside a range. One selection statement is not a tool at all — it is the thing that can do what none of the three can.
| API | What it is | What you get | What it costs |
|---|---|---|---|
Mathf.Clamp(value, min, max) |
Returns the value pushed inside the range. Integer and float overloads exist. | Both ends in one call, and health stays a whole number. | It returns a number and nothing else — it cannot tell you a heal was wasted. |
Mathf.Min(a, b) |
Returns the smaller of the two. | A ceiling in a single expression. | One end only, so spikes plus potions means a second tool beside it. |
Mathf.Max(a, b) |
Returns the larger of the two. | A floor — "never below zero", on its own terms. | The mirror of Mathf.Min(a, b), and it caps nothing on its own. |
Selection — if / else if / else |
A statement, not a method. It returns nothing. | Runs its block only when the condition holds — and it can change the value and report, or refuse the heal outright. | More lines, and the rule sits at the call site instead of inside the method that owns the value, so the next healing source repeats it. |
[SerializeField] private int |
A field declaration, not a call — the form you have used since 1.3. | A named value that survives every frame and appears in the Inspector as the designer's tuning knob. | A value is not a rule: the Inspector can set maximum health below current health and nothing warns you. |
OnTriggerEnter2D(Collider2D other) |
Callback. Unity calls it; you never do. | Fires on the first frame two shapes overlap. The parameter is the other object's collider. | Triggers only — make the potion solid and OnCollisionEnter2D(Collision2D collision) takes over. |
Mathf.Clamp(value, min, max) whose result you drop is a line that does nothing
at all.
- What field holds the maximum health? What type, what starting value, and how is it different from the current-health field? Which of the two is the designer's tuning knob and which is game state?
- Where exactly does the ceiling get applied — in the potion before it calls the player, or inside the method that changes health? Which of those cannot be bypassed by a future caller?
- Which tool from the table did you choose, and why? If you chose the one that bounds both ends, what is the lower bound — and what does bounding at 0 mean for the death check you wrote in 2.2?
- Which callback, and which tag check, recognizes the potion? Which method on the player do you call, and do you pass a positive or a negative number? Why does one method have to serve both the spike and the potion?
- Do you destroy the potion — and which object's GameObject, the collider's or your own? What should happen when a player already at full health touches one: consume it, or leave it on the ground? Either is defensible — pick one, and name the check your choice requires and where that check belongs.
Save, press Play, and verify. Drop your health on a spike, then walk into a potion and confirm it comes back up. Walk into another potion at full health and confirm your rule holds. Then set maximum health in the Inspector while the game is running and see what your rule does when the ceiling moves underneath the value.
Similar, Different, and Why It Matters
- The potion reuses the coin's trigger, its callback, and its destroy. What is the only thing that genuinely differs?
- Without a ceiling, a player who collects ten potions has ten times the health. Which breaks first — the challenge, the economy, or the feedback the player gets?
- The rule "health must never exceed the maximum" has to live somewhere. Where could it live, and which of those places keeps the rule true no matter which script calls the healing method?
Checkpoint — before moving on, you must be able to:
- Create a prefab and a brand-new project tag from scratch, in the pane each step happens in, and place instances in the scene
- Explain the overheal exploit, and which rule prevents it
- Compare
if,Mathf.Min(a, b),Mathf.Max(a, b)andMathf.Clamp(value, min, max), and defend the one you chose — including what it costs - Explain why the rule lives in the method that changes health rather than in the potion
- Test at the boundary — health exactly at maximum — instead of only in the middle
- Commit and push
Reference Docs
Need a deeper explanation? These pages cover the same concepts:
- Unit 2: Stats & Pickups — selection, pickups and health in depth
- Triggers vs Collisions — the reference for trigger pickups
- Unity + C# Quick Reference — Mathf and selection syntax
- How do I…? index — find this lesson again by the question you were asking
Next: 2.4 — Prefabs, Spawning & Playtesting, where one working object becomes a whole level.
Saved on this device only — the Dashboard tracks your progress.