Week 3 — Build a Full Game: Duck Invaders · w3d2 · 3h 05m

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 Plugins folder 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 global Scripts folder

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
  • ScreenToWorldPoint explained 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, subtracts nearClipPlane, and feeds that as the Z component of the mouse position
  • MoveTowards with 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 IDamageable interface, an abstract Projectile base with OnCollisionEnter + TryGetComponent, and LaserProjectile deciding what actually happens on hit
  • Weapon base class that instantiates a projectile prefab at a projectileSpawnTransform — position, rotation and local scale, so one weapon can fire bigger versions of the same prefab without a second asset
  • Input split into StartFire, Fire and StopFire so 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 WorldToScreenPoint plus a bounds check destroys them

Health and a first refactor

  • EntityHealth as its own component with Change(float delta) and a public event Action Died, invoked with ?.Invoke()
  • Player and chicken both subscribe to Died in Start and destroy themselves
  • BallisticProjectile extracted once there are two projectile types, with an abstract Direction property — 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, Plugins folder 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:00PlayerInput via ChatGPT, then reading it line by line — ScreenToWorldPoint and 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:00MoveTowards speed 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:00IDamageable interface and the abstract Projectile base class
  • 02:02:00LaserProjectile, FixedUpdate movement, fixedDeltaTime vs deltaTime
  • 02:10:00Weapon class, projectile spawn transform, wiring fire input through the Player script
  • 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:00EntityHealth, the Died event, and refactoring to BallisticProjectile

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.