Guide 3 — Hot Air
Julian Vargas
What you're building
A runaway hot air balloon drifting into a dangerous mountain range with almost no fuel. You manage the burner, swing off rock faces with a grappling hook, and get shoved around by high winds. Reach the checkpoints without hitting anything.
Yours is the first game in the class where the player controller is replaced entirely — no walking, no jumping. BalloonController takes over, and everything else in the kit (health, checkpoints, hazards, camera) carries on working unchanged. That's worth noticing: the kit is built in pieces you can swap.
Time: about two hours, but expect to spend a good chunk of it tuning physics. That tuning is the game.
1. Before you start
- Finish Guide 0.
- No extra packages.
- You'll need one new Input Action:
Grapple. See section 4. - You do not need Guide B's player prefab — skip to section 3. You will want Guide B §B2 (managers) and §B3 (camera).
2. Scripts you'll use
The balloon
BalloonController · FuelSystem · FuelPickup · WindZone2D · GrapplingHook
Core
Health · DamageOnContact · Checkpoint · RespawnManager · CameraTargetAssigner · SceneLoader · GameManager · AudioManager · HUDController · ParallaxBackground
Hazards
SpikeTrap (mountain peaks) · KillZone · LevelExit
You will not need: PlayerController2D, PlayerAnimatorBridge, or anything from Tiers 2, 5, 6, 7, 8. Your balloon doesn't fight anything.
3. Build the balloon
- GameObject → Create Empty, name it
Balloon. - Tag →
Player— everything else in the kit looks for this. - Add a Sprite Renderer with your balloon sprite.
- Add these:
| Component | Settings |
|---|---|
| Rigidbody2D | Gravity Scale 0.3 · Linear Damping 1 · Angular Damping 2 · do NOT freeze rotation |
| Capsule Collider 2D | fit the envelope (the balloon part) |
| Player Input | Behavior: Send Messages · Default Map Player |
| Health | Max Health 1 |
| FuelSystem | Max Fuel 100 · Start Fuel 100 |
| BalloonController | see below |
| GrapplingHook | see section 4 |
Why not freeze rotation?
A balloon that tilts as it drifts looks alive. BalloonController adds a small torque when you steer and a spring that rights it again. Freezing rotation kills that for no benefit.
BalloonController settings
| Field | Value | What it does |
|---|---|---|
| Burner Force | 8 | upward push while holding Jump |
| Fuel Per Second | 10 | how fast the burner drinks fuel |
| Max Rise Speed | 5 | ceiling on how fast you climb |
| Horizontal Force | 3 | steering nudge — deliberately weak |
| Max Horizontal Speed | 4 | |
| Max Sink Speed | 3 | stops you plummeting |
| Lean Torque | 0.5 | tilt when steering |
| Upright Spring | 2 | how fast it rights itself |
| Fuel | drag the FuelSystem on the same object | |
| Burner Particles | a Particle System child, if you make one | plays while burning |
The feel you're going for
The balloon should always be sinking gently. Holding the burner slows or reverses that; letting go means you drift down. Horizontal control should feel weak and floaty — a balloon isn't a plane, and the whole tension of your game is that you can't just fly where you want.
Tune in this order:
- Set Burner Force so that holding it gives a slow, controlled climb — not a rocket. With Gravity Scale 0.3 and mass 1, start at 8.
- Set Max Sink Speed so falling never feels like dropping. 3 is gentle; 5 is scary.
- Only then touch horizontal. If steering feels precise, lower it.
Play after each change. One number at a time.
4. Add the grappling hook
This is your second core mechanic and the fiddliest part of the build. Take it slowly.
First: add the input action
Open your input actions asset (Guide 0 §6):
- Under the Player map, click + → name it exactly
Grapple - Action Type: Button
- Add Binding → Path → Listen → click the right mouse button
- Save Asset
Then: the rope visual
- Right-click
Balloon→ Create Empty, name itRope. - Add a Line Renderer.
- Settings: Positions → Size 2 · Width 0.05 · Use World Space ✓
- Materials → Element 0 → pick any material. (A Line Renderer with no material renders bright magenta — that's not a bug, it just has nothing to draw with.
Sprites-Defaultworks fine.)
Then: the component
On Balloon, add GrapplingHook:
| Field | Value |
|---|---|
| Aim | Mouse |
| Max Distance | 10 |
| Grapple Layer | Ground |
| Rope | drag the Rope child in |
| Reel Speed | 4 |
| Min Rope Length | 1 |
| Rope Goes Slack | ✓ |
| Attach Impulse | 2 |
Rope Goes Slack matters: ticked, the rope is a maximum length and you swing under it like a pendulum. Unticked, it's a rigid rod and you'll bounce strangely.
How it plays
Hold right mouse to fire at a rock face within 10 units. While attached, W/S reel in and out, A/D add swing. Release to let go.
GrapplingHook creates its own DistanceJoint2D at runtime — don't add one yourself.
5. Build the level
Mountains
Your obstacles are terrain, not enemies.
- Draw or block out mountain shapes with sprites or a Tilemap.
- Give them colliders (Polygon Collider 2D works well for irregular rock).
- Layer →
Ground— this does double duty: it's what the balloon crashes into and what the grappling hook can grab. - For the deadly peaks, add a child with a trigger collider and
DamageOnContact(Instant Kill ✓).
Design tip: not every surface should be deadly. If everything kills you, the grappling hook is useless — you need safe rock to swing from. Make the tips of peaks lethal and the flanks safe. That turns the mountains into a resource as well as a threat.
The floor of the world
Empty GameObject with a very wide trigger Box Collider 2D below everything, plus a KillZone component. Falling out of the level should kill you, not drop you forever.
Checkpoints
Same as any game: empty GameObject + trigger collider + Checkpoint. Place them at the safe pockets between hazard stretches. Your pitch mentions checkpoints explicitly, so these are load-bearing — space them so a death costs about thirty seconds.
Fuel pickups
- Empty GameObject
FuelCan+ Sprite Renderer. - Circle Collider 2D, Is Trigger ✓.
- FuelPickup: Amount 30 · Sfx Name
collect· Bob ✓. - Save as a prefab and scatter them along the route.
Where you place fuel is your level design. A can tucked behind a peak makes the player choose between the safe route and the fuel they'll need later. That choice is the most interesting thing in your game.
6. Add wind
- Empty GameObject
WindZone. - Box Collider 2D, Is Trigger ✓, covering the gust area.
- Rotate the object to aim the wind — it blows along the object's local +X (right). The gizmo arrow in the Scene view shows you which way.
- Add WindZone2D:
| Field | Value |
|---|---|
| Strength | 5 |
| Gust Amplitude | 2 |
| Gust Frequency | 0.5 |
| Affected Tags | leave empty (affects everything with a Rigidbody2D) |
The gust settings make the wind pulse rather than push steadily, which reads far better and makes the player adjust constantly.
Your pitch says the player is "pushed through the level by the high winds" — so consider one long, gentle wind zone covering the whole level as the baseline, with stronger local gusts layered on top at pinch points.
7. Camera and managers
Camera
Guide B §B3 — Cinemachine 2D camera + CameraTargetAssigner.
Two adjustments for a balloon:
- Damping Y higher than default (try 1.5) — vertical drift is constant in your game and a twitchy camera is exhausting.
- Dead Zone Height larger (try 0.3) so small bobbing doesn't move the view.
Add a CinemachineConfiner2D with a CameraBounds polygon if your level has edges.
Managers
Guide B §B2: GameManager, RespawnManager, AudioManager, SceneLoader. No CollectionTracker needed — FuelSystem handles fuel on its own.
On RespawnManager: Use Lives unticked (unlimited retries suits a physics game where death is often the wind's fault).
8. The HUD
The fuel gauge is essential — a fuel game with no visible fuel is a guessing game.
- GameObject → UI → Canvas.
- Right-click Canvas → UI → Slider. Name it
FuelBar. Put it top-left.- Delete its Handle Slide Area child (you don't want a draggable handle)
- Set Interactable ✗
- Set Min Value 0, Max Value 1
- Colour the Fill something readable — orange reads as fuel
- Optionally add a TMP text label next to it saying
FUEL.
9. Wire it up
| On this object | Event | Drag in | Choose |
|---|---|---|---|
Balloon → FuelSystem | On Fuel Changed | FuelBar | Slider.value (Dynamic float) |
Balloon → Health | On Died | RespawnManager | RespawnManager.OnPlayerDied |
RespawnManager | On Respawned | Balloon | BalloonController.ResetMotion |
RespawnManager | On Respawned | Balloon | FuelSystem.RefillFull |
Balloon → FuelSystem | On Empty | AudioManager | AudioManager.PlaySFX (string) → fuel_empty |
Balloon → GrapplingHook | On Attached | AudioManager | AudioManager.PlaySFX (string) → grapple |
Rows 3 and 4 matter more than they look. Without ResetMotion, you respawn with all your old velocity and immediately fly into the same rock. Without RefillFull, you respawn with an empty tank and can't do anything. Both are the sort of bug that makes a game feel broken rather than hard.
For row 1, the dropdown will show
valuetwice. Pick the one under Dynamic float — that's the one that receives the number the event sends.
10. First playtest
Build one short stretch — thirty seconds of flying — with a couple of peaks, one wind zone, one fuel can, one checkpoint.
- The balloon sinks slowly when you do nothing. If it drops fast, lower Gravity Scale or raise Max Sink Speed... actually lower Max Sink Speed. If it doesn't sink at all, Gravity Scale is 0.
- Holding Jump climbs. If it rockets, lower Burner Force.
- The fuel bar goes down while burning. If it doesn't move, check the
OnFuelChanged → Slider.valuewiring and that you picked Dynamic. - Fuel runs out and the burner stops.
- A fuel can refills you.
- Right mouse fires the hook at nearby rock, and you swing. If nothing happens: is
Grapplein the input asset? Is Grapple Layer set toGround? Are you within Max Distance? - The rope is visible. Magenta line = no material on the Line Renderer.
- Wind pushes you.
- Hitting a deadly peak kills you and you respawn at the checkpoint with full fuel and no velocity.
11. Then add
- More level. This is the bulk of your remaining work — the systems are done, the game is the route.
ParallaxBackground— mountains at Depth 0.3, distant haze at 0.15, foreground rocks at 0.8. This is high-value for you: parallax sells altitude and drift better than almost anything else.- Burner particles. A Particle System child that plays while burning, dragged into
BalloonController. Cheap and very readable. - Sound. A burner roar, wind, a rope creak. The burner especially — it's the sound of spending your resource.
- A second level with a harder route.
- Optional: a
SpikeTrapon a timer for a geyser or a rockfall, if you want a hazard that isn't static.
12. Design notes for your game specifically
Your game is about scarcity, not skill. The fuel gauge is the whole design. Every burn is a decision the player can't take back, and the interesting moments are the ones where they realise they burned too much earlier. Make sure the gauge is always visible and always legible.
The grappling hook is the answer to "I'm out of fuel." It costs nothing and lets a stranded player recover. That relationship — fuel is limited, rope is free but needs something to grab — is what makes the mountains interesting terrain rather than just obstacles. Design your rock faces with the hook in mind: a stretch with no grabbable surface is a stretch where running out of fuel is fatal, and that should be a deliberate choice.
Wind should be readable before it's felt. Particles, drifting debris, bent trees — anything that shows the player which way a gust blows before they enter it. An invisible force that shoves you into a rock is frustrating; a visible one you have to plan around is a mechanic.
Physics tuning is real work, not a chore. Budget an hour just flying around an empty scene adjusting numbers. Your game lives or dies on whether the balloon feels heavy and reluctant in the right way, and no amount of level design fixes a balloon that handles badly.
13. Common problems
| What you see | What's wrong |
|---|---|
| Balloon falls like a rock | Gravity Scale too high — try 0.3. Check Max Sink Speed too |
| Balloon doesn't move at all | Gravity Scale is 0 and burner off; or Player Input Behavior isn't Send Messages |
| Burner does nothing | FuelSystem not dragged into BalloonController's Fuel field, or fuel is at 0 |
| Fuel bar doesn't move | Wired to the static value, not Dynamic float |
| Balloon spins wildly | Angular Damping too low — raise to 2 or 3. Or Lean Torque too high |
| Hook never attaches | Grapple action missing from the input asset; or Grapple Layer isn't Ground; or you're beyond Max Distance |
| Rope is bright magenta | Line Renderer has no material. Assign Sprites-Default |
| Rope invisible | Line Renderer width is 0, or Use World Space is unticked |
| Swinging feels rigid | Rope Goes Slack unticked |
| Respawn flies straight into a wall | OnRespawned → ResetMotion not wired |
| Respawn with no fuel | OnRespawned → RefillFull not wired |
| Wind blows the wrong way | Rotate the WindZone object — it blows along its local +X. Check the gizmo arrow |
| Everything on screen gets blown around | That's correct behaviour. Set Affected Tags to Player if you only want the balloon affected |