7 painful nights at corbuns zombie island v2 offical

This is an immersive 3D first-person survival game built with Three.js where players explore a tropical island, manage health and hunger, and battle waves of zombies during nighttime. The game features day-night cycles, multiple zombie types, hired guards, a climbable ladder, and fruit-gathering mechanics.

🧪 Try This!

Experiment with the code by making these changes:

  1. Spawn more frequent zombies — Change the zombieSpawnInterval to spawn a new zombie every 1 second instead of 2 seconds, doubling the challenge during night
  2. Make Tank zombies dominant — Change zombie spawn weights so 80% are Tank zombies (tough but slow) instead of 60% Regular, making combat much harder
  3. Make fruit hunger-cheap to harvest — Reduce the hunger cost to hire a guard from 77 to 30, making it much easier to spam guards for support
  4. Speed up the night — Cut night length to 150 seconds (2.5 minutes) from 300, making each night pass twice as fast but giving less time to survive
  5. Disable climbing altogether — Comment out the ladder interaction check so pressing E no longer climbs, removing roof access as a strategy
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates an immersive first-person 3D survival game on a tropical island rendered in Three.js. You explore a house, palm trees, and open terrain while managing health, hunger, and ammunition through a day-night cycle. During the day, you harvest fruit from palm trees to keep your hunger meter full; at night, waves of three different zombie types—regular, fast, and tank variants—spawn and attack while you defend yourself with a gun and hired guards. The visuals are powered by Three.js 3D rendering, PointerLockControls for first-person movement, real-time collision detection, and dynamic lighting that shifts with the time of day.

The code is organized into a Three.js scene setup in setup(), a main game loop in draw() that updates all game systems sixty times per second, and specialized classes for Zombie, Bullet, Casing, and Guard entities. You will learn how to build a complete game loop with state management, how to implement procedural enemy spawning with type variation, how collision detection protects the player from walking through walls, and how a day-night cycle drives gameplay pacing and zombie behavior.

⚙️ How It Works

  1. When the sketch loads, setup() initializes a Three.js scene with a camera, lighting (directional sun and hemisphere ambient light), a ground plane, a house structure with walls and a tilted ladder, and ten randomly-placed palm trees. Player position starts outside the house, and the game waits for the 'Click to Start Game' button.
  2. Once the game starts via startGame(), the draw() loop begins running sixty times per second. Each frame updates the day-night cycle, player movement from keyboard input (W/A/S/D for movement, Space to jump, E to interact), zombie positions, bullet trajectories, ammo casings physics, and hunger depletion.
  3. The day-night cycle runs on a 60-second day followed by a 300-second night. During the day, the sun moves across the sky, zombies do not spawn, and the player can harvest fruit from palm trees by pressing E near them to restore hunger. The UI shows health, ammo, hunger, and nights survived.
  4. When night falls, the sky darkens, a gun appears in the player's hand, and zombies begin spawning every 2 seconds in random locations around the island. There are three zombie types, each with different speeds, health, and damage values: Regular (60% spawn rate, slow but tough), Fast (25% spawn rate, quick but fragile), and Tank (15% spawn rate, slow and durable but deadly).
  5. The player shoots zombies by clicking the mouse; bullets are created from the camera position and travel in the look direction. Each shot costs 1 ammo (or is free in God Mode). When a bullet hits a zombie (distance < 1), it deals 10 damage and despawns. Shells eject from the gun with physics simulation and clatter to the ground.
  6. The player can hire guards by pressing Q (costs 77 hunger points). Guards spawn near the player, automatically target and shoot at nearby zombies, and defend the player. If hunger reaches zero, the player takes damage over time and must eat fruit quickly. Surviving 7 complete night cycles wins the game; dying from zombies, starvation, or falling outside the map boundaries ends it. A tilted ladder on the back of the house can be climbed with E and W/S keys to reach the roof for a vantage point.

🎓 Concepts You'll Learn

Three.js 3D rendering and scene graphFirst-person camera controls (PointerLockControls)Real-time collision detection (Box3, Sphere)Object-oriented game entities (Zombie, Bullet, Guard classes)Day-night cycle and time-based state managementProcedural enemy spawning with type variationPhysics simulation (gravity, velocity, projectiles)Dynamic lighting and shadow mappingGame state machine (day, night, game over, win)UI updates and player feedback systems

📝 Code Breakdown

setup()

setup() runs once when the sketch loads. It is where you initialize your Three.js scene, add all permanent objects (lights, ground, house, trees), configure the camera and renderer, and set up UI. The separation of setup() and draw() mirrors p5.js tradition but uses Three.js for 3D rendering instead of 2D canvas drawing.

function setup() {
  createCanvas(1, 1);
  noLoop();

  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;

  camera.position.copy(playerPosition);

  hemisphereLight = new THREE.HemisphereLight(0xadd8e6, 0x00008b, 0.5);
  scene.add(hemisphereLight);

  directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  directionalLight.position.set(5, 10, 5);
  directionalLight.castShadow = true;
  directionalLight.shadow.mapSize.width = 2048;
  directionalLight.shadow.mapSize.height = 2048;
  directionalLight.shadow.camera.near = 0.5;
  directionalLight.shadow.camera.far = 50;
  directionalLight.shadow.camera.left = -20;
  directionalLight.shadow.camera.right = 20;
  directionalLight.shadow.camera.top = 20;
  directionalLight.shadow.camera.bottom = -20;
  scene.add(directionalLight);

  houseInteriorLight = new THREE.PointLight(0xffffee, 0.8, 10);
  houseInteriorLight.position.set(0, 3, 0);
  houseInteriorLight.castShadow = true;
  scene.add(houseInteriorLight);

  createGround();
  createMapBarriers();
  createHouse();
  createPalmTrees(10);

  controls = new THREE.PointerLockControls(camera, renderer.domElement);
  scene.add(controls.getObject());

  uiDiv = createDiv();
  uiDiv.id('ui');
  uiDiv.style('position', 'absolute');
  uiDiv.style('top', '10px');
  uiDiv.style('left', '10px');
  uiDiv.style('color', 'white');
  uiDiv.style('font-family', 'monospace');
  uiDiv.style('font-size', '16px');
  uiDiv.style('z-index', '100');

  healthSpan = createSpan(`Health: ${playerHealth}`);
  healthSpan.id('health');
  uiDiv.child(healthSpan);
  uiDiv.child(createElement('br'));

  ammoSpan = createSpan(`Ammo: ${playerAmmo}`);
  ammoSpan.id('ammo');
  uiDiv.child(ammoSpan);
  uiDiv.child(createElement('br'));

  hungerSpan = createSpan(`Hunger: ${playerHunger}`);
  hungerSpan.id('hunger');
  uiDiv.child(hungerSpan);
  uiDiv.child(createElement('br'));

  nightSpan = createSpan(`Nights Survived: ${nightsSurvived}`);
  nightSpan.id('nights');
  uiDiv.child(nightSpan);
  uiDiv.child(createElement('br'));

  messageDiv = createDiv('');
  messageDiv.id('message');
  messageDiv.style('font-size', '24px');
  messageDiv.style('text-align', 'center');
  messageDiv.style('position', 'absolute');
  messageDiv.style('width', '100%');
  messageDiv.style('top', '40%');
  uiDiv.child(messageDiv);

  instructionsDiv = createDiv(`
    <p>Welcome to Island Survival!</p>
    <p>Controls:</p>
    <ul>
      <li>W, A, S, D: Move</li>
      <li>Space: Jump</li>
      <li>Mouse: Look around</li>
      <li>Left Click: Shoot (at night)</li>
      <li>R: Reload (at night)</li>
      <li>E: Interact (e.g., harvest fruit from trees, climb ladder)</li>
      <li>Q: Hire a guard (${GUARD_COST_HUNGER} hunger)</li>
      <li>Esc: Exit Pointer Lock</li>
    </ul>
    <p>Survive 7 nights to win. Zombies appear at night. A gun will appear when night falls. Keep your hunger up by eating fruit!</p>
    <p>Watch out for different types of zombies: Regular 🧟, Fast 💨, and Tank 🛡️!</p>
  `);
  instructionsDiv.id('instructions');
  instructionsDiv.style('position', 'absolute');
  instructionsDiv.style('top', '50%');
  instructionsDiv.style('left', '50%');
  instructionsDiv.style('transform', 'translate(-50%, -50%)');
  instructionsDiv.style('background', 'rgba(0,0,0,0.7)');
  instructionsDiv.style('padding', '20px');
  instructionsDiv.style('border-radius', '10px');
  instructionsDiv.style('color', 'white');
  instructionsDiv.style('text-align', 'left');
  instructionsDiv.style('z-index', '101');
  document.body.appendChild(instructionsDiv.elt);

  startGameButton = createButton('Click to Start Game');
  startGameButton.parent(instructionsDiv);
  startGameButton.style('margin-top', '20px');
  startGameButton.style('padding', '10px 20px');
  startGameButton.style('font-size', '18px');
  startGameButton.style('cursor', 'pointer');
  startGameButton.mousePressed(startGame);

  gunMesh = new THREE.Mesh(
    new THREE.BoxGeometry(0.1, 0.1, 0.5),
    new THREE.MeshStandardMaterial({ color: 0x808080 })
  );
  gunMesh.position.set(0.3, -0.2, -0.5);
  gunMesh.castShadow = true;
  gunMesh.receiveShadow = true;
  gunMesh.visible = false;
  camera.add(gunMesh);

  renderer.domElement.addEventListener('mousedown', onMouseDown, false);
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

initialization p5.js Canvas Setup createCanvas(1, 1); noLoop();

Creates a minimal p5.js canvas to satisfy p5.js lifecycle; noLoop() prevents p5.js draw from running until startGame() calls loop()

initialization Three.js Scene and Renderer scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); renderer.shadowMap.enabled = true;

Initializes the Three.js scene graph, a perspective camera with 75° field of view, and a WebGL renderer that fills the entire window with shadow mapping enabled

initialization Lighting System hemisphereLight = new THREE.HemisphereLight(0xadd8e6, 0x00008b, 0.5); scene.add(hemisphereLight); directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(5, 10, 5); directionalLight.castShadow = true;

Creates ambient hemisphere lighting (sky blue above, dark blue below) and a directional sun light that casts shadows—both change during the day-night cycle

initialization Environment Creation createGround(); createMapBarriers(); createHouse(); createPalmTrees(10);

Populates the scene with the ground plane, invisible map barriers at edges, the house structure, and 10 randomly-scattered palm trees

initialization Pointer Lock Controls controls = new THREE.PointerLockControls(camera, renderer.domElement); scene.add(controls.getObject());

Attaches PointerLockControls to the camera so mouse movement rotates the camera view in first-person

initialization UI Creation uiDiv = createDiv(); uiDiv.id('ui');

Creates a p5.js div to hold HUD elements like health, ammo, hunger, and nights survived

initialization Gun Model gunMesh = new THREE.Mesh( new THREE.BoxGeometry(0.1, 0.1, 0.5), new THREE.MeshStandardMaterial({ color: 0x808080 }) ); gunMesh.position.set(0.3, -0.2, -0.5); gunMesh.visible = false; camera.add(gunMesh);

Creates a simple grey box gun model and attaches it to the camera so it stays in the player's right hand; hidden until nightfall

createCanvas(1, 1);
Creates a 1x1 p5.js canvas (mostly invisible) to maintain p5.js event handling and lifecycle
noLoop();
Stops the p5.js draw loop from running automatically—the loop only starts when startGame() is called
scene = new THREE.Scene();
Creates a Three.js scene object that will hold all 3D objects (camera, lights, meshes, zombies)
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
Creates a perspective camera with a 75-degree field of view; 0.1 to 1000 are near and far clipping planes
renderer = new THREE.WebGLRenderer({ antialias: true });
Creates a WebGL renderer with antialiasing enabled to smooth out jagged edges
renderer.shadowMap.enabled = true;
Enables shadow mapping so objects cast realistic shadows on surfaces
hemisphereLight = new THREE.HemisphereLight(0xadd8e6, 0x00008b, 0.5);
Creates ambient lighting: light blue from the sky, dark blue from below, intensity 0.5—simulates natural ambient light
directionalLight.castShadow = true;
Marks the directional (sun) light as shadow-casting so objects will have shadows on the ground
directionalLight.shadow.mapSize.width = 2048;
Sets the shadow map resolution to 2048 pixels for crisp shadow edges (higher = more detailed but slower)
createGround();
Calls a helper function that creates a large green plane to serve as the island terrain
controls = new THREE.PointerLockControls(camera, renderer.domElement);
Creates pointer lock controls that make the mouse control camera rotation; locked when the player clicks the canvas
scene.add(controls.getObject());
Adds the camera to the scene; the controls object wraps the camera and handles mouse look
gunMesh.visible = false;
Hides the gun initially; it becomes visible when night falls
camera.add(gunMesh);
Attaches the gun to the camera so it moves and rotates with the player's viewpoint

draw()

draw() is the core game loop, called 60 times per second. It updates all game state and renders each frame. The pattern—calculate delta time, update all systems, check collisions, render—is the foundation of real-time game development. Notice how each system (movement, enemies, bullets) is updated independently, then collisions are checked to resolve interactions.

function draw() {
  if (gameOver || winGame) {
    noLoop();
    return;
  }

  const deltaTime = (millis() - time) / 1000;
  time = millis();

  updateDayNightCycle();
  updatePlayerMovement(deltaTime);
  updateZombies(deltaTime);
  updateBullets(deltaTime);
  updateCasings(deltaTime);
  updatePalmTrees(deltaTime);
  updateHunger(deltaTime);
  updateGuards(deltaTime);
  checkCollisions();
  checkGuardCollisions();
  drawUI();

  renderer.render(scene, camera);
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

conditional Game Over Check if (gameOver || winGame) { noLoop(); return; }

Stops the game loop if the player has lost or won

calculation Delta Time Calculation const deltaTime = (millis() - time) / 1000; time = millis();

Calculates elapsed time in seconds since the last frame for smooth, frame-rate-independent updates

sequence Game System Updates updateDayNightCycle(); updatePlayerMovement(deltaTime); updateZombies(deltaTime); updateBullets(deltaTime); updateCasings(deltaTime); updatePalmTrees(deltaTime); updateHunger(deltaTime); updateGuards(deltaTime); checkCollisions(); checkGuardCollisions();

Calls all game system update functions in order to advance the simulation one frame

calculation Three.js Render renderer.render(scene, camera);

Renders the entire Three.js scene from the camera's viewpoint to the screen

if (gameOver || winGame) {
Checks if the game has ended (player died or survived 7 nights)
noLoop();
Stops the draw loop from running so the game freezes on the final frame
const deltaTime = (millis() - time) / 1000;
Calculates how many seconds have passed since the last draw call by subtracting the last recorded time from the current time and converting milliseconds to seconds
time = millis();
Records the current time in milliseconds for use in the next frame's delta time calculation
updateDayNightCycle();
Updates the sun position, lighting colors, and spawns zombies during night or clears them at dawn
updatePlayerMovement(deltaTime);
Processes keyboard input, updates player position and velocity, handles jumping, climbing, and ladder interaction
updateZombies(deltaTime);
Moves all zombies toward the player or guards and triggers attacks when they get close
updateBullets(deltaTime);
Moves all active bullets forward and removes them when their lifetime expires
updateCasings(deltaTime);
Updates physics for ejected shell casings (gravity, bouncing, tumbling)
updatePalmTrees(deltaTime);
Checks if fruit on any palm tree should respawn based on elapsed time
updateHunger(deltaTime);
Decreases player hunger over time and applies starvation damage if hunger reaches zero
updateGuards(deltaTime);
Updates all hired guard positions and makes them shoot at nearby zombies
checkCollisions();
Checks for bullet-zombie hits and player-wall collisions to prevent walking through houses and barriers
checkGuardCollisions();
Checks for zombie-guard contact so guards can take damage from close zombie attacks
drawUI();
Updates the on-screen UI text (health, ammo, hunger, nights) with current game values
renderer.render(scene, camera);
Tells Three.js to draw everything in the scene as seen from the camera's perspective to the canvas

updatePlayerMovement()

updatePlayerMovement() handles all player-driven changes to position and state. It demonstrates key concepts: pointer lock for first-person, gravity and ground collision, keyboard input mapping, and special interaction zones (ladder). The use of applyQuaternion() is crucial—it rotates movement by the camera's orientation so forward always means toward where the player is looking.

🔬 This block controls climbing: W moves up and back, S moves down and forward. What happens if you remove the `else if` and allow both W and S to affect the position simultaneously? What if you swap the X and Z constraints?

    if (keyState['KeyW']) {
      playerPosition.y += climbSpeedY;
      playerPosition.z -= climbSpeedZ;
    } else if (keyState['KeyS']) {
      playerPosition.y -= climbSpeedY;
      playerPosition.z += climbSpeedZ;
    }

🔬 These four lines read WASD input and build the local movement vector. What happens if you remove the moveSpeed multiplier (just set moveZ = -1 and moveZ = 1) or if you swap 'moveLeft' and 'moveRight' so they're reversed?

    if (moveForward) moveZ -= moveSpeed;
    if (moveBackward) moveZ += moveSpeed;
    if (moveLeft) moveX -= moveSpeed;
    if (moveRight) moveX += moveSpeed;
function updatePlayerMovement(deltaTime) {
  if (!controls.isLocked || gameOver || winGame) return;

  const playerSphere = new THREE.Sphere(playerPosition, 0.5);

  let nearLadder = false;
  if (ladderClimbZone) {
    const climbZoneBox = new THREE.Box3().setFromObject(ladderClimbZone);
    if (playerSphere.intersectsBox(climbZoneBox) && playerPosition.y < WALL_HEIGHT + 1.7) {
      nearLadder = true;
    }
  }

  if (isClimbing) {
    playerPosition.x = house.position.x;
    playerVelocity.x = 0;
    playerVelocity.z = 0;
    playerVelocity.y = 0;

    const climbSpeedY = LADDER_CLIMB_SPEED * Math.sin(LADDER_TILT_ANGLE);
    const climbSpeedZ = LADDER_CLIMB_SPEED * Math.cos(LADDER_TILT_ANGLE);

    if (keyState['KeyW']) {
      playerPosition.y += climbSpeedY;
      playerPosition.z -= climbSpeedZ;
    } else if (keyState['KeyS']) {
      playerPosition.y -= climbSpeedY;
      playerPosition.z += climbSpeedZ;
    }

    const minClimbY = 1.7;
    const maxClimbY = WALL_HEIGHT + 0.85;
    playerPosition.y = constrain(playerPosition.y, minClimbY, maxClimbY);

    const backWallZ = house.position.z - HOUSE_DEPTH / 2;
    const ladderBottomZ = backWallZ - (WALL_HEIGHT / Math.tan(LADDER_TILT_ANGLE));
    const playerFootY = playerPosition.y - 0.85;
    playerPosition.z = map(playerFootY, 0, WALL_HEIGHT, ladderBottomZ, backWallZ);

    canJump = false;

    if (playerPosition.y >= maxClimbY - 0.1 || playerPosition.y <= minClimbY + 0.1) {
      isClimbing = false;
      playerVelocity.y = 0;
      messageDiv.html("Exited ladder.");
      setTimeout(() => messageDiv.html(''), 3000);
    }

  } else {
    playerVelocity.y -= gravity;

    let moveX = 0;
    let moveZ = 0;
    if (moveForward) moveZ -= moveSpeed;
    if (moveBackward) moveZ += moveSpeed;
    if (moveLeft) moveX -= moveSpeed;
    if (moveRight) moveX += moveSpeed;

    playerVelocity.x = moveX;
    playerVelocity.z = moveZ;
    playerVelocity.applyQuaternion(camera.quaternion);
    playerPosition.add(playerVelocity);

    if (playerPosition.y < 1.7) {
      playerPosition.y = 1.7;
      playerVelocity.y = 0;
      canJump = true;
    }
  }

  camera.position.copy(playerPosition);

  if (keyState['KeyE'] && !isClimbing && nearLadder) {
    isClimbing = true;
    keyState['KeyE'] = false;
    playerVelocity.x = 0;
    playerVelocity.z = 0;
    playerVelocity.y = 0;
    messageDiv.html("Climbing the tilted ladder! (W/S to climb, E to exit)");
    setTimeout(() => messageDiv.html(''), 3000);
  } else if (keyState['KeyE'] && isClimbing) {
    isClimbing = false;
    keyState['KeyE'] = false;
    playerVelocity.y = -0.1;
    messageDiv.html("Exited ladder.");
    setTimeout(() => messageDiv.html(''), 3000);
  }

  if (keyState['Digit6'] && keyState['Digit7'] && !cheatActive && millis() - lastCheatTime > CHEAT_COOLDOWN) {
    cheatActive = true;
    lastCheatTime = millis();
    for (let i = 0; i < CHEAT_GUARD_COUNT; i++) {
      const guardPosition = playerPosition.clone().add(new THREE.Vector3(random(-5, 5), 0, random(-5, 5)));
      guards.push(new Guard(guardPosition.x, guardPosition.y, guardPosition.z));
    }
    messageDiv.html(`Cheat Activated! ${CHEAT_GUARD_COUNT} free guards have joined your team! 💰`);
    setTimeout(() => messageDiv.html(''), 3000);
    keyState['Digit6'] = false;
    keyState['Digit7'] = false;
  }

  if (keyState['Digit6'] && keyState['Digit7'] && keyState['Digit8'] && keyState['Digit9'] && !godModeActive && millis() - lastGodModeCheatTime > GOD_MODE_CHEAT_COOLDOWN) {
    godModeActive = true;
    lastGodModeCheatTime = millis();
    messageDiv.html('God Mode Activated! Your defenders are invincible, and your supplies are endless! ✨');
    setTimeout(() => messageDiv.html(''), 3000);
    keyState['Digit6'] = false;
    keyState['Digit7'] = false;
    keyState['Digit8'] = false;
    keyState['Digit9'] = false;
  }
}
Line-by-line explanation (18 lines)

🔧 Subcomponents:

conditional Pointer Lock Check if (!controls.isLocked || gameOver || winGame) return;

Prevents movement if the pointer isn't locked to the canvas (mouse hasn't clicked to lock) or the game has ended

conditional Ladder Proximity Detection let nearLadder = false; if (ladderClimbZone) { const climbZoneBox = new THREE.Box3().setFromObject(ladderClimbZone); if (playerSphere.intersectsBox(climbZoneBox) && playerPosition.y < WALL_HEIGHT + 1.7) { nearLadder = true; } }

Checks if the player is near the invisible ladder climb zone so they can press E to start climbing

conditional Climbing System if (isClimbing) { playerPosition.x = house.position.x; // ... ladder movement ... } else { // ... normal movement ... }

If the player is climbing, movement is restricted to the ladder path; otherwise normal WASD movement applies

calculation Normal Ground Movement playerVelocity.y -= gravity; let moveX = 0; let moveZ = 0; if (moveForward) moveZ -= moveSpeed; if (moveBackward) moveZ += moveSpeed; if (moveLeft) moveX -= moveSpeed; if (moveRight) moveX += moveSpeed; playerVelocity.applyQuaternion(camera.quaternion); playerPosition.add(playerVelocity);

Applies gravity, reads WASD input, converts movement to world space using camera rotation, and updates player position

conditional Ground Collision if (playerPosition.y < 1.7) { playerPosition.y = 1.7; playerVelocity.y = 0; canJump = true; }

Prevents the player from falling through the ground and allows jumping when feet are on the ground

conditional Ladder Interaction (E Key) if (keyState['KeyE'] && !isClimbing && nearLadder) { isClimbing = true; // ... start climbing ... } else if (keyState['KeyE'] && isClimbing) { isClimbing = false; // ... stop climbing ... }

Pressing E near the ladder starts climbing; pressing E while climbing exits

conditional Guard Cheat Code (6+7) if (keyState['Digit6'] && keyState['Digit7'] && !cheatActive && millis() - lastCheatTime > CHEAT_COOLDOWN) { cheatActive = true; // ... spawn 10 guards ... }

Pressing 6 and 7 simultaneously spawns 10 free guards around the player (cooldown prevents spam)

conditional God Mode Cheat Code (6+7+8+9) if (keyState['Digit6'] && keyState['Digit7'] && keyState['Digit8'] && keyState['Digit9'] && !godModeActive && millis() - lastGodModeCheatTime > GOD_MODE_CHEAT_COOLDOWN) { godModeActive = true; // ... }

Pressing 6, 7, 8, and 9 simultaneously activates God Mode: infinite ammo, guards are invincible, hunger doesn't decrease

if (!controls.isLocked || gameOver || winGame) return;
Early exit: if pointer isn't locked, or game has ended, stop updating movement
const playerSphere = new THREE.Sphere(playerPosition, 0.5);
Creates an invisible sphere around the player with radius 0.5 units to use for collision detection with the ladder zone
const climbZoneBox = new THREE.Box3().setFromObject(ladderClimbZone);
Converts the ladder climb zone mesh into a bounding box for intersection testing
if (playerSphere.intersectsBox(climbZoneBox) && playerPosition.y < WALL_HEIGHT + 1.7) {
Checks if the player sphere touches the ladder zone AND the player is below the roof peak—both conditions must be true to set nearLadder to true
if (isClimbing) {
Enters climbing mode, which overrides all other movement logic
playerPosition.x = house.position.x;
Locks the player's X position to the house's X position so they stay centered on the ladder
const climbSpeedY = LADDER_CLIMB_SPEED * Math.sin(LADDER_TILT_ANGLE);
Calculates how much Y (vertical) to move when climbing by decomposing the climb speed along the ladder's tilt angle
const climbSpeedZ = LADDER_CLIMB_SPEED * Math.cos(LADDER_TILT_ANGLE);
Calculates how much Z (forward) to move when climbing—the ladder is tilted, so climbing up also moves backward
if (keyState['KeyW']) { playerPosition.y += climbSpeedY; playerPosition.z -= climbSpeedZ; }
When W is pressed while climbing, move up (Y+) and backward (Z-) along the ladder slope
playerPosition.y = constrain(playerPosition.y, minClimbY, maxClimbY);
Clamps the player's Y position between ground level and roof level so they cannot climb infinitely up or underground
playerPosition.z = map(playerFootY, 0, WALL_HEIGHT, ladderBottomZ, backWallZ);
Maps the player's foot height (0 to WALL_HEIGHT) to the ladder's Z range so as they climb higher, they move backward automatically
playerVelocity.y -= gravity;
Applies downward acceleration to simulate gravity pulling the player down (when not climbing)
if (moveForward) moveZ -= moveSpeed;
If W is pressed, decrease moveZ (move forward in local space, which will be rotated to world space later)
playerVelocity.applyQuaternion(camera.quaternion);
Rotates the local movement vector by the camera's rotation quaternion so movement is relative to where the player is looking
playerPosition.add(playerVelocity);
Adds the velocity to the player position to move them
if (playerPosition.y < 1.7) { playerPosition.y = 1.7; playerVelocity.y = 0; canJump = true; }
Prevents falling through the ground by clamping Y to 1.7 (player height), resets downward velocity, and allows jumping
camera.position.copy(playerPosition);
Updates the camera position to match the player position so the player sees from their eyes
if (keyState['KeyE'] && !isClimbing && nearLadder) { isClimbing = true;
If E is pressed, not currently climbing, and near a ladder, set isClimbing to true to activate climbing mode

updateDayNightCycle()

updateDayNightCycle() implements the game's time engine. The modulo operator (%) is key—it wraps elapsed time into a repeating cycle. This function shows how to drive gameplay state changes (zombie spawning, lighting, UI messages) off of time alone. The sin/cos animation of the light position is a classic technique for smooth, looping motion.

🔬 This block moves the sun across the sky using sine and cosine. What happens if you swap the sin and cos calls? Or multiply dayProgress * Math.PI by 2 instead of 1 so the sun oscillates faster?

    const dayProgress = currentCycleTime / dayLength;
    directionalLight.position.x = Math.sin(dayProgress * Math.PI) * 10;
    directionalLight.position.y = 10;
    directionalLight.position.z = -5 + Math.cos(dayProgress * Math.PI) * 10;
function updateDayNightCycle() {
  const currentCycleTime = millis() % cycleLength;

  if (currentCycleTime < dayLength) {
    // Day
    if (isNight) {
      isNight = false;
      nightsSurvived++;
      messageDiv.html(`Night ${nightsSurvived} Survived!`);
      zombies.forEach(z => { scene.remove(z.mesh); });
      zombies = [];
      gunEquipped = false;
      gunMesh.visible = false;
      casings.forEach(c => { scene.remove(c.mesh); });
      casings = [];
      palmTrees.forEach(tree => {
        tree.hasFruit = true;
        tree.harvestCooldown = 0;
        if (tree.fruitMesh) tree.fruitMesh.visible = true;
      });

      if (nightsSurvived >= MAX_NIGHTS) {
        winGame = true;
        messageDiv.html(`You survived ${MAX_NIGHTS} nights! YOU WIN! 🎉`);
        gameOver = true;
      } else {
        setTimeout(() => messageDiv.html(''), 3000);
      }
    }

    const dayProgress = currentCycleTime / dayLength;
    directionalLight.position.x = Math.sin(dayProgress * Math.PI) * 10;
    directionalLight.position.y = 10;
    directionalLight.position.z = -5 + Math.cos(dayProgress * Math.PI) * 10;
    directionalLight.color.setHex(0xffffff);
    directionalLight.intensity = 1;

    hemisphereLight.color.setHex(0xadd8e6);
    hemisphereLight.intensity = 0.5;
    renderer.setClearColor(0x87ceeb);
  } else {
    // Night
    if (!isNight) {
      isNight = true;
      nightfallTime = millis();
      messageDiv.html('Nightfall! Zombies are coming! 🧟');
      gunEquipped = true;
      gunMesh.visible = true;
      playerAmmo = 10;
      setTimeout(() => messageDiv.html(''), 3000);
    }

    const nightProgress = (currentCycleTime - dayLength) / nightLength;
    directionalLight.position.x = Math.sin(nightProgress * Math.PI) * 10;
    directionalLight.position.y = 10;
    directionalLight.position.z = 5 - Math.cos(nightProgress * Math.PI) * 10;
    directionalLight.color.setHex(0xaaaaee);
    directionalLight.intensity = 0.3;

    hemisphereLight.color.setHex(0x000033);
    hemisphereLight.intensity = 0.2;
    renderer.setClearColor(0x000000);

    if (millis() - lastZombieSpawnTime > zombieSpawnInterval) {
      spawnZombie();
      lastZombieSpawnTime = millis();
    }
  }
}
Line-by-line explanation (18 lines)

🔧 Subcomponents:

calculation Cycle Time Calculation const currentCycleTime = millis() % cycleLength;

Uses modulo to loop the elapsed time through a complete day-night cycle, so after nightLength + dayLength, it wraps back to 0

conditional Day Logic if (currentCycleTime < dayLength) { ... }

If the current cycle time is in the day period, execute day behavior: light sun, clear zombies, reset fruit

conditional Night Transition Check if (isNight) { isNight = false; // ... cleanup ... }

When switching from night to day, clean up zombies and casings, reset all fruit, and increment nightsSurvived

calculation Day Lighting const dayProgress = currentCycleTime / dayLength; directionalLight.position.x = Math.sin(dayProgress * Math.PI) * 10; // ...

Animates the sun position from east to west across the sky over the course of the day

conditional Night Logic } else { ... }

If not in day phase, execute night behavior: dim lights, show gun, spawn zombies

conditional Night Transition Check if (!isNight) { isNight = true; // ... setup ... }

When switching from day to night, equip the gun, reset ammo, and show the nightfall message

conditional Zombie Spawning if (millis() - lastZombieSpawnTime > zombieSpawnInterval) { spawnZombie(); lastZombieSpawnTime = millis(); }

Spawns one zombie every zombieSpawnInterval milliseconds during night

const currentCycleTime = millis() % cycleLength;
Calculates the current position in the repeating day-night cycle by using modulo (%) to wrap time back to the start after each complete cycle
if (currentCycleTime < dayLength) {
Checks if the current cycle time is less than the day length (60 seconds), meaning it's daytime
if (isNight) {
Checks if the game was in night mode previously—if so, it's the transition from night to day
isNight = false;
Marks the transition from night to day
nightsSurvived++;
Increments the counter for completed nights (increases by 1 each dawn)
zombies.forEach(z => { scene.remove(z.mesh); });
Removes all remaining zombie meshes from the scene and clears the zombies array
zombies = [];
Clears the zombies array completely so no zombies remain in memory
const dayProgress = currentCycleTime / dayLength;
Calculates a normalized value (0 to 1) representing how far through the day we are
directionalLight.position.x = Math.sin(dayProgress * Math.PI) * 10;
Uses sine wave to move the sun left-right across the sky (east to west): at progress 0, sin = 0 (center); at 0.5, sin = 1 (peak right); at 1, sin = 0 (center again)
directionalLight.position.z = -5 + Math.cos(dayProgress * Math.PI) * 10;
Uses cosine to move the sun forward-back (representing altitude): at start and end it's low (-5), in the middle it's high (+5)
directionalLight.color.setHex(0xffffff);
Sets the sun color to pure white (#ffffff) during the day
renderer.setClearColor(0x87ceeb);
Sets the background color to sky blue so the canvas has a day-like appearance
} else {
Enters night mode because currentCycleTime is >= dayLength
if (!isNight) {
Checks if the game was in day mode previously—if so, it's the transition from day to night
isNight = true;
Marks the transition from day to night
gunEquipped = true; gunMesh.visible = true;
Equips the gun and makes it visible in the player's hand
playerAmmo = 10;
Resets ammo to 10 bullets at the start of each night
if (millis() - lastZombieSpawnTime > zombieSpawnInterval) {
Checks if enough time has passed since the last zombie spawn—if so, spawn a new one

Zombie class

The Zombie class demonstrates object-oriented enemy AI. Each zombie tracks its own health, target, and attack cooldown. The key insight is that zombies are intelligent—they choose targets based on distance and switch between attacking the player and guards dynamically. The .normalize() operation is crucial for movement: it converts a difference vector (which has varying magnitude) into a pure direction with magnitude 1, so multiplying by speed gives consistent movement.

🔬 This block finds the nearest target. What happens if you remove the guards loop so zombies always target the player regardless of guards? Or reverse the if condition (distanceToGuard > minDistance) to make zombies prefer guards that are farther away?

    let targetPosition = playerPosition.clone();
    let targetMesh = null;
    let minDistance = this.mesh.position.distanceTo(playerPosition);

    if (guards.length > 0) {
      for (const guard of guards) {
        const distanceToGuard = this.mesh.position.distanceTo(guard.mesh.position);
        if (distanceToGuard < minDistance) {
          minDistance = distanceToGuard;
          targetPosition = guard.mesh.position.clone();
          targetMesh = guard;
        }
      }
    }
class Zombie {
  constructor(x, y, z, speed, health, damage, color) {
    const bodyGeometry = new THREE.BoxGeometry(0.8, 1.5, 0.5);
    const headGeometry = new THREE.BoxGeometry(0.6, 0.6, 0.6);
    const bodyMaterial = new THREE.MeshStandardMaterial({ color: color });
    const headMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });

    this.body = new THREE.Mesh(bodyGeometry, bodyMaterial);
    this.head = new THREE.Mesh(headGeometry, headMaterial);
    this.head.position.y = 1;

    this.mesh = new THREE.Group();
    this.mesh.add(this.body);
    this.mesh.add(this.head);
    this.mesh.position.set(x, y + 0.75, z);
    this.mesh.castShadow = true;
    this.mesh.receiveShadow = true;
    scene.add(this.mesh);

    this.health = health;
    this.attackTimer = 0;
    this.speed = speed;
    this.damage = damage;
  }

  update(deltaTime) {
    if (!gameActive || gameOver || winGame) return;

    let targetPosition = playerPosition.clone();
    let targetMesh = null;
    let minDistance = this.mesh.position.distanceTo(playerPosition);

    if (guards.length > 0) {
      for (const guard of guards) {
        const distanceToGuard = this.mesh.position.distanceTo(guard.mesh.position);
        if (distanceToGuard < minDistance) {
          minDistance = distanceToGuard;
          targetPosition = guard.mesh.position.clone();
          targetMesh = guard;
        }
      }
    }

    const direction = targetPosition.clone().sub(this.mesh.position).normalize();
    this.mesh.position.addScaledVector(direction, this.speed);
    this.mesh.lookAt(targetPosition.x, this.mesh.position.y, targetPosition.z);

    if (minDistance < 1.5) {
      if (millis() - this.attackTimer > ZOMBIE_ATTACK_COOLDOWN) {
        if (targetMesh) {
          targetMesh.takeDamage(this.damage);
        } else {
          playerHealth -= this.damage;
          if (playerHealth <= 0) {
            playerHealth = 0;
            gameOver = true;
            messageDiv.html('GAME OVER! You were overwhelmed by zombies. 💀');
            noLoop();
          }
        }
        this.attackTimer = millis();
      }
    }
  }

  takeDamage(damage) {
    this.health -= damage;
    if (this.health <= 0) {
      scene.remove(this.mesh);
      zombies = zombies.filter(z => z !== this);
    }
  }
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

initialization Constructor constructor(x, y, z, speed, health, damage, color) { ... }

Creates a zombie with a body and head mesh, positions it in the world, and initializes health, speed, and damage stats

calculation Target Selection let targetPosition = playerPosition.clone(); let targetMesh = null; let minDistance = this.mesh.position.distanceTo(playerPosition); if (guards.length > 0) { for (const guard of guards) { const distanceToGuard = this.mesh.position.distanceTo(guard.mesh.position); if (distanceToGuard < minDistance) { minDistance = distanceToGuard; targetPosition = guard.mesh.position.clone(); targetMesh = guard; } } }

Finds the nearest target (player or guard) and sets targetPosition and targetMesh accordingly

calculation Movement and Facing const direction = targetPosition.clone().sub(this.mesh.position).normalize(); this.mesh.position.addScaledVector(direction, this.speed); this.mesh.lookAt(targetPosition.x, this.mesh.position.y, targetPosition.z);

Calculates direction toward the target, moves the zombie by that direction scaled by speed, and rotates the zombie to face the target

conditional Attack Logic if (minDistance < 1.5) { if (millis() - this.attackTimer > ZOMBIE_ATTACK_COOLDOWN) { if (targetMesh) { targetMesh.takeDamage(this.damage); } else { playerHealth -= this.damage; // ... } this.attackTimer = millis(); } }

If the zombie is close enough to the target, and enough time has passed since the last attack, deal damage to the target

constructor(x, y, z, speed, health, damage, color) {
Defines the constructor function that takes position (x, y, z), movement speed, health points, damage per hit, and color as parameters
const bodyGeometry = new THREE.BoxGeometry(0.8, 1.5, 0.5);
Creates the shape for the zombie's body: a box 0.8 units wide, 1.5 units tall, 0.5 units deep
const bodyMaterial = new THREE.MeshStandardMaterial({ color: color });
Creates a material using the passed-in color—this is how different zombie types (green, red, gray) get their colors
this.mesh = new THREE.Group();
Creates a group (container) to hold both the body and head meshes as a single unit
this.mesh.position.set(x, y + 0.75, z);
Positions the zombie group at the specified world coordinates (the +0.75 offset moves the zombie up slightly)
this.health = health;
Stores the health parameter in the zombie object so it can be reduced when hit by bullets
let targetPosition = playerPosition.clone();
Initializes the target to the player's position; .clone() creates a copy so modifying targetPosition doesn't affect playerPosition
const distanceToGuard = this.mesh.position.distanceTo(guard.mesh.position);
Calculates the straight-line distance from the zombie to a guard using Three.js's built-in distance function
if (distanceToGuard < minDistance) {
Checks if this guard is closer than the current best target; if so, update the target to be this guard
const direction = targetPosition.clone().sub(this.mesh.position).normalize();
Calculates the direction vector from the zombie to the target, then normalizes it to length 1 so direction has only angle, not magnitude
this.mesh.position.addScaledVector(direction, this.speed);
Moves the zombie in the direction toward the target, moving a distance equal to this.speed each frame
this.mesh.lookAt(targetPosition.x, this.mesh.position.y, targetPosition.z);
Rotates the zombie mesh so it faces the target; keeps Y the same so the zombie doesn't tilt up or down
if (minDistance < 1.5) {
Checks if the zombie is within 1.5 units of its target (close enough to attack)
if (millis() - this.attackTimer > ZOMBIE_ATTACK_COOLDOWN) {
Checks if enough time (1000ms) has passed since the zombie's last attack; prevents attacking every single frame
targetMesh.takeDamage(this.damage);
If the target is a guard, calls the guard's takeDamage method to reduce its health
playerHealth -= this.damage;
If the target is the player, directly reduces the player's health by the zombie's damage value

onMouseDown()

onMouseDown() shows how to convert mouse input into in-world effects. Key techniques: getWorldDirection() extracts the camera's facing direction as a 3D vector, getWorldPosition() gets an object's position in world space (important because the gun is attached to the camera), and applyQuaternion() rotates a vector by a rotation. The casing ejection demonstrates that even small visual details (random direction, upward bias) create immersion.

🔬 This block creates random ejection direction. What happens if you increase the ranges (e.g., random(0.5, 1) for X) so casings eject farther away? Or remove the applyQuaternion line so casings eject in the same direction no matter where the player is facing?

    const ejectDirection = new THREE.Vector3(
      random(0.05, 0.1),
      random(0.2, 0.3),
      random(-0.05, 0.05)
    );
    ejectDirection.applyQuaternion(camera.quaternion);
function onMouseDown(event) {
  if (!gameActive || gameOver || winGame || !controls.isLocked) return;

  if (isNight && gunEquipped && (playerAmmo > 0 || godModeActive)) {
    if (!godModeActive) {
      playerAmmo--;
    }

    const direction = new THREE.Vector3();
    camera.getWorldDirection(direction);

    const gunWorldPosition = new THREE.Vector3();
    gunMesh.getWorldPosition(gunWorldPosition);

    bullets.push(new Bullet(gunWorldPosition, direction));

    const ejectDirection = new THREE.Vector3(
      random(0.05, 0.1),
      random(0.2, 0.3),
      random(-0.05, 0.05)
    );
    ejectDirection.applyQuaternion(camera.quaternion);

    casings.push(new Casing(gunWorldPosition, ejectDirection));

  } else if (isNight && gunEquipped && playerAmmo <= 0 && !godModeActive) {
    messageDiv.html('Out of ammo! Press R to reload.');
    setTimeout(() => messageDiv.html(''), 2000);
  }
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

conditional Preconditions Check if (!gameActive || gameOver || winGame || !controls.isLocked) return;

Returns early if the game isn't active, has ended, or the mouse isn't locked

conditional Can Shoot Check if (isNight && gunEquipped && (playerAmmo > 0 || godModeActive)) {

Checks if it's night, gun is equipped, and ammo is available (or god mode is on)

conditional Ammo Deduction if (!godModeActive) { playerAmmo--; }

Decreases ammo by 1 unless god mode is active

calculation Bullet Creation const direction = new THREE.Vector3(); camera.getWorldDirection(direction); const gunWorldPosition = new THREE.Vector3(); gunMesh.getWorldPosition(gunWorldPosition); bullets.push(new Bullet(gunWorldPosition, direction));

Gets the camera's look direction and the gun's world position, then creates a new bullet

calculation Casing Ejection const ejectDirection = new THREE.Vector3( random(0.05, 0.1), random(0.2, 0.3), random(-0.05, 0.05) ); ejectDirection.applyQuaternion(camera.quaternion); casings.push(new Casing(gunWorldPosition, ejectDirection));

Creates an eject direction with random variation, rotates it by the camera's orientation, and spawns a casing

conditional Out of Ammo Message } else if (isNight && gunEquipped && playerAmmo <= 0 && !godModeActive) { messageDiv.html('Out of ammo! Press R to reload.'); setTimeout(() => messageDiv.html(''), 2000); }

Shows a message if the player tries to shoot without ammo and god mode is off

if (!gameActive || gameOver || winGame || !controls.isLocked) return;
Early exit: shooting is disabled if the game isn't running, the player has won/lost, or the pointer isn't locked to the canvas
if (isNight && gunEquipped && (playerAmmo > 0 || godModeActive)) {
Checks three conditions: it must be night, the gun must be equipped, AND either the player has ammo OR god mode is active
if (!godModeActive) { playerAmmo--; }
If god mode is NOT active, decrease ammo; god mode grants infinite ammo
const direction = new THREE.Vector3();
Creates a new empty 3D vector to store the camera's forward direction
camera.getWorldDirection(direction);
Fills the direction vector with the unit vector pointing where the camera is looking (where the player is aiming)
const gunWorldPosition = new THREE.Vector3(); gunMesh.getWorldPosition(gunWorldPosition);
Gets the world position of the gun mesh (which is attached to the camera) so bullets spawn from the gun's tip, not the camera center
bullets.push(new Bullet(gunWorldPosition, direction));
Creates a new Bullet object at the gun position traveling in the camera's direction, and adds it to the bullets array
const ejectDirection = new THREE.Vector3( random(0.05, 0.1), random(0.2, 0.3), random(-0.05, 0.05) );
Creates a random eject direction with slight upward bias (+Y 0.2-0.3) and small left-right/forward variation to simulate natural ejection
ejectDirection.applyQuaternion(camera.quaternion);
Rotates the eject direction by the camera's quaternion (rotation) so casings eject in a consistent direction relative to where the player is facing
casings.push(new Casing(gunWorldPosition, ejectDirection));
Creates a new casing object with an initial velocity and adds it to the casings array so it bounces on the ground
} else if (isNight && gunEquipped && playerAmmo <= 0 && !godModeActive) {
Alternative condition: if all these are true, the player clicked but has no ammo
messageDiv.html('Out of ammo! Press R to reload.');
Displays a message to the player telling them to reload

spawnZombie()

spawnZombie() demonstrates two important game design patterns: safe spawn zones (using a do-while loop to ensure spawns don't happen on the player) and weighted random selection (using thresholds on a random value to pick one of several options with different probabilities). This is how procedural content generation works in games—each spawn is independent and random, but bounded by rules.

function spawnZombie() {
  let x, z;
  do {
    x = random(-15, 15);
    z = random(-15, 15);
  } while (Math.abs(x) < 5 && Math.abs(z) < 5);

  let zombieType = random();
  let speed, health, damage, color;

  if (zombieType < 0.6) {
    speed = REGULAR_ZOMBIE_SPEED;
    health = REGULAR_ZOMBIE_HEALTH;
    damage = REGULAR_ZOMBIE_DAMAGE;
    color = REGULAR_ZOMBIE_COLOR;
  } else if (zombieType < 0.85) {
    speed = FAST_ZOMBIE_SPEED;
    health = FAST_ZOMBIE_HEALTH;
    damage = FAST_ZOMBIE_DAMAGE;
    color = FAST_ZOMBIE_COLOR;
  } else {
    speed = TANK_ZOMBIE_SPEED;
    health = TANK_ZOMBIE_HEALTH;
    damage = TANK_ZOMBIE_DAMAGE;
    color = TANK_ZOMBIE_COLOR;
  }

  zombies.push(new Zombie(x, 0, z, speed, health, damage, color));
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

while-loop Safe Spawn Location let x, z; do { x = random(-15, 15); z = random(-15, 15); } while (Math.abs(x) < 5 && Math.abs(z) < 5);

Generates random spawn coordinates until finding a location outside the safe zone around the house (so zombies don't spawn on top of the player)

switch-case Zombie Type Selection let zombieType = random(); let speed, health, damage, color; if (zombieType < 0.6) { ... } else if (zombieType < 0.85) { ... } else { ... }

Randomly chooses one of three zombie types with weighted probabilities and assigns corresponding stats

initialization Zombie Creation zombies.push(new Zombie(x, 0, z, speed, health, damage, color));

Creates a new zombie with the selected type's stats and adds it to the game

let x, z;
Declares variables to store the spawn X and Z coordinates (Y will always be 0 for ground level)
do {
Starts a do-while loop that always runs at least once
x = random(-15, 15);
Picks a random X coordinate between -15 and 15
z = random(-15, 15);
Picks a random Z coordinate between -15 and 15
} while (Math.abs(x) < 5 && Math.abs(z) < 5);
Repeats if the absolute values of both x and z are less than 5, meaning the spawn point is too close to the house center; keeps looping until a safe spot is found
let zombieType = random();
Generates a random number between 0 and 1 that will be used to choose the zombie type
if (zombieType < 0.6) {
If the random number is less than 0.6 (60% chance), the zombie is Regular type
} else if (zombieType < 0.85) {
If it's between 0.6 and 0.85 (25% chance), the zombie is Fast type
} else {
Otherwise (15% chance), the zombie is Tank type
zombies.push(new Zombie(x, 0, z, speed, health, damage, color));
Creates a new Zombie object at the spawn location with the selected stats and adds it to the zombies array

checkCollisions()

checkCollisions() is the collision detection engine. It uses two different techniques: distance-based detection for bullets (simple and fast), and box-sphere intersection for player-wall collisions (more accurate but slower). The backwards loop is important—when you remove an element from an array while iterating forwards, you skip the next element, so reverse iteration prevents this bug. The clampPoint and normalize pattern is how you separate overlapping objects: find the closest point on the obstacle, then push outward along the normalized direction.

🔬 Notice we loop backwards (i-- and j--) instead of forwards. What happens if you change to i++ and j++? Or remove the 'break' so one bullet can hit multiple zombies?

  for (let i = bullets.length - 1; i >= 0; i--) {
    const bullet = bullets[i];
    for (let j = zombies.length - 1; j >= 0; j--) {
      const zombie = zombies[j];
      const distance = bullet.mesh.position.distanceTo(zombie.mesh.position);
      if (distance < 1) {
        zombie.takeDamage(BULLET_DAMAGE);
        scene.remove(bullet.mesh);
        bullets.splice(i, 1);
        break;
      }
    }
  }
function checkCollisions() {
  if (!gameActive || gameOver || winGame) return;

  // Bullet-Zombie collisions
  for (let i = bullets.length - 1; i >= 0; i--) {
    const bullet = bullets[i];
    for (let j = zombies.length - 1; j >= 0; j--) {
      const zombie = zombies[j];
      const distance = bullet.mesh.position.distanceTo(zombie.mesh.position);
      if (distance < 1) {
        zombie.takeDamage(BULLET_DAMAGE);
        scene.remove(bullet.mesh);
        bullets.splice(i, 1);
        break;
      }
    }
  }

  // Player-House collision (using individual wall collision objects)
  const playerSphere = new THREE.Sphere(playerPosition, 0.5);

  for (const collisionObject of houseCollisionObjects) {
    if (collisionObject.box.intersectsSphere(playerSphere)) {
      const closestPoint = collisionObject.box.clampPoint(playerPosition, new THREE.Vector3());
      const direction = playerPosition.clone().sub(closestPoint).normalize();
      playerPosition.copy(closestPoint.addScaledVector(direction, 0.5));
    }
  }
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

for-loop Bullet-Zombie Collision Loop for (let i = bullets.length - 1; i >= 0; i--) { const bullet = bullets[i]; for (let j = zombies.length - 1; j >= 0; j--) { const zombie = zombies[j]; const distance = bullet.mesh.position.distanceTo(zombie.mesh.position); if (distance < 1) { zombie.takeDamage(BULLET_DAMAGE); scene.remove(bullet.mesh); bullets.splice(i, 1); break; } } }

Checks every bullet against every zombie; if they're within 1 unit, deals damage and removes the bullet

for-loop Player-Wall Collision Loop const playerSphere = new THREE.Sphere(playerPosition, 0.5); for (const collisionObject of houseCollisionObjects) { if (collisionObject.box.intersectsSphere(playerSphere)) { const closestPoint = collisionObject.box.clampPoint(playerPosition, new THREE.Vector3()); const direction = playerPosition.clone().sub(closestPoint).normalize(); playerPosition.copy(closestPoint.addScaledVector(direction, 0.5)); } }

Checks if the player sphere overlaps any wall; if so, pushes the player out of the wall

if (!gameActive || gameOver || winGame) return;
Early exit: collision checking is skipped if the game isn't active
for (let i = bullets.length - 1; i >= 0; i--) {
Loops through bullets in reverse (from last to first) so removing a bullet doesn't skip the next one
for (let j = zombies.length - 1; j >= 0; j--) {
Nested loop: for each bullet, checks all zombies also in reverse order
const distance = bullet.mesh.position.distanceTo(zombie.mesh.position);
Calculates the straight-line distance between the bullet and zombie
if (distance < 1) {
If they're within 1 unit (close enough to be a hit), proceed with collision response
zombie.takeDamage(BULLET_DAMAGE);
Calls the zombie's takeDamage method to reduce its health
scene.remove(bullet.mesh);
Removes the bullet's visual mesh from the Three.js scene
bullets.splice(i, 1);
Removes the bullet from the bullets array at index i
break;
Exits the inner zombie loop so this bullet doesn't hit multiple zombies in one frame
const playerSphere = new THREE.Sphere(playerPosition, 0.5);
Creates a sphere centered on the player with radius 0.5 units to represent the player's collision shape
for (const collisionObject of houseCollisionObjects) {
Loops through all collision objects (walls, barriers, furniture) in the scene
if (collisionObject.box.intersectsSphere(playerSphere)) {
Tests if the wall's bounding box overlaps the player sphere—if so, there's a collision
const closestPoint = collisionObject.box.clampPoint(playerPosition, new THREE.Vector3());
Finds the closest point on the wall to the player and clamps it to the wall's box
const direction = playerPosition.clone().sub(closestPoint).normalize();
Calculates the direction from the wall's closest point toward the player
playerPosition.copy(closestPoint.addScaledVector(direction, 0.5));
Moves the player to 0.5 units away from the wall along the direction away from it (pushing them out)

updateHunger()

updateHunger() introduces a secondary resource management mechanic beyond health. By using deltaTime, hunger loss is frame-rate independent—whether the game runs at 30 FPS or 60 FPS, the player loses hunger at the same real-world rate. The starvation damage creates urgency: hunger is a ticking clock that forces the player to explore and harvest fruit, adding strategic depth to survival.

function updateHunger(deltaTime) {
  if (!gameActive || gameOver || winGame || godModeActive) return;

  playerHunger -= HUNGER_DECREASE_RATE * deltaTime;
  playerHunger = constrain(playerHunger, 0, 100);

  if (playerHunger <= 0) {
    playerHealth -= HUNGER_DAMAGE_RATE * deltaTime;
    messageDiv.html('You are starving! Find fruit quickly! 🍎');
    if (playerHealth <= 0) {
      playerHealth = 0;
      gameOver = true;
      messageDiv.html('GAME OVER! You starved to death. 💀');
      noLoop();
    }
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Game State Check if (!gameActive || gameOver || winGame || godModeActive) return;

Skips hunger updates if game isn't running, has ended, or god mode is active

calculation Hunger Decrease playerHunger -= HUNGER_DECREASE_RATE * deltaTime; playerHunger = constrain(playerHunger, 0, 100);

Reduces hunger by a rate per second (frame-rate independent) and clamps it between 0 and 100

conditional Starvation Damage if (playerHunger <= 0) { playerHealth -= HUNGER_DAMAGE_RATE * deltaTime; messageDiv.html('You are starving! Find fruit quickly! 🍎'); if (playerHealth <= 0) { playerHealth = 0; gameOver = true; messageDiv.html('GAME OVER! You starved to death. 💀'); noLoop(); } }

If hunger is zero, applies damage to health every frame; if health reaches zero, ends the game

if (!gameActive || gameOver || winGame || godModeActive) return;
Early exit: hunger doesn't decrease in god mode or if the game is inactive
playerHunger -= HUNGER_DECREASE_RATE * deltaTime;
Decreases hunger by the rate (0.05) multiplied by deltaTime in seconds, making hunger loss independent of frame rate
playerHunger = constrain(playerHunger, 0, 100);
Clamps hunger to be between 0 and 100 so it never goes negative or above 100
if (playerHunger <= 0) {
Checks if the player is starving (hunger at or below zero)
playerHealth -= HUNGER_DAMAGE_RATE * deltaTime;
Applies starvation damage (2 health per second) to the player
messageDiv.html('You are starving! Find fruit quickly! 🍎');
Displays a warning message to the player
if (playerHealth <= 0) {
Checks if the starvation damage killed the player
gameOver = true;
Marks the game as over
messageDiv.html('GAME OVER! You starved to death. 💀');
Displays the game over message with the starvation cause

createHouse()

createHouse() demonstrates 3D modeling and collision infrastructure. The use of Groups organizes complex hierarchies. The ladder is particularly interesting—its positioning requires trigonometry to ensure it reaches the right height and angle. The invisible climb zone shows a common game development pattern: invisible collision geometry separate from visual geometry, allowing precise interaction detection without visible artifacts.

function createHouse() {
  const houseGroup = new THREE.Group();
  const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xdeb887 });
  const interiorMaterial = new THREE.MeshStandardMaterial({ color: 0xfffafa });
  const furnitureMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });

  const wallThickness = 0.1;
  const houseWidth = HOUSE_WIDTH;
  const houseDepth = HOUSE_DEPTH;

  // Back Wall
  const backWall = new THREE.Mesh(
    new THREE.BoxGeometry(houseWidth, WALL_HEIGHT, wallThickness),
    bodyMaterial
  );
  backWall.position.set(0, WALL_HEIGHT / 2, -houseDepth / 2);
  backWall.castShadow = true;
  backWall.receiveShadow = true;
  houseGroup.add(backWall);
  houseCollisionObjects.push({ mesh: backWall, box: new THREE.Box3().setFromObject(backWall) });

  // Left Wall, Right Wall, Front Walls, Interior Walls, Furniture, Roof, Door (similarly structured)
  // ... (walls, ceiling, roof, furniture, ladder)

  // Tilted Ladder Creation
  const ladderMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });

  const ladderVerticalHeight = WALL_HEIGHT;
  const ladderSlopeLength = ladderVerticalHeight / Math.sin(LADDER_TILT_ANGLE);
  const ladderHorizontalDepth = ladderVerticalHeight / Math.tan(LADDER_TILT_ANGLE);

  tiltedLadder = new THREE.Mesh(
    new THREE.BoxGeometry(1, ladderSlopeLength, 0.2),
    ladderMaterial
  );
  tiltedLadder.rotation.x = LADDER_TILT_ANGLE;
  tiltedLadder.position.set(
    0,
    ladderSlopeLength / 2 * Math.sin(LADDER_TILT_ANGLE),
    -houseDepth / 2 - ladderHorizontalDepth / 2
  );
  tiltedLadder.castShadow = true;
  tiltedLadder.receiveShadow = true;
  houseGroup.add(tiltedLadder);
  houseCollisionObjects.push({ mesh: tiltedLadder, box: new THREE.Box3().setFromObject(tiltedLadder) });

  ladderClimbZone = new THREE.Mesh(
    new THREE.BoxGeometry(1.2, ladderSlopeLength + 0.5, ladderHorizontalDepth + 0.5),
    new THREE.MeshBasicMaterial({ visible: false })
  );
  ladderClimbZone.rotation.x = LADDER_TILT_ANGLE;
  ladderClimbZone.position.set(
    0,
    ladderSlopeLength / 2 * Math.sin(LADDER_TILT_ANGLE),
    -houseDepth / 2 - ladderHorizontalDepth / 2
  );
  houseGroup.add(ladderClimbZone);

  house = houseGroup;
  scene.add(house);

  houseInteriorLight.position.set(0, 3, 0);
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

initialization House Group const houseGroup = new THREE.Group();

Creates a parent group to hold all house meshes so they can be moved/rotated together

initialization Material Setup const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xdeb887 }); const interiorMaterial = new THREE.MeshStandardMaterial({ color: 0xfffafa }); const furnitureMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });

Creates three materials for outer walls (tan), interior walls (off-white), and furniture (brown)

initialization Wall Creation // Back, Left, Right, and Front walls with door opening

Creates four exterior walls with collision boxes; front wall has a door-sized opening

calculation Ladder Geometry Calculation const ladderVerticalHeight = WALL_HEIGHT; const ladderSlopeLength = ladderVerticalHeight / Math.sin(LADDER_TILT_ANGLE); const ladderHorizontalDepth = ladderVerticalHeight / Math.tan(LADDER_TILT_ANGLE);

Calculates the ladder's dimensions based on wall height and tilt angle using trigonometry

calculation Ladder Positioning tiltedLadder.rotation.x = LADDER_TILT_ANGLE; tiltedLadder.position.set( 0, ladderSlopeLength / 2 * Math.sin(LADDER_TILT_ANGLE), -houseDepth / 2 - ladderHorizontalDepth / 2 );

Positions and tilts the ladder mesh against the back wall of the house

initialization Climb Zone Creation ladderClimbZone = new THREE.Mesh( new THREE.BoxGeometry(1.2, ladderSlopeLength + 0.5, ladderHorizontalDepth + 0.5), new THREE.MeshBasicMaterial({ visible: false }) );

Creates an invisible box around the ladder for E-key interaction detection

const houseGroup = new THREE.Group();
Creates an empty group that will contain all house meshes, allowing them to be treated as a single unit
const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xdeb887 });
Creates a tan/beige material using THREE.MeshStandardMaterial (physically-based material that responds to light)
const wallThickness = 0.1;
Sets the thickness of all walls to 0.1 units (thin but visible)
const backWall = new THREE.Mesh( new THREE.BoxGeometry(houseWidth, WALL_HEIGHT, wallThickness), bodyMaterial );
Creates a box mesh for the back wall with dimensions 6 wide, 4 tall, 0.1 thick
backWall.position.set(0, WALL_HEIGHT / 2, -houseDepth / 2);
Positions the wall at the back of the house; Y is offset by half height so the wall sits on the ground
houseCollisionObjects.push({ mesh: backWall, box: new THREE.Box3().setFromObject(backWall) });
Adds a collision object containing the wall mesh and its bounding box so the player can't walk through it
const ladderVerticalHeight = WALL_HEIGHT;
The ladder needs to reach from ground to roof, so its vertical reach equals the wall height
const ladderSlopeLength = ladderVerticalHeight / Math.sin(LADDER_TILT_ANGLE);
Uses trigonometry: if the ladder is tilted at an angle and needs to reach vertical height H, the slope length is H / sin(angle)
const ladderHorizontalDepth = ladderVerticalHeight / Math.tan(LADDER_TILT_ANGLE);
Calculates how far from the wall the ladder extends horizontally: H / tan(angle)
tiltedLadder.rotation.x = LADDER_TILT_ANGLE;
Rotates the ladder mesh by the tilt angle (30 degrees) so it leans at an angle
tiltedLadder.position.set( 0, ladderSlopeLength / 2 * Math.sin(LADDER_TILT_ANGLE), -houseDepth / 2 - ladderHorizontalDepth / 2 );
Positions the ladder: X at center, Y offset to account for the tilt, Z against the back wall extending backward
ladderClimbZone = new THREE.Mesh( new THREE.BoxGeometry(1.2, ladderSlopeLength + 0.5, ladderHorizontalDepth + 0.5), new THREE.MeshBasicMaterial({ visible: false }) );
Creates an invisible box slightly larger than the ladder for interaction detection—invisible but checkable for collisions

📦 Key Variables

scene THREE.Scene

The Three.js scene graph that holds all 3D objects, lights, and cameras

scene = new THREE.Scene();
camera THREE.PerspectiveCamera

The first-person camera that renders the player's view

camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer THREE.WebGLRenderer

The WebGL renderer that draws the Three.js scene to the canvas

renderer = new THREE.WebGLRenderer({ antialias: true });
playerHealth number

Player's current health points (0-100); game ends when it reaches 0

let playerHealth = 100;
playerAmmo number

Number of bullets in the gun; decreases when shooting, resets when pressing R

let playerAmmo = 10;
playerHunger number

Player's hunger level (0-100); decreases over time, replenished by eating fruit, damages health if zero

let playerHunger = 100;
playerPosition THREE.Vector3

Player's 3D position in world space; updated by movement and collision; copied to camera.position

let playerPosition = new THREE.Vector3(0, 1.7, 5);
isNight boolean

True during night phase when zombies spawn; false during day

let isNight = false;
nightsSurvived number

Counter for completed nights; incremented each dawn; player wins when this reaches 7

let nightsSurvived = 0;
zombies array of Zombie objects

Array holding all active zombies in the game; zombies are added when spawned and removed when killed or night ends

let zombies = [];
bullets array of Bullet objects

Array holding all active bullets in flight; bullets are added when fired and removed on impact or timeout

let bullets = [];
guards array of Guard objects

Array holding all hired guards; guards shoot at zombies and defend the player

let guards = [];
palmTrees array of THREE.Group objects

Array holding all palm trees on the island; each tree has fruit that can be harvested

let palmTrees = [];
gameActive boolean

True when the game is running; false before start or after game over

let gameActive = false;
gameOver boolean

True when the player has died; stops the game loop

let gameOver = false;
winGame boolean

True when the player survives all 7 nights; stops the game loop with victory

let winGame = false;
dayLength number

Duration of the day phase in milliseconds (60,000 = 60 seconds)

const dayLength = 60 * 1000;
nightLength number

Duration of the night phase in milliseconds (300,000 = 300 seconds)

const nightLength = 300 * 1000;
godModeActive boolean

True when god mode cheat is activated; grants infinite ammo, invincible guards, no hunger loss

let godModeActive = false;

🔧 Potential Improvements (8)

Here are some ways this code could be enhanced:

BUG updatePlayerMovement() climbing

Ladder climb zone intersection check uses playerPosition.y but that position changes mid-climb, potentially causing the player to exit unexpectedly at exact boundaries

💡 Cache the initial Y value before climbing to determine nearLadder, and use constrain() to prevent Y from reaching exact boundary values (minClimbY + 0.1 instead of minClimbY)

PERFORMANCE checkCollisions()

Checks every bullet against every zombie (O(n*m) complexity); with many bullets and zombies, this becomes expensive

💡 Implement spatial partitioning (quadtree or grid) to only check collisions between nearby objects, or use Three.js raycasting for bullet hits instead of sphere-sphere distance

BUG updateDayNightCycle()

When transitioning from night to day, all zombies are removed but zombies can still be in mid-animation frame, potentially causing visual pops

💡 Fade zombies out over a short time (0.5 seconds) before removing them, or trigger the cleanup slightly before the transition time

STYLE onMouseDown() and shooting mechanics

Ammo deduction and casing creation are scattered; shooting logic could be encapsulated in a Player class

💡 Create a Player class to hold position, health, ammo, hunger, and shooting behavior; move shooting logic into a Player.shoot() method

FEATURE Game mechanics

No visual feedback when the player takes damage (no screen shake, flash, or red tint)

💡 Add a brief camera shake or screen flash when the player is hit by a zombie, and a separate effect when starving; improves feedback and makes danger more visceral

BUG interactWithEnvironment()

If a player interacts with a tree that's respawning at the exact moment of interaction, the message may be misleading or the respawn timer could be overwritten

💡 Add a flag to trees to track if they're 'locked' during interaction, preventing double-harvests or confusion during respawn

PERFORMANCE Guard.update() and Guard movement

Every guard calculates distance to every zombie every frame (O(n*m)); with 10 guards and 20 zombies, this is 200 calculations per frame

💡 Cache the nearest zombie and only recalculate every 5 frames, or use spatial hashing to find nearby zombies in O(1) time

BUG createPalmTrees()

Palm trees can spawn within the house bounds if random()selects coordinates in the 5-unit safe zone boundary—no collision check for indoor trees

💡 Apply the same safe-zone check as zombie spawning: `while (Math.abs(x) < 5 && Math.abs(z) < 5)` to ensure trees don't spawn indoors

🔄 Code Flow

Code flow showing setup, draw, updateplayermovement, updatedaynightcycle, zombie, onmousedown, spawnzombie, checkcollisions, updatehunger, createhouse

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> canvas-creation[Canvas Creation] setup --> three-scene-init[Three.js Scene and Renderer] setup --> lighting-setup[Lighting System] setup --> environment-creation[Environment Creation] setup --> pointer-lock[Pointer Lock Controls] setup --> ui-setup[UI Creation] setup --> gun-mesh[Gun Model] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-creation href "#sub-canvas-creation" click three-scene-init href "#sub-three-scene-init" click lighting-setup href "#sub-lighting-setup" click environment-creation href "#sub-environment-creation" click pointer-lock href "#sub-pointer-lock" click ui-setup href "#sub-ui-setup" click gun-mesh href "#sub-gun-mesh" draw --> game-over-check[Game Over Check] draw --> delta-time-calc[Delta Time Calculation] draw --> update-systems[Game System Updates] draw --> render[Three.js Render] click draw href "#fn-draw" click game-over-check href "#sub-game-over-check" click delta-time-calc href "#sub-delta-time-calc" click update-systems href "#sub-update-systems" click render href "#sub-render" update-systems --> lock-check[Pointer Lock Check] update-systems --> updateplayermovement[updatePlayerMovement] update-systems --> updatedaynightcycle[updateDayNightCycle] update-systems --> updatehunger[updateHunger] click updateplayermovement href "#fn-updateplayermovement" click updatedaynightcycle href "#fn-updatedaynightcycle" click updatehunger href "#fn-updatehunger" updateplayermovement --> normal-movement[Normal Ground Movement] updateplayermovement --> ground-collision[Ground Collision] updateplayermovement --> ladder-zone-check[Ladder Proximity Detection] updateplayermovement --> climbing-logic[Climbing System] updateplayermovement --> cheat-guards[Guard Cheat Code] updateplayermovement --> cheat-godmode[God Mode Cheat Code] click normal-movement href "#sub-normal-movement" click ground-collision href "#sub-ground-collision" click ladder-zone-check href "#sub-ladder-zone-check" click climbing-logic href "#sub-climbing-logic" click cheat-guards href "#sub-cheat-guards" click cheat-godmode href "#sub-cheat-godmode" updatedaynightcycle --> cycle-time-calc[Cycle Time Calculation] updatedaynightcycle --> day-branch[Day Logic] updatedaynightcycle --> night-branch[Night Logic] click cycle-time-calc href "#sub-cycle-time-calc" click day-branch href "#sub-day-branch" click night-branch href "#sub-night-branch" day-branch --> day-lighting[Day Lighting] day-branch --> night-transition[Night Transition Check] click day-lighting href "#sub-day-lighting" click night-transition href "#sub-night-transition" night-branch --> night-transition-check[Night Transition Check] night-branch --> zombie-spawn[Zombie Spawning] click night-transition-check href "#sub-night-transition-check" click zombie-spawn href "#sub-zombie-spawn" zombie-spawn --> spawnzombie[spawnZombie] click spawnzombie href "#fn-spawnzombie" spawnzombie --> spawn-location[Safe Spawn Location] spawnzombie --> type-selection[Zombie Type Selection] spawnzombie --> zombie-creation[Zombie Creation] click spawn-location href "#sub-spawn-location" click type-selection href "#sub-type-selection" click zombie-creation href "#sub-zombie-creation" zombie --> target-selection[Target Selection] zombie --> movement-and-facing[Movement and Facing] zombie --> attack-logic[Attack Logic] click target-selection href "#sub-target-selection" click movement-and-facing href "#sub-movement-and-facing" click attack-logic href "#sub-attack-logic" checkcollisions --> bullet-zombie-check[Bullet-Zombie Collision Loop] checkcollisions --> player-wall-check[Player-Wall Collision Loop] click checkcollisions href "#fn-checkcollisions" click bullet-zombie-check href "#sub-bullet-zombie-check" click player-wall-check href "#sub-player-wall-check" updatehunger --> hunger-check[Game State Check] updatehunger --> hunger-decrease[Hunger Decrease] updatehunger --> starvation-damage[Starvation Damage] click hunger-check href "#sub-hunger-check" click hunger-decrease href "#sub-hunger-decrease" click starvation-damage href "#sub-starvation-damage" createhouse --> group-creation[House Group] createhouse --> material-setup[Material Setup] createhouse --> wall-creation[Wall Creation] createhouse --> ladder-geometry[Ladder Geometry Calculation] createhouse --> ladder-positioning[Ladder Positioning] createhouse --> climb-zone[Climb Zone Creation] click createhouse href "#fn-createhouse" click group-creation href "#sub-group-creation" click material-setup href "#sub-material-setup" click wall-creation href "#sub-wall-creation" click ladder-geometry href "#sub-ladder-geometry" click ladder-positioning href "#sub-ladder-positioning" click climb-zone href "#sub-climb-zone"

❓ Frequently Asked Questions

What visual experience does the '7 painful nights at corbuns zombie island v2 official' sketch provide?

The sketch creates a 3D tropical island environment that transitions from a serene day to a terrifying night filled with zombies, featuring detailed elements like palm trees, a house, and various lighting effects.

How do users interact with the zombie survival game in this p5.js sketch?

Users navigate through the island in first-person, dodging zombies, managing health, ammo, and hunger, while shooting at the undead to survive the night.

What creative coding concepts does this sketch demonstrate?

The sketch exemplifies real-time game mechanics, including day-night cycles, player state management, and collision detection using 3D graphics with Three.js integration.

Preview

7 painful nights at corbuns zombie island v2 offical - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of 7 painful nights at corbuns zombie island v2 offical - Code flow showing setup, draw, updateplayermovement, updatedaynightcycle, zombie, onmousedown, spawnzombie, checkcollisions, updatehunger, createhouse
Code Flow Diagram