gta6 current state updated

This sketch creates an immersive 3D GTA-style game where you drive a car through an endless procedurally generated city, then step out on foot to shoot enemies while evading increasingly aggressive police vehicles. As your wanted level rises from 0 to 5 stars, cop cars, SWAT trucks, Humvees, and finally tanks spawn and hunt you down.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the game harder by doubling cop spawns — More cops spawn per chunk, making it harder to survive and escape
  2. Speed up the car to wild speeds — The car accelerates much faster and reaches a higher top speed, making driving chaotic and fun
  3. Paint the entire city green — All buildings turn green, creating a surreal aesthetic
  4. Make survival time give stars faster
  5. Disable world wrapping to see the edge — Comment out the world wrapping function to watch the car get stopped at the boundary
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a fully playable GTA-inspired sandbox game in the browser using three.js for 3D rendering, p5.js for sound, and custom game logic. You can drive a car through a seamlessly infinite city, press E to exit and walk on foot, aim with your mouse crosshair, shoot cops and civilians with raycasting, and watch your wanted level climb as police AI hunts you across a wrapped world. It combines procedural chunk generation, vehicle physics, collision detection, and enemy behavior trees—all the mechanics that power real open-world games.

The code is organized into several layers: global state variables track the player car and position, a chunk system that generates and destroys city content dynamically, AI logic that makes police vehicles chase and shoot, and an animation loop that updates everything 60 times per second. By studying this sketch you will learn how to build a 3D game world that feels alive, spawn and manage hundreds of interactive objects without crashing, make enemies with realistic behavior, and use raycasting to turn mouse clicks into gunshots.

⚙️ How It Works

  1. When the sketch loads, setup() calls initThreeJS() to create a three.js scene with a camera, renderer, and lighting. It then creates the player's car and initializes the chunk system, which generates the terrain, buildings, roads, NPCs, and police vehicles in a 3x3 grid of tiles around the player.
  2. Every frame, the animate() loop updates the game state. If the player is driving, it reads keyboard input to accelerate, brake, and steer the car. It then updates the camera to follow the car from a third-person perspective and calls updateChunks() to load new terrain tiles as the player moves.
  3. The police AI continuously chases the player's car—each cop car calculates the direction to the player, turns gradually toward them, and moves forward. Tanks have a turret that aims at the player and fires projectiles that move in a straight line and cause a game-over collision.
  4. Collision detection runs every frame: if the player hits a police vehicle for 2 seconds continuously, their wanted level increases by 1 star. At 5 stars, if they collide for 7 more seconds, the game ends. If a tank projectile hits the player, the game also ends.
  5. The wanted level decays automatically—if the player avoids police for 30 seconds without shooting or colliding, one star drops. Survival time milestones (1, 2, 3, 4, 5 minutes) automatically award stars even if the player doesn't interact with police.
  6. When the player presses E, they exit the car and enter on-foot mode: their gun becomes visible, a crosshair appears at screen center, and mouse clicks fire raycasts that hit and destroy NPCs or cop cars. The chunk system continuously spawns new enemies as the player explores, and the game world wraps at a boundary so it feels infinite.

🎓 Concepts You'll Learn

Three.js 3D renderingProcedural chunk-based world generationAI pathfinding and chasingCollision detection and physicsRaycasting for shooting and hit detectionState machines (driving vs on-foot)World wrapping for seamless infinite mapsGame balance (wanted levels, spawn rates, timers)

📝 Code Breakdown

preload()

preload() is called once before setup(). It's the perfect place to initialize sound generators and load resources. Using p5.Oscillator instead of a file URL guarantees the sound will work without external server dependencies.

function preload() {
  gunshotSound = new p5.Oscillator('triangle');
  gunshotSound.freq(3000);
  gunshotSound.amp(0.5);
  gunshotSound.start();
  gunshotSound.amp(0, 0);
}
Line-by-line explanation (5 lines)
gunshotSound = new p5.Oscillator('triangle');
Creates a synthetic sound generator using a triangle wave, which gives a sharp, electronic gunshot tone
gunshotSound.freq(3000);
Sets the frequency to 3000 Hz, which is a high-pitched frequency appropriate for a gunshot sound effect
gunshotSound.amp(0.5);
Sets the initial amplitude (volume) to 0.5 so the sound isn't too loud
gunshotSound.start();
Starts the oscillator running (but at 0 amplitude, so you won't hear it yet)
gunshotSound.amp(0, 0);
Immediately sets amplitude to 0 after starting, keeping the sound silent until we want to play it during shooting

setup()

setup() runs once when the sketch starts. This sketch uses it to initialize both p5.js (for sound) and three.js (for 3D rendering), then caches DOM elements so we can update the UI efficiently during the game loop.

function setup() {
  createCanvas(windowWidth, windowHeight);
  noCanvas();
  crosshairElement = document.getElementById('crosshair');
  crosshairElement.style.display = 'none';
  starsElement = document.getElementById('starsDisplay');
  timerElement = document.getElementById('timerDisplay');
  closestCopElement = document.getElementById('closestCopDisplay');
  totalCopsElement = document.getElementById('totalCopsDisplay');
  updateStarDisplay();
  timerElement.innerHTML = `Time: ${nf(survivalTimer / 1000, 1, 0)}s`;
  closestCopElement.innerHTML = 'Closest Cop: N/A';
  totalCopsElement.innerHTML = 'Total Cops: 0';
  initThreeJS();
  animate();
  mouse.x = 0;
  mouse.y = 0;
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

calculation DOM Element Caching crosshairElement = document.getElementById('crosshair');

Grabs HTML elements from the page so the sketch can show/hide and update them during gameplay

function-call Three.js Initialization initThreeJS();

Calls the main three.js setup function to create the 3D scene, camera, renderer, and all game objects

createCanvas(windowWidth, windowHeight);
Creates a p5.js canvas (though we'll immediately hide it because three.js will handle all rendering)
noCanvas();
Tells p5.js not to render anything to its canvas—three.js takes over all drawing
crosshairElement = document.getElementById('crosshair');
Stores a reference to the crosshair div so we can show it when the player enters on-foot mode
starsElement = document.getElementById('starsDisplay');
Stores a reference to the wanted level display so we can update it every time stars change
updateStarDisplay();
Initializes the UI to show 0 stars at the start of the game
initThreeJS();
Calls the massive three.js initialization function that creates the scene, camera, lights, car, player, and chunk system
animate();
Starts the three.js animation loop, which will now run 60 times per second and update the game

initThreeJS()

initThreeJS() is where all the 3D magic happens. It sets up the scene, camera, lights, and geometries once at startup. Creating geometries and materials once and reusing them for hundreds of objects is a critical performance optimization—it's why this sketch can handle spawning cop cars and buildings without lag.

function initThreeJS() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x87ceeb);
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(0, 10, 20);
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.domElement.id = 'threejs-canvas';
  document.body.appendChild(renderer.domElement);
  const ambientLight = new THREE.AmbientLight(0x404040);
  scene.add(ambientLight);
  const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  directionalLight.position.set(5, 10, 7);
  directionalLight.castShadow = true;
  directionalLight.shadow.mapSize.width = 1024;
  directionalLight.shadow.mapSize.height = 1024;
  directionalLight.shadow.camera.far = chunkSize * visibleRadius * 2;
  directionalLight.shadow.camera.left = -chunkSize * visibleRadius;
  directionalLight.shadow.camera.right = chunkSize * visibleRadius;
  directionalLight.shadow.camera.top = chunkSize * visibleRadius;
  directionalLight.shadow.camera.bottom = -chunkSize * visibleRadius;
  scene.add(directionalLight);
  // Reusable Geometries
  groundGeometry = new THREE.PlaneGeometry(chunkSize, chunkSize);
  roadGeometry = new THREE.PlaneGeometry(10, chunkSize);
  sirenLightGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.5, 8);
  groundMaterial = new THREE.MeshLambertMaterial({ color: 0x33aa33 });
  roadMaterial = new THREE.MeshLambertMaterial({ color: 0x555555 });
  buildingMaterial = new THREE.MeshLambertMaterial({ color: 0xaaaaaa });
  npcMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
  redSirenMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 });
  blueSirenMaterial = new THREE.MeshLambertMaterial({ color: 0x0000ff });
  carBodyGeometry = new THREE.BoxGeometry(3, 1, 1.5);
  carBodyMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 });
  carCabinGeometry = new THREE.BoxGeometry(2, 0.8, 1.2);
  carCabinMaterial = new THREE.MeshLambertMaterial({ color: 0x3333ff });
  carWheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.4, 16);
  carWheelMaterial = new THREE.MeshLambertMaterial({ color: 0x000000 });
  copCarBodyGeometry = new THREE.BoxGeometry(3, 1, 1.5);
  copCarBodyMaterial = new THREE.MeshLambertMaterial({ color: 0x0000ff });
  copCarCabinGeometry = new THREE.BoxGeometry(2, 0.8, 1.2);
  copCarCabinMaterial = new THREE.MeshLambertMaterial({ color: 0x3333ff });
  copCarWheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.4, 16);
  copCarWheelMaterial = new THREE.MeshLambertMaterial({ color: 0x000000 });
  playerBodyGeometry = new THREE.CylinderGeometry(0.5, 0.5, 1.8, 16);
  playerBodyMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00 });
  gunGeometry = new THREE.CylinderGeometry(0.1, 0.2, 0.8, 8);
  gunMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
  tankBodyGeometry = new THREE.BoxGeometry(4, 2, 2.5);
  tankBodyMaterial = new THREE.MeshLambertMaterial({ color: 0x444444 });
  turretGeometry = new THREE.CylinderGeometry(0.8, 1.2, 1, 16);
  turretMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 });
  cannonGeometry = new THREE.CylinderGeometry(0.3, 0.4, 2, 8);
  cannonMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
  tankWheelGeometry = new THREE.BoxGeometry(0.8, 1.5, 0.5);
  swatBodyGeometry = new THREE.BoxGeometry(4, 1.5, 2);
  swatBodyMaterial = new THREE.MeshLambertMaterial({ color: 0x222222 });
  swatTurretGeometry = new THREE.BoxGeometry(1, 0.8, 0.8);
  humveeBodyGeometry = new THREE.BoxGeometry(3.5, 1.2, 1.8);
  humveeBodyMaterial = new THREE.MeshLambertMaterial({ color: 0x445544 });
  createCar();
  createPlayer();
  createGun();
  player.position.copy(car.position);
  player.visible = false;
  updateChunks();
  document.addEventListener('keydown', onKeyDown);
  document.addEventListener('keyup', onKeyUp);
  document.addEventListener('mousedown', onMouseDown);
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

