W3D2: Live coding Duck Invaders (1/4)
Live coding day one of Duck Invaders — project setup, mouse-controlled player, particle trail, laser weapon, IDamageable and collision layers.
Lucian Lazar · 24 Jul 2024
Lucian opens an empty Unity project and starts building the Chicken Invaders clone described in the Duck Invaders GDD. No slides, no prepared repo — just the game design doc on one monitor and three hours of building on the other. This is the first of four live sessions, and it covers the spine of the game: a player you can move and shoot with, an enemy that takes damage and dies, and the folder/assembly structure that keeps the rest of the project from turning into a mess. If you’ve done the earlier weeks and want to see how the pieces fit together in an actual project, this is where that happens. It’s also honest about the process — things break, he debugs them on camera, and at one point he forgets how to multiply two Vector3s and looks it up.
What’s covered
Scoping before coding
- Walking the GDD and marking features as must-have vs optional (loading screens, scrolling background, sound and screen effects get pushed to day three)
- Day one target: player, mouse movement, one weapon, one enemy type with health
- Breadth-first over depth-first — get cubes standing in for every entity in the game before polishing any single one
Project setup
- New 3D built-in render pipeline project, and why he picks 3D even for a 2D game (all the tutorials assume it, 2D is just 3D with constraints, and he keeps the option of moving the camera later)
- A
Pluginsfolder for downloaded assets, and namespacing conventions: anything without a publisher prefix is ours - One assembly definition for the whole game with a root namespace, so new scripts get their namespace generated automatically
- Folders grouped by what’s spatially or temporally close —
Level1/Player,Level1/Weapons— not a globalScriptsfolder
Player movement
- Blocking out a ship from primitive cubes and adding a particle system trail using rate over distance, so the thrust only emits when the ship actually moves
- Simulation space set to World so particles don’t drag along with the parent
ScreenToWorldPointexplained properly: screen space is bottom-left zero, the Z argument is distance from the near plane, and the point projects along the camera frustum rather than straight back- The first version doesn’t work. He stores the initial camera-to-player Z distance in
Start, subtractsnearClipPlane, and feeds that as the Z component of the mouse position MoveTowardswith a speed cap instead of locking the ship to the cursor, because an upper bound on movement speed means players have to think, not just flick
Weapons and damage
- Laser projectile built from stretched cubes with the legacy particles-additive material for the Star Wars bolt look
- An
IDamageableinterface, an abstractProjectilebase withOnCollisionEnter+TryGetComponent, andLaserProjectiledeciding what actually happens on hit Weaponbase class that instantiates a projectile prefab at aprojectileSpawnTransform— position, rotation and local scale, so one weapon can fire bigger versions of the same prefab without a second asset- Input split into
StartFire,FireandStopFireso weapons can decide whether they’re click-to-shoot or hold-to-shoot
Physics debugging, live
- Projectile passes through the chicken. Turns out colliders need a Rigidbody on at least one side, and kinematic bodies won’t report collisions against plain colliders
- Rigidbody goes on the projectile rather than every chicken, for performance
- Four layers (Player, PlayerProjectile, Enemy, EnemyProjectile), then the entire collision matrix gets unchecked and re-enabled only where it matters
- Projectiles that fly off the top never die, so
WorldToScreenPointplus a bounds check destroys them
Health and a first refactor
EntityHealthas its own component withChange(float delta)and apublic event Action Died, invoked with?.Invoke()- Player and chicken both subscribe to
DiedinStartand destroy themselves BallisticProjectileextracted once there are two projectile types, with an abstractDirectionproperty — up for the laser, down for the egg. Abstract only when you actually need it
Timestamps
- 00:00:00 — Stream check, then walking the Duck Invaders GDD and deciding what’s in scope for the three coding days
- 00:11:00 — Breadth-first development: get everything in the game as cubes before polishing anything
- 00:16:00 — Creating the project, 3D built-in render pipeline, and why not the 2D template
- 00:21:00 — Blocking out the player ship from primitives
- 00:26:00 — Particle system thrust trail, rate over distance, world simulation space
- 00:36:00 — Grabbing a free skybox from the Asset Store,
Pluginsfolder convention, lighting environment settings - 00:48:00 — The 2D vs 3D question answered: battery, physics, and why 3D is the standard path
- 00:56:00 — Assembly definition, root namespace, and folder structure by proximity
- 01:04:00 —
PlayerInputvia ChatGPT, then reading it line by line —ScreenToWorldPointand screen bounds - 01:14:00 — Near clip plane and camera frustum demonstrated with a cube in the scene view
- 01:26:00 — Fixing the broken movement by preserving the camera-to-player Z distance
- 01:35:00 —
MoveTowardsspeed cap, tuning the trail, turning the player into a prefab - 01:42:00 — Laser projectile visuals, additive particle material, importing the 2D sprite package
- 01:52:00 —
IDamageableinterface and the abstractProjectilebase class - 02:02:00 —
LaserProjectile, FixedUpdate movement,fixedDeltaTimevsdeltaTime - 02:10:00 —
Weaponclass, projectile spawn transform, wiring fire input through thePlayerscript - 02:26:00 — Chicken enemy, and debugging why the projectile passes straight through it
- 02:40:00 — Layers and the collision matrix
- 02:50:00 — Destroying off-screen projectiles with
WorldToScreenPoint - 02:55:00 —
EntityHealth, theDiedevent, and refactoring toBallisticProjectile
What you build
By the end you have a playable core loop. The ship follows the mouse with a capped speed and a particle trail, fires lasers on click, and the lasers destroy chickens that have their own health. Chicken eggs kill the player back. Everything is prefabbed, layered, and namespaced, and the projectile hierarchy is already set up so adding a third weapon type means writing one direction property.
Two short pieces worth seeing in isolation. The collision check in the base projectile:
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.TryGetComponent<IDamageable>(out var toDamage))
{
ApplyDamageTo(toDamage);
}
}
protected abstract void ApplyDamageTo(IDamageable damageable);
And the health component that everything hangs off:
```csharp
public event Action Died;
public void Change(float delta)
{
health += delta;
if (health <= 0)
{
Died?.Invoke();
}
}
## Who should watch this
This is for people who've learned the individual Unity concepts and now want to see them assembled under time pressure. You'll get as much from the debugging detours — the kinematic Rigidbody problem, the scale bug on spawned projectiles — as from the code that works first time. Watch it alongside the GDD if you're planning to build your own version.
Part of the full curriculum. Course overview.