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


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

  1. GameObject → Create Empty, name it Balloon.
  2. Tag → Player — everything else in the kit looks for this.
  3. Add a Sprite Renderer with your balloon sprite.
  4. 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:

  1. 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.
  2. Set Max Sink Speed so falling never feels like dropping. 3 is gentle; 5 is scary.
  3. 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):

  1. Under the Player map, click + → name it exactly Grapple
  2. Action Type: Button
  3. Add Binding → Path → Listen → click the right mouse button
  4. Save Asset

Then: the rope visual

  1. Right-click Balloon → Create Empty, name it Rope.
  2. Add a Line Renderer.
  3. Settings: Positions → Size 2 · Width 0.05 · Use World Space ✓
  4. 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-Default works 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.

  1. Draw or block out mountain shapes with sprites or a Tilemap.
  2. Give them colliders (Polygon Collider 2D works well for irregular rock).
  3. Layer → Ground — this does double duty: it's what the balloon crashes into and what the grappling hook can grab.
  4. 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

  1. Empty GameObject FuelCan + Sprite Renderer.
  2. Circle Collider 2D, Is Trigger ✓.
  3. FuelPickup: Amount 30 · Sfx Name collect · Bob ✓.
  4. 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

  1. Empty GameObject WindZone.
  2. Box Collider 2D, Is Trigger ✓, covering the gust area.
  3. 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.
  4. 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:

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.

  1. GameObject → UI → Canvas.
  2. 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
  3. 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 value twice. 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.

  1. 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.
  2. Holding Jump climbs. If it rockets, lower Burner Force.
  3. The fuel bar goes down while burning. If it doesn't move, check the OnFuelChanged → Slider.value wiring and that you picked Dynamic.
  4. Fuel runs out and the burner stops.
  5. A fuel can refills you.
  6. Right mouse fires the hook at nearby rock, and you swing. If nothing happens: is Grapple in the input asset? Is Grapple Layer set to Ground? Are you within Max Distance?
  7. The rope is visible. Magenta line = no material on the Line Renderer.
  8. Wind pushes you.
  9. Hitting a deadly peak kills you and you respawn at the checkpoint with full fuel and no velocity.

11. Then add

  1. More level. This is the bulk of your remaining work — the systems are done, the game is the route.
  2. 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.
  3. Burner particles. A Particle System child that plays while burning, dragged into BalloonController. Cheap and very readable.
  4. Sound. A burner roar, wind, a rope creak. The burner especially — it's the sound of spending your resource.
  5. A second level with a harder route.
  6. Optional: a SpikeTrap on 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

14. Checklist