calculation Three.js Core Setup scene = new THREE.Scene();

Creates the three.js scene, camera, and renderer that will display all 3D geometry

calculation Lighting Configuration const ambientLight = new THREE.AmbientLight(0x404040);

Adds ambient and directional lights so 3D models are illuminated and cast shadows

calculation Reusable Geometries and Materials groundGeometry = new THREE.PlaneGeometry(chunkSize, chunkSize);

Pre-creates all geometry and material objects once, then reuses them for all city objects—huge performance win

function-call Game Object Creation createCar();

Creates the player's red car, on-foot player model, gun, and initial chunk of city

calculation Input Event Listeners document.addEventListener('keydown', onKeyDown);

Attaches keyboard and mouse handlers so player input is captured during the game

scene = new THREE.Scene();
Creates the main three.js scene—the container for all 3D objects, lights, and the camera
scene.background = new THREE.Color(0x87ceeb);
Sets the sky background color to a light blue (hex color 0x87ceeb)
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
Creates a perspective camera with a 75-degree field of view and aspect ratio matching the window
camera.position.set(0, 10, 20);
Positions the camera 10 units above the ground and 20 units behind the car's starting position
renderer = new THREE.WebGLRenderer({ antialias: true });
Creates a WebGL renderer that will draw the scene to the screen with anti-aliasing for smooth edges
renderer.setSize(window.innerWidth, window.innerHeight);
Makes the three.js canvas fill the entire window
document.body.appendChild(renderer.domElement);
Adds the three.js canvas to the HTML page so you can see the 3D world
const ambientLight = new THREE.AmbientLight(0x404040);
Creates soft ambient lighting so all surfaces are illuminated even without direct light
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
Creates a sun-like light that casts shadows and illuminates the scene realistically
directionalLight.castShadow = true;
Enables shadow casting for the directional light so objects cast shadows on the ground
groundGeometry = new THREE.PlaneGeometry(chunkSize, chunkSize);
Creates a plane geometry once and reuses it for every ground tile in every chunk—critical for performance
groundMaterial = new THREE.MeshLambertMaterial({ color: 0x33aa33 });
Creates a green material once and reuses it for all ground planes
createCar();
Builds the player's starting car from the pre-created geometries and materials
createPlayer();
Creates the on-foot player character (initially invisible since the player starts driving)
updateChunks();
Generates the initial 3x3 grid of city chunks around the player's starting position
document.addEventListener('keydown', onKeyDown);
Registers a function to run every time the player presses a key

createChunkContent(chunkX, chunkZ)

createChunkContent() is called every time the player moves into a new tile. It procedurally generates buildings, roads, NPCs, and police vehicles in a random but consistent way. The key optimization is that it only spawns police near the player, keeping the object count manageable. Each object is marked with chunkCoords so it can be cleaned up when the chunk is unloaded.

function createChunkContent(chunkX, chunkZ) {
  const chunkGroup = new THREE.Group();
  const offsetX = chunkX * chunkSize;
  const offsetZ = chunkZ * chunkSize;
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
  ground.rotation.x = -Math.PI / 2;
  ground.position.y = -0.5;
  ground.position.x = offsetX;
  ground.position.z = offsetZ;
  ground.receiveShadow = true;
  chunkGroup.add(ground);
  const road1 = new THREE.Mesh(roadGeometry, roadMaterial);
  road1.rotation.x = -Math.PI / 2;
  road1.position.y = -0.49;
  road1.position.x = offsetX;
  road1.position.z = offsetZ;
  road1.receiveShadow = true;
  chunkGroup.add(road1);
  const road2 = new THREE.Mesh(roadGeometry, roadMaterial);
  road2.rotation.x = -Math.PI / 2;
  road2.position.y = -0.49;
  road2.position.x = offsetX + 20;
  road2.position.z = offsetZ;
  road2.receiveShadow = true;
  chunkGroup.add(road2);
  const road3 = new THREE.Mesh(roadGeometry, roadMaterial);
  road3.rotation.x = -Math.PI / 2;
  road3.position.y = -0.49;
  road3.position.x = offsetX - 20;
  road3.position.z = offsetZ;
  road3.receiveShadow = true;
  chunkGroup.add(road3);
  const numBuildingsPerChunk = 15;
  const halfChunk = chunkSize / 2;
  for (let i = 0; i < numBuildingsPerChunk; i++) {
    const height = Math.random() * 20 + 5;
    const building = new THREE.Mesh(carBodyGeometry.clone(), buildingMaterial);
    building.geometry.applyMatrix4(new THREE.Matrix4().makeScale(1, height / 1, 1));
    building.position.y = height / 2;
    let x, z;
    do {
      x = (Math.random() * chunkSize - halfChunk);
      z = (Math.random() * chunkSize - halfChunk);
    } while (x > -5 && x < 5);
    building.position.x = offsetX + x;
    building.position.z = offsetZ + z;
    building.castShadow = true;
    building.receiveShadow = true;
    chunkGroup.add(building);
  }
  const numNpcsPerChunk = 8;
  for (let i = 0; i < numNpcsPerChunk; i++) {
    const npcHeight = 1.8;
    const npc = new THREE.Mesh(playerBodyGeometry.clone(), npcMaterial);
    npc.geometry.applyMatrix4(new THREE.Matrix4().makeScale(1, npcHeight / 1.8, 1));
    npc.position.y = npcHeight / 2;
    let x, z;
    do {
      x = (Math.random() * chunkSize - halfChunk);
      z = (Math.random() * chunkSize - halfChunk);
    } while (x > -10 && x < 10);
    npc.position.x = offsetX + x;
    npc.position.z = offsetZ + z;
    npc.castShadow = true;
    npc.receiveShadow = true;
    chunkGroup.add(npc);
    npcs.push(npc);
    npc.chunkCoords = { x: chunkX, z: chunkZ };
    npc.isNPC = true;
  }
  const spawnRadius = 2;
  if (stars < 5 && Math.abs(chunkX - currentChunkX) <= spawnRadius && Math.abs(chunkZ - currentChunkZ) <= spawnRadius) {
    for (let i = 0; i < numCopCarsPerChunk; i++) {
      const copCar = createCopCarModel();
      let x, z;
      do {
        x = (Math.random() * chunkSize - halfChunk);
        z = (Math.random() * chunkSize - halfChunk);
      } while (x > -30 && x < 30);
      copCar.position.set(offsetX + x, 0, offsetZ + z);
      chunkGroup.add(copCar);
      copCars.push(copCar);
      copCar.chunkCoords = { x: chunkX, z: chunkZ };
    }
    for (let i = 0; i < numSwatTrucksPerChunk; i++) {
      const swatTruck = createSwatTruckModel();
      let x, z;
      do {
        x = (Math.random() * chunkSize - halfChunk);
        z = (Math.random() * chunkSize - halfChunk);
      } while (x > -30 && x < 30);
      swatTruck.position.set(offsetX + x, 0, offsetZ + z);
      chunkGroup.add(swatTruck);
      copCars.push(swatTruck);
      swatTruck.chunkCoords = { x: chunkX, z: chunkZ };
    }
    for (let i = 0; i < numMilitaryHumveesPerChunk; i++) {
      const humvee = createMilitaryHumveeModel();
      let x, z;
      do {
        x = (Math.random() * chunkSize - halfChunk);
        z = (Math.random() * chunkSize - halfChunk);
      } while (x > -30 && x < 30);
      humvee.position.set(offsetX + x, 0, offsetZ + z);
      chunkGroup.add(humvee);
      copCars.push(humvee);
      humvee.chunkCoords = { x: chunkX, z: chunkZ };
    }
  }
  return chunkGroup;
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

for-loop Ground and Road Creation const ground = new THREE.Mesh(groundGeometry, groundMaterial);

Creates the grass ground plane and three parallel roads using pre-made geometries

for-loop Procedural Building Generation for (let i = 0; i < numBuildingsPerChunk; i++) {

Randomly places 15 buildings of varying heights throughout the chunk, avoiding the main road

for-loop NPC Pedestrian Spawning for (let i = 0; i < numNpcsPerChunk; i++) {

Randomly places 8 NPC civilians throughout the chunk that can be shot for wanted level points

conditional Police Vehicle Spawning if (stars < 5 && Math.abs(chunkX - currentChunkX) <= spawnRadius && Math.abs(chunkZ - currentChunkZ) <= spawnRadius) {

Only spawns police vehicles in chunks near the player and only when wanted level is below 5 stars

const chunkGroup = new THREE.Group();
Creates a container group that will hold all the objects (ground, buildings, NPCs, police) for this chunk
const offsetX = chunkX * chunkSize;
Calculates the world-space X coordinate for this chunk based on its grid position and chunk size
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
Creates the grass ground plane for this chunk by combining the pre-made ground geometry with the green material
ground.rotation.x = -Math.PI / 2;
Rotates the ground plane 90 degrees so it's horizontal (by default planes are vertical)
ground.position.set(offsetX + x, 0, offsetZ + z);
Places this NPC in the world at the calculated chunk offset plus random position within the chunk
npc.isNPC = true;
Marks this mesh with a property so the shooting code knows it's an NPC (vs a cop car) and awards 1 star for hitting it
if (stars < 5 && Math.abs(chunkX - currentChunkX) <= spawnRadius && Math.abs(chunkZ - currentChunkZ) <= spawnRadius) {
Only spawn police vehicles in chunks near the player (within 2 chunks in any direction) and only if wanted level is under 5 stars
for (let i = 0; i < numCopCarsPerChunk; i++) {
Loops to create multiple regular cop cars per chunk (default is 3, controlled by the numCopCarsPerChunk constant)
while (x > -30 && x < 30);
Keeps generating random positions until we find one outside the main road area (x=0 ± 30), so cops don't spawn directly on the road

animate()

animate() is the heart of the game—it runs 60 times per second and is responsible for physics, AI, collision detection, and rendering. This function is enormous because the sketch does so much: car physics, police AI with turret aiming, projectile tracking, wanted-level management, and chunk updates. In a professional game engine, this would be split into separate systems, but here it's all in one place for clarity.

function animate() {
  requestAnimationFrame(animate);
  const currentTime = Date.now();
  if (gameOver) {
    renderer.render(scene, camera);
    return;
  }
  if (!gameOver) {
    survivalTimer += deltaTime;
    timerElement.innerHTML = `Time: ${nf(survivalTimer / 1000, 1, 0)}s`;
    for (let i = 0; i < survivalMilestones.length; i++) {
      if (survivalTimer >= survivalMilestones[i] && stars <= i) {
        stars = i + 1;
        updateStarDisplay();
        console.log(`Reached ${stars} stars!`);
        lastWantedActionTime = currentTime;
        if (stars === 5) {
          console.log("5 Stars! Spawning Tank Police Cars!");
          const spawnRadius = 2;
          const halfChunk = chunkSize / 2;
          for (let j = 0; j < numTankCopCars; j++) {
            let x, z;
            do {
              x = (Math.random() * chunkSize - halfChunk);
              z = (Math.random() * chunkSize - halfChunk);
            } while (x > -30 && x < 30);
            const tankCar = createTankCopCarModel();
            tankCar.position.set(currentChunkX * chunkSize + x, 0, currentChunkZ * chunkSize + z);
            scene.add(tankCar);
            copCars.push(tankCar);
          }
        }
        break;
      }
    }
  }
  if (stars > 0 && currentTime - lastWantedActionTime > wantedLevelDecayThreshold) {
    stars = Math.max(0, stars - 1);
    updateStarDisplay();
    lastWantedActionTime = currentTime;
    console.log(`Wanted level decreased to ${stars} stars.`);
  }
  if (playerState === 'driving') {
    if (keys['w']) {
      carSpeed += carAcceleration;
      carSpeed = Math.min(carSpeed, carMaxSpeed);
    } else if (keys['s']) {
      if (carSpeed > 0) {
        carSpeed -= carBrakeDeceleration;
        carSpeed = Math.max(carSpeed, 0);
      } else {
        carSpeed -= carAcceleration / 2;
        carSpeed = Math.max(carSpeed, -carMaxSpeed / 2);
      }
    } else {
      if (carSpeed > 0) {
        carSpeed -= carDeceleration;
        carSpeed = Math.max(carSpeed, 0);
      } else if (carSpeed < 0) {
        carSpeed += carDeceleration;
        carSpeed = Math.min(carSpeed, 0);
      }
    }
    if (carSpeed !== 0) {
      if (keys['a']) {
        car.rotation.y += carTurnSpeed * (carSpeed > 0 ? 1 : -1);
      }
      if (keys['d']) {
        car.rotation.y -= carTurnSpeed * (carSpeed > 0 ? 1 : -1);
      }
    }
    const forwardVector = new THREE.Vector3(0, 0, -1);
    forwardVector.applyQuaternion(car.quaternion);
    car.position.addScaledVector(forwardVector, carSpeed);
    car.position.y = 0;
    wrapPosition(car.position, worldSize);
    const cameraOffset = new THREE.Vector3(0, 10, 15);
    const rotatedOffset = cameraOffset.clone().applyQuaternion(car.quaternion);
    camera.position.copy(car.position).add(rotatedOffset);
    camera.lookAt(car.position);
    updateChunks();
    for (const policeVehicle of copCars) {
      let playerPos = car.position;
      let policeVehiclePos = policeVehicle.position;
      let direction = playerPos.clone().sub(policeVehiclePos);
      direction.y = 0;
      if (Math.abs(direction.x) > worldSize / 2) {
        direction.x -= Math.sign(direction.x) * worldSize;
      }
      if (Math.abs(direction.z) > worldSize / 2) {
        direction.z -= Math.sign(direction.z) * worldSize;
      }
      direction.normalize();
      let desiredAngle = Math.atan2(direction.x, direction.z);
      let currentAngle = policeVehicle.rotation.y;
      let angleDiff = desiredAngle - currentAngle;
      if (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
      if (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
      let vehicleTurnSpeed = copCarTurnSpeed;
      if (policeVehicle.isTank) vehicleTurnSpeed = tankCopCarTurnSpeed;
      policeVehicle.rotation.y += Math.sign(angleDiff) * Math.min(Math.abs(angleDiff), vehicleTurnSpeed);
      let vehicleSpeed = copCarSpeed;
      if (policeVehicle.isTank) vehicleSpeed = tankCopCarSpeed;
      policeVehicle.position.addScaledVector(direction, vehicleSpeed);
      policeVehicle.position.y = 0;
      wrapPosition(policeVehicle.position, worldSize);
      if (policeVehicle.isTank) {
        const distanceToPlayer = policeVehicle.position.distanceTo(car.position);
        if (distanceToPlayer < tankShootingRange && carSpeed !== 0) {
          const tankTurret = policeVehicle.children[1];
          const tankTurretPos = new THREE.Vector3();
          tankTurret.getWorldPosition(tankTurretPos);
          const targetPos = car.position.clone();
          targetPos.y += 0.5;
          const tankDirection = targetPos.clone().sub(tankTurretPos).normalize();
          const desiredTurretAngle = Math.atan2(tankDirection.x, tankDirection.z);
          let currentTurretAngle = tankTurret.rotation.y;
          let turretAngleDiff = desiredTurretAngle - currentTurretAngle;
          if (turretAngleDiff > Math.PI) turretAngleDiff -= 2 * Math.PI;
          if (turretAngleDiff < -Math.PI) turretAngleDiff += 2 * Math.PI;
          tankTurret.rotation.y += Math.sign(turretAngleDiff) * 0.08;
          if (currentTime - policeVehicle.shootingTimer > tankShootingCooldown) {
            policeVehicle.shootingTimer = currentTime;
            const projectile = createProjectile(tankTurretPos, tankDirection);
            projectiles.push(projectile);
            scene.add(projectile);
          }
        }
      }
    }
    inContinuousCollision = false;
    for (const policeVehicle of copCars) {
      if (car.position.distanceTo(policeVehicle.position) < collisionDistance) {
        inContinuousCollision = true;
        break;
      }
    }
    if (inContinuousCollision) {
      if (continuousCollisionTimer === 0) {
        continuousCollisionTimer = currentTime;
      } else {
        const elapsed = currentTime - continuousCollisionTimer;
        if (elapsed >= continuousCollisionThreshold) {
          stars = Math.min(stars + 1, 5);
          updateStarDisplay();
          lastWantedActionTime = currentTime;
          continuousCollisionTimer = currentTime;
          console.log(`Wanted level increased to ${stars} stars due to collision.`);
        }
      }
      if (stars === 5) {
        if (finalGameOverCollisionTimer === 0) {
          finalGameOverCollisionTimer = currentTime;
        } else {
          const finalElapsed = currentTime - finalGameOverCollisionTimer;
          if (finalElapsed >= finalGameOverCollisionThreshold) {
            gameOver = true;
            carSpeed = 0;
            scene.background = new THREE.Color(0x000000);
            document.getElementById('gameOverImage').style.display = 'block';
            document.getElementById('crosshair').style.display = 'none';
            console.log('Game Over! You were caught by a police vehicle for 7 seconds at 5 stars!');
            return;
          }
        }
      } else {
        finalGameOverCollisionTimer = 0;
      }
    } else {
      continuousCollisionTimer = 0;
      finalGameOverCollisionTimer = 0;
    }
    for (let i = projectiles.length - 1; i >= 0; i--) {
      const projectile = projectiles[i];
      projectile.position.addScaledVector(projectile.velocity, projectileSpeed);
      if (projectile.position.distanceTo(car.position) < (projectileRadius + collisionDistance / 2)) {
        gameOver = true;
        carSpeed = 0;
        scene.background = new THREE.Color(0x000000);
        document.getElementById('gameOverImage').style.display = 'block';
        document.getElementById('crosshair').style.display = 'none';
        console.log('Game Over! You were hit by a tank projectile!');
        scene.remove(projectile);
        projectile.geometry.dispose();
        projectile.material.dispose();
        projectiles.splice(i, 1);
        return;
      }
      if (Math.abs(projectile.position.x) > worldSize / 2 || Math.abs(projectile.position.z) > worldSize / 2) {
        scene.remove(projectile);
        projectile.geometry.dispose();
        projectile.material.dispose();
        projectiles.splice(i, 1);
      }
    }
    let minDistance = Infinity;
    for (const policeVehicle of copCars) {
      minDistance = Math.min(minDistance, car.position.distanceTo(policeVehicle.position));
    }
    if (copCars.length > 0) {
      closestCopElement.innerHTML = `Closest Cop: ${nf(minDistance, 1, 0)}m`;
    } else {
      closestCopElement.innerHTML = 'Closest Cop: N/A';
    }
    totalCopsElement.innerHTML = `Total Cops: ${copCars.length}`;
  } else if (playerState === 'onFoot') {
    const playerForwardVector = new THREE.Vector3(0, 0, -1);
    playerForwardVector.applyQuaternion(player.quaternion);
    const playerRightVector = new THREE.Vector3(1, 0, 0);
    playerRightVector.applyQuaternion(player.quaternion);
    if (keys['w']) {
      player.position.addScaledVector(playerForwardVector, playerSpeed);
    }
    if (keys['s']) {
      player.position.addScaledVector(playerForwardVector, -playerSpeed);
    }
    if (keys['a']) {
      player.rotation.y += playerTurnSpeed;
    }
    if (keys['d']) {
      player.rotation.y -= playerTurnSpeed;
    }
    player.position.y = 0;
    wrapPosition(player.position, worldSize);
    const playerCameraOffset = new THREE.Vector3(0, 1.6, 0);
    const rotatedPlayerOffset = playerCameraOffset.clone().applyQuaternion(player.quaternion);
    camera.position.copy(player.position).add(rotatedPlayerOffset);
    camera.lookAt(player.position.clone().add(playerForwardVector.multiplyScalar(10)));
    let minDistance = Infinity;
    for (const policeVehicle of copCars) {
      minDistance = Math.min(minDistance, player.position.distanceTo(policeVehicle.position));
    }
    if (copCars.length > 0) {
      closestCopElement.innerHTML = `Closest Cop: ${nf(minDistance, 1, 0)}m`;
    } else {
      closestCopElement.innerHTML = 'Closest Cop: N/A';
    }
    totalCopsElement.innerHTML = `Total Cops: ${copCars.length}`;
  }
  renderer.render(scene, camera);
}
Line-by-line explanation (29 lines)

🔧 Subcomponents:

conditional Survival Timer and Star Milestones if (survivalTimer >= survivalMilestones[i] && stars <= i) {

Every frame, check if the player has survived long enough to earn the next star (60s, 120s, 180s, etc.)

calculation Car Physics and Controls if (keys['w']) {

Updates car speed, rotation, and position based on keyboard input and physics constants

for-loop Police AI Chasing Loop for (const policeVehicle of copCars) {

Every cop car calculates direction to the player, turns toward them, moves forward, and fires projectiles if it's a tank

conditional Collision Detection if (inContinuousCollision) {

If the player touches a cop for 2 seconds continuously, increase wanted level by 1 star; at 5 stars, game ends after 7 seconds

for-loop Tank Projectile Updates for (let i = projectiles.length - 1; i >= 0; i--) {

Every projectile moves forward, checks collision with the player's car, and is removed if it goes out of bounds

requestAnimationFrame(animate);
Schedules animate() to run again on the next frame, creating a continuous loop
const currentTime = Date.now();
Captures the current time in milliseconds so we can measure cooldown timers and elapsed collision time
if (gameOver) {
If the game is over, stop all game logic and just render the final scene (black screen with game-over image)
survivalTimer += deltaTime;
deltaTime is a p5.js variable that stores the time elapsed since the last frame—add it to track total survival time
if (survivalTimer >= survivalMilestones[i] && stars <= i) {
If the player has survived long enough (e.g., 60 seconds for the first star) and doesn't already have this star, award it
if (stars === 5) {
When the player reaches 5 stars (either through survival time or collision), spawn 5 tank cop cars immediately
if (keys['w']) {
If the W key is pressed, increase the car's speed up to the maximum
carSpeed += carAcceleration;
Add acceleration (0.05 units/frame) to the car's current speed
carSpeed = Math.min(carSpeed, carMaxSpeed);
Cap the speed at the maximum so the car doesn't accelerate infinitely
if (carSpeed !== 0) {
Only allow turning if the car is moving—realistic car physics where you can't turn while stopped
car.rotation.y += carTurnSpeed * (carSpeed > 0 ? 1 : -1);
Rotate the car left; multiply by (carSpeed > 0 ? 1 : -1) so turning reverses if the car is moving backwards
const forwardVector = new THREE.Vector3(0, 0, -1);
Create a vector pointing forward in the car's local coordinate space (before rotation)
forwardVector.applyQuaternion(car.quaternion);
Apply the car's rotation to this vector so it points in the direction the car is facing
car.position.addScaledVector(forwardVector, carSpeed);
Move the car forward by adding the forward vector scaled by the current speed
camera.position.copy(car.position).add(rotatedOffset);
Position the camera behind and above the car so the player sees a third-person view
for (const policeVehicle of copCars) {
Loop through every active police vehicle and make it chase the player
let direction = playerPos.clone().sub(policeVehiclePos);
Calculate the vector from the cop to the player—this points in the direction the cop should move
direction.normalize();
Make the direction vector have length 1 so all cops move at the same speed regardless of distance
policeVehicle.rotation.y += Math.sign(angleDiff) * Math.min(Math.abs(angleDiff), vehicleTurnSpeed);
Rotate the cop toward the desired angle, but only by a limited amount per frame (vehicleTurnSpeed)
policeVehicle.position.addScaledVector(direction, vehicleSpeed);
Move the cop forward in the direction of the player at its speed (copCarSpeed or tankCopCarSpeed)
if (policeVehicle.isTank) {
Tanks have special behavior: they aim their turret and fire projectiles at the player
if (distanceToPlayer < tankShootingRange && carSpeed !== 0) {
Only shoot if the player is within range and moving—stationary players are harder to hit
const projectile = createProjectile(tankTurretPos, tankDirection);
Create a red sphere projectile at the tank's turret position, moving in the direction of the player
if (inContinuousCollision) {
If the player is touching a cop car right now, start or continue the collision timer
if (elapsed >= continuousCollisionThreshold) {
If the collision has lasted 2 seconds, increase the wanted level by 1 star and reset the timer
if (finalElapsed >= finalGameOverCollisionThreshold) {
If the player is at 5 stars AND has collided for 7 seconds, end the game
projectile.position.addScaledVector(projectile.velocity, projectileSpeed);
Move the projectile forward by adding its velocity scaled by projectileSpeed (10 units/frame)
if (projectile.position.distanceTo(car.position) < (projectileRadius + collisionDistance / 2)) {
If the projectile comes within a small distance of the player's car center, it's a hit and the game ends
renderer.render(scene, camera);
Draw the entire scene to the screen with the camera's current view

shoot()

shoot() is triggered every time the player clicks the mouse while on foot. It uses raycasting—a technique where you fire an invisible ray from the camera and check what 3D objects it passes through. This is much more efficient than checking distance to every object. The function also plays sound effects and creates temporary visual feedback (hit effects and muzzle flashes) to make shooting feel responsive and satisfying.

function shoot() {
  raycaster.setFromCamera(mouse, camera);
  if (gunshotSound) {
    if (gunshotSound instanceof p5.Oscillator) {
      gunshotSound.amp(0, 0.01);
      gunshotSound.amp(0.5, 0.05);
      gunshotSound.amp(0.2, 0.05);
      gunshotSound.amp(0, 0.2);
    } else {
      gunshotSound.play();
    }
  }
  const hittableObjects = [...npcs, ...copCars];
  const intersects = raycaster.intersectObjects(hittableObjects);
  if (intersects.length > 0) {
    const hitObject = intersects[0].object;
    console.log('Hit:', hitObject);
    scene.remove(hitObject);
    if (hitObject.isNPC) {
      npcs = npcs.filter(npc => npc !== hitObject);
      stars = Math.min(stars + 1, 5);
      updateStarDisplay();
    } else if (hitObject.isCopCar || hitObject.isSwatTruck || hitObject.isMilitaryHumvee || hitObject.isTank) {
      copCars = copCars.filter(copCar => copCar !== hitObject);
      stars = Math.min(stars + 2, 5);
      updateStarDisplay();
    }
    if (hitObject.geometry) hitObject.geometry.dispose();
    const hitEffectGeometry = new THREE.SphereGeometry(0.1, 8, 8);
    const hitEffectMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
    const hitEffect = new THREE.Mesh(hitEffectGeometry, hitEffectMaterial);
    hitEffect.position.copy(intersects[0].point);
    scene.add(hitEffect);
    setTimeout(() => {
      scene.remove(hitEffect);
      hitEffectGeometry.dispose();
      hitEffectMaterial.dispose();
    }, 100);
  }
  const gunFlashGeometry = new THREE.CylinderGeometry(0.1, 0.2, 0.2, 8);
  const gunFlashMaterial = new THREE.MeshBasicMaterial({ color: 0xffa500 });
  const gunFlash = new THREE.Mesh(gunFlashGeometry, gunFlashMaterial);
  gunFlash.position.copy(gun.position);
  gunFlash.position.z -= 0.5;
  gunFlash.rotation.x = Math.PI / 2;
  camera.add(gunFlash);
  setTimeout(() => {
    camera.remove(gunFlash);
    gunFlashGeometry.dispose();
    gunFlashMaterial.dispose();
  }, 50);
}
Line-by-line explanation (18 lines)

🔧 Subcomponents:

calculation Raycast Initialization raycaster.setFromCamera(mouse, camera);

Creates an invisible ray from the camera through the screen center (where the crosshair is)

conditional Gunshot Sound Playback if (gunshotSound instanceof p5.Oscillator) {

Plays a brief synthetic gunshot sound by quickly ramping the oscillator's amplitude up and down

conditional Hit Detection and Target Removal if (intersects.length > 0) {

If the ray intersects any object, remove it from the scene, update the wanted level, and play hit effects

calculation Muzzle Flash and Hit Effect const gunFlashGeometry = new THREE.CylinderGeometry(0.1, 0.2, 0.2, 8);

Creates temporary visual feedback (a yellow hit effect and orange muzzle flash) to make shooting feel satisfying

raycaster.setFromCamera(mouse, camera);
Sets up a raycaster that shoots a ray from the camera through the screen center (mouse.x=0, mouse.y=0)
if (gunshotSound instanceof p5.Oscillator) {
Checks if the gunshot sound is a p5.Oscillator (synthetic) or a loaded sound file
gunshotSound.amp(0, 0.01);
Instantly mutes the sound to ensure it starts silent
gunshotSound.amp(0.5, 0.05);
Ramps the volume up to 0.5 over 0.05 seconds (attack phase)
gunshotSound.amp(0.2, 0.05);
Ramps the volume down to 0.2 over 0.05 seconds (decay phase)
gunshotSound.amp(0, 0.2);
Ramps the volume to 0 over 0.2 seconds (release phase), completing the gunshot envelope
const hittableObjects = [...npcs, ...copCars];
Creates a combined array of all NPCs and police vehicles that can be shot
const intersects = raycaster.intersectObjects(hittableObjects);
Finds all objects the ray passes through, sorted by distance from the camera
const hitObject = intersects[0].object;
Gets the first (closest) object hit by the ray
scene.remove(hitObject);
Removes the hit object from the scene so it disappears immediately
if (hitObject.isNPC) {
If the hit object is marked as an NPC, award 1 star and remove it from the NPC array
stars = Math.min(stars + 1, 5);
Increase wanted level by 1, but cap it at 5 stars maximum
if (hitObject.geometry) hitObject.geometry.dispose();
Frees the GPU memory used by the hit object's geometry to prevent memory leaks
const hitEffect = new THREE.Mesh(hitEffectGeometry, hitEffectMaterial);
Creates a small yellow sphere at the hit point to visually show where the bullet struck
hitEffect.position.copy(intersects[0].point);
Places the hit effect exactly where the ray intersected the object
setTimeout(() => { scene.remove(hitEffect); }, 100);
After 100 milliseconds, removes the hit effect from the scene so it doesn't clutter the view
const gunFlash = new THREE.Mesh(gunFlashGeometry, gunFlashMaterial);
Creates an orange cylinder at the gun barrel to simulate a muzzle flash
camera.add(gunFlash);
Adds the gun flash to the camera so it moves with the player's view and appears in the correct position

onKeyDown(event)

onKeyDown() is called every time the player presses a key. It records which key was pressed in the `keys` object, which animate() reads every frame. When E is pressed, it seamlessly switches between driving and on-foot mode by syncing positions, showing/hiding the car and gun, and updating the UI.

function onKeyDown(event) {
  if (gameOver) return;
  keys[event.key.toLowerCase()] = true;
  if (event.key.toLowerCase() === 'e') {
    if (playerState === 'driving') {
      playerState = 'onFoot';
      player.position.copy(car.position);
      player.rotation.copy(car.rotation);
      car.visible = false;
      gun.visible = true;
      carSpeed = 0;
      if (crosshairElement) crosshairElement.style.display = 'block';
    } else {
      playerState = 'driving';
      car.position.copy(player.position);
      car.rotation.copy(player.rotation);
      car.visible = true;
      gun.visible = false;
      if (crosshairElement) crosshairElement.style.display = 'none';
    }
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

calculation Key State Tracking keys[event.key.toLowerCase()] = true;

Records that this key is pressed so the animate() loop can read it every frame

conditional Driving/On-Foot Toggle if (event.key.toLowerCase() === 'e') {

Pressing E swaps between driving the car and walking on foot, syncing positions and showing/hiding the gun

if (gameOver) return;
Don't allow any input if the game is over
keys[event.key.toLowerCase()] = true;
Stores the pressed key in an object so animate() can check if W, A, S, or D are held down
if (event.key.toLowerCase() === 'e') {
If E is pressed, toggle between driving and walking
playerState = 'onFoot';
Switch to walking mode
player.position.copy(car.position);
Teleport the player to the car's current location so they don't appear across the map
player.rotation.copy(car.rotation);
Make the player face the same direction the car was facing
car.visible = false;
Hide the car so it doesn't appear as a ghost while walking
gun.visible = true;
Show the gun attached to the camera so the player can aim and shoot
carSpeed = 0;
Stop the car's movement so it doesn't drift away while the player is on foot
if (crosshairElement) crosshairElement.style.display = 'block';
Show the crosshair at the screen center so the player can aim at NPCs and police

onMouseDown(event)

onMouseDown() is called every time the player clicks the mouse. It enforces a shooting cooldown (300ms) so the player can't fire infinitely fast, which keeps the game balanced. It also resets the wanted-level decay timer since shooting is an action that keeps cops alert.

function onMouseDown(event) {
  if (gameOver) return;
  if (playerState === 'onFoot' && event.button === 0) {
    const currentTime = Date.now();
    if (currentTime - lastShotTime > shootingCooldown) {
      lastShotTime = currentTime;
      shoot();
      lastWantedActionTime = currentTime;
    }
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Shooting Cooldown Check if (currentTime - lastShotTime > shootingCooldown) {

Ensures the player can't shoot faster than once every 300 milliseconds, balancing gameplay

if (gameOver) return;
Don't allow shooting if the game is over
if (playerState === 'onFoot' && event.button === 0) {
Only shoot if the player is walking (not driving) and the left mouse button was clicked
const currentTime = Date.now();
Get the current time in milliseconds
if (currentTime - lastShotTime > shootingCooldown) {
Check if enough time (300 milliseconds) has passed since the last shot
lastShotTime = currentTime;
Record the time of this shot so the cooldown timer starts over
shoot();
Fire the gun, which performs raycasting to hit enemies
lastWantedActionTime = currentTime;
Reset the wanted-level decay timer because shooting is a 'wanted action' that prolongs the timer

updateChunks()

updateChunks() is called every frame but only does expensive work when the player moves to a new chunk. It maintains a 3x3 grid of chunks around the player, creating new ones as the player explores and destroying old ones that are far away. This is how infinite worlds work—the world doesn't really extend infinitely; it just creates and destroys terrain tiles as the player moves.

function updateChunks() {
  const newChunkX = Math.floor(car.position.x / chunkSize);
  const newChunkZ = Math.floor(car.position.z / chunkSize);
  if (newChunkX !== currentChunkX || newChunkZ !== currentChunkZ) {
    console.time('Chunk Update');
    currentChunkX = newChunkX;
    currentChunkZ = newChunkZ;
    const activeChunkKeys = new Set();
    for (let cx = currentChunkX - visibleRadius; cx <= currentChunkX + visibleRadius; cx++) {
      for (let cz = currentChunkZ - visibleRadius; cz <= currentChunkZ + visibleRadius; cz++) {
        const key = `${cx},${cz}`;
        activeChunkKeys.add(key);
        if (!chunks.has(key)) {
          const chunkGroup = createChunkContent(cx, cz);
          chunks.set(key, chunkGroup);
          scene.add(chunkGroup);
        }
      }
    }
    const sharedGeometries = new Set([
        groundGeometry, roadGeometry, sirenLightGeometry,
        carBodyGeometry, carCabinGeometry, carWheelGeometry,
        copCarBodyGeometry, copCarCabinGeometry, copCarWheelGeometry,
        playerBodyGeometry, gunGeometry,
        tankBodyGeometry, turretGeometry, cannonGeometry,
        swatBodyGeometry, swatTurretGeometry,
        humveeBodyGeometry
    ]);
    const sharedMaterials = new Set([
        groundMaterial, roadMaterial, buildingMaterial, npcMaterial,
        redSirenMaterial, blueSirenMaterial,
        carBodyMaterial, carCabinMaterial, carWheelMaterial,
        copCarBodyMaterial, copCarCabinMaterial, copCarWheelMaterial,
        playerBodyMaterial, gunMaterial,
        tankBodyGeometry, turretMaterial, cannonMaterial, tankWheelMaterial,
        swatBodyMaterial,
        humveeBodyMaterial
    ]);
    chunks.forEach((chunkGroup, key) => {
      if (!activeChunkKeys.has(key)) {
        scene.remove(chunkGroup);
        chunkGroup.children.forEach(object => {
          if (object.geometry && !sharedGeometries.has(object.geometry)) {
            object.geometry.dispose();
          }
          if (object.material && !sharedMaterials.has(object.material)) {
            object.material.dispose();
          }
        });
        npcs = npcs.filter(npc => npc.chunkCoords && (npc.chunkCoords.x !== currentChunkX || npc.chunkCoords.z !== currentChunkZ));
        copCars = copCars.filter(copCar => copCar.chunkCoords && (copCar.chunkCoords.x !== currentChunkX || copCar.chunkCoords.z !== currentChunkZ));
        chunks.delete(key);
      }
    });
    console.timeEnd('Chunk Update');
  }
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

calculation Chunk Position Calculation const newChunkX = Math.floor(car.position.x / chunkSize);

Converts the car's world position into grid coordinates (which chunk is the car in?)

for-loop Load New Chunks for (let cx = currentChunkX - visibleRadius; cx <= currentChunkX + visibleRadius; cx++) {

Creates a 3x3 grid of chunks around the player and loads any new ones that haven't been created yet

for-loop Unload Old Chunks chunks.forEach((chunkGroup, key) => {

Removes chunks that are no longer visible, disposing of their geometry and materials to free memory

const newChunkX = Math.floor(car.position.x / chunkSize);
Divides the car's X position by the chunk size (200) and rounds down to get the chunk's grid X coordinate
if (newChunkX !== currentChunkX || newChunkZ !== currentChunkZ) {
Only update chunks if the player has moved to a new chunk—this saves performance by skipping the update when the player is still in the same chunk
for (let cx = currentChunkX - visibleRadius; cx <= currentChunkX + visibleRadius; cx++) {
Loop through chunks in a 3x3 grid centered on the player (visibleRadius=1 means 1 chunk in each direction)
const key = `${cx},${cz}`;
Create a string key like '5,3' to uniquely identify this chunk
if (!chunks.has(key)) {
If this chunk hasn't been created yet, create it now and add it to the scene
const chunkGroup = createChunkContent(cx, cz);
Call createChunkContent() to generate the terrain, buildings, NPCs, and police for this chunk
chunks.set(key, chunkGroup);
Store the chunk group in the chunks map so we can find and remove it later
scene.add(chunkGroup);
Add the chunk to the three.js scene so it appears in the world
const sharedGeometries = new Set([groundGeometry, roadGeometry, ...]);
Creates a set of all geometries that are shared (reused across many chunks) so they won't be disposed when a chunk is removed
chunks.forEach((chunkGroup, key) => {
Loop through all active chunks and remove those that are no longer visible
if (!activeChunkKeys.has(key)) {
If this chunk is not in the set of currently visible chunks, remove it
scene.remove(chunkGroup);
Remove the chunk group from the three.js scene so it stops rendering
if (object.geometry && !sharedGeometries.has(object.geometry)) {
Only dispose unique geometries (not shared ones) to avoid deleting resources still in use by other chunks
npcs = npcs.filter(npc => npc.chunkCoords && (npc.chunkCoords.x !== currentChunkX || npc.chunkCoords.z !== currentChunkZ));
Remove NPCs from the global array if they belong to the unloaded chunk

wrapPosition(position, worldSize)

wrapPosition() is a clever utility that makes the world feel infinite by wrapping positions. When the player drives too far in one direction, they wrap around to the opposite side—like a Pac-Man game but in 3D. This saves memory because the world doesn't need to extend infinitely; it just repeats.

function wrapPosition(position, worldSize) {
  position.x = (position.x + worldSize / 2) % worldSize - worldSize / 2;
  position.z = (position.z + worldSize / 2) % worldSize - worldSize / 2;
}
Line-by-line explanation (2 lines)
position.x = (position.x + worldSize / 2) % worldSize - worldSize / 2;
Uses modulo arithmetic to wrap the X position: when the car goes past +worldSize/2, it reappears at -worldSize/2. This makes the world feel seamless and infinite.
position.z = (position.z + worldSize / 2) % worldSize - worldSize / 2;
Same wrapping logic for the Z position

📦 Key Variables

keys object

Stores the state of every key on the keyboard (true if pressed, false if released) so animate() can check input without waiting for events

let keys = {};
carSpeed number

The car's current velocity in units per frame—positive is forward, negative is backward

let carSpeed = 0;
playerState string

Either 'driving' or 'onFoot' to track which mode the player is in and control which camera and movement system is active

let playerState = 'driving';
car object (THREE.Group)

The 3D model of the player's car, made of body, cabin, and wheel parts

let car;
player object (THREE.Group)

The 3D model of the player's on-foot character (a green cylinder)

let player;
gun object (THREE.Mesh)

The 3D gun model attached to the camera for first-person shooting

let gun;
npcs array

Array of all spawned NPC pedestrians—updated as chunks load and unload

let npcs = [];
copCars array

Array of all active police vehicles (cops, SWAT trucks, Humvees, tanks)—updated as chunks load, unload, and new vehicles spawn

let copCars = [];
stars number

The wanted level from 0 to 5 stars—increases through survival time, collision, or shooting, and decreases through inactivity

let stars = 0;
survivalTimer number

How long the player has survived in milliseconds—used to award stars at 60s, 120s, 180s, 240s, and 300s milestones

let survivalTimer = 0;
chunks Map (object)

Stores all active chunks by their string key (e.g., '5,3') so they can be loaded and unloaded efficiently

let chunks = new Map();
gameOver boolean

True when the game has ended (either from 7 seconds collision at 5 stars or from being hit by a tank projectile), which stops all game logic

let gameOver = false;
projectiles array

Array of all active tank projectiles—updated each frame to move projectiles and check collisions

let projectiles = [];
scene object (THREE.Scene)

The main three.js scene container that holds all 3D objects, lights, and the camera

let scene;
camera object (THREE.Camera)

The three.js camera that defines the player's viewpoint and is positioned to follow the car or player

let camera;
renderer object (THREE.WebGLRenderer)

The three.js renderer that draws the scene to the screen every frame

let renderer;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

PERFORMANCE animate() - police AI loop

The police AI recalculates direction and angle every frame for every cop car, even cops far away from the player. This is O(n) where n is the number of cops and can cause slowdowns with hundreds of vehicles.

💡 Add a distance check before updating police movement: `if (distanceToPlayer < 200)` so only nearby cops are actively chasing. Distant cops could patrol randomly or be culled entirely.

BUG animate() - on-foot state

When the player is on foot, police vehicles still spawn and consume memory, but they don't chase the player because the AI in animate() only uses `car.position`. The on-foot player can be hunted by cops they spawn, but cops in other chunks won't chase them.

💡 Modify the police AI to check `playerState === 'onFoot'` and use `player.position` instead of `car.position` when appropriate. This makes on-foot gameplay consistently challenging.

BUG createChunkContent() - NPC and police spawning

The `chunkCoords` property is set on NPC and police meshes, but when chunks are unloaded, the filter logic checks `npc.chunkCoords.x !== currentChunkX`. This uses `currentChunkX` (the player's current chunk), not the chunk that was just unloaded, so NPCs/cops might not be properly cleaned up from the arrays.

💡 Pass the unloading chunk's coordinates to the cleanup logic or track which chunk each object belongs to more explicitly to ensure correct removal.

STYLE Global variables - beginning of sketch

The sketch has 60+ global variables scattered throughout the file, making it hard to understand what state is being tracked. Variables are grouped by feature (car, police, game state) but could be more organized.

💡 Consider grouping globals into objects: `const carState = { speed: 0, rotation: 0, ... }` and `const gameState = { stars: 0, gameOver: false, ... }` to reduce the global namespace pollution and make relationships clearer.

FEATURE animate() - collision system

Collision only triggers when driving the car. When the player is on foot, they can walk through cops and buildings without consequence.

💡 Add collision detection for the on-foot player against police vehicles and buildings (using `player.position` instead of `car.position`). This would make on-foot gameplay more dangerous and engaging.

PERFORMANCE shoot() - hit effect cleanup

Each time the player shoots, two new temporary geometries and materials are created (hit effect and gun flash), used for 50-100ms, then disposed. Over hundreds of shots, this creates garbage collection pressure.

💡 Pre-create a pool of reusable hit effect and muzzle flash meshes at startup, then show/hide them instead of creating and destroying them repeatedly. This would eliminate garbage collection overhead.

🔄 Code Flow

Code flow showing preload, setup, initthreejs, createchunkcontent, animate, shoot, onkeydown, onmousedown, updatechunks, wrapposition

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> animate[animate] animate --> car-physics[car-physics] car-physics --> input-tracking[input-tracking] input-tracking --> state-toggle[state-toggle] state-toggle --> collision-detection[collision-detection] collision-detection --> police-ai[police-ai] police-ai --> projectile-physics[projectile-physics] projectile-physics --> survival-timer[survival-timer] survival-timer --> updatechunks[updatechunks] updatechunks --> chunk-load[chunk-load] chunk-load --> chunk-unload[chunk-unload] chunk-unload --> draw click setup href "#fn-setup" click draw href "#fn-draw" click animate href "#fn-animate" click car-physics href "#sub-car-physics" click input-tracking href "#sub-input-tracking" click state-toggle href "#sub-state-toggle" click collision-detection href "#sub-collision-detection" click police-ai href "#sub-police-ai" click projectile-physics href "#sub-projectile-physics" click survival-timer href "#sub-survival-timer" click updatechunks href "#fn-updatechunks" click chunk-load href "#sub-chunk-load" click chunk-unload href "#sub-chunk-unload" setup --> dom-setup[dom-setup] dom-setup --> three-init[three-init] three-init --> scene-camera-renderer[scene-camera-renderer] scene-camera-renderer --> lighting-setup[lighting-setup] lighting-setup --> geometry-materials[geometry-materials] geometry-materials --> game-objects[game-objects] game-objects --> input-listeners[input-listeners] input-listeners --> draw click dom-setup href "#sub-dom-setup" click three-init href "#sub-three-init" click scene-camera-renderer href "#sub-scene-camera-renderer" click lighting-setup href "#sub-lighting-setup" click geometry-materials href "#sub-geometry-materials" click game-objects href "#sub-game-objects" click input-listeners href "#sub-input-listeners" animate --> chunk-position[chunk-position] chunk-position --> createchunkcontent[createchunkcontent] createchunkcontent --> ground-roads[ground-roads] ground-roads --> building-loop[building-loop] building-loop --> npc-loop[npc-loop] npc-loop --> police-spawn[police-spawn] police-spawn --> draw click chunk-position href "#sub-chunk-position" click createchunkcontent href "#fn-createchunkcontent" click ground-roads href "#sub-ground-roads" click building-loop href "#sub-building-loop" click npc-loop href "#sub-npc-loop" click police-spawn href "#sub-police-spawn" shoot[shoot] --> raycast-setup[raycast-setup] raycast-setup --> hit-detection[hit-detection] hit-detection --> sound-playback[sound-playback] sound-playback --> visual-effects[visual-effects] visual-effects --> draw click shoot href "#fn-shoot" click raycast-setup href "#sub-raycast-setup" click hit-detection href "#sub-hit-detection" click sound-playback href "#sub-sound-playback" click visual-effects href "#sub-visual-effects" onkeydown[onkeydown] --> input-tracking click onkeydown href "#fn-onkeydown" onmousedown[onmousedown] --> shooting-cooldown[shooting-cooldown] shooting-cooldown --> input-tracking click onmousedown href "#fn-onmousedown"

❓ Frequently Asked Questions

What visual experience does the 'gta6 current state updated' sketch provide?

The sketch creates a dynamic 3D cityscape where users can drive vehicles, explore on foot, and navigate through a chaotic environment filled with cop cars and military vehicles.

How can users interact with the 'gta6 current state updated' sketch?

Users can control their character using the keyboard and mouse to drive, walk, and shoot, while avoiding an increasing number of pursuing law enforcement.

What creative coding concepts are demonstrated in the 'gta6 current state updated' sketch?

This sketch showcases procedural generation of a 3D world, real-time character control, and event-driven dynamics with an increasing difficulty level based on player actions.

Preview

gta6 current state updated - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of gta6 current state updated - Code flow showing preload, setup, initthreejs, createchunkcontent, animate, shoot, onkeydown, onmousedown, updatechunks, wrapposition
Code Flow Diagram