gta 6 current state

This is a GTA-inspired 3D game prototype built with three.js where you drive a car, evade cop cars, and switch to on-foot mode to shoot NPCs. The game escalates with a five-star wanted level system that spawns increasingly dangerous police units, ending when you're caught or hit by tank fire.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the car faster — Increase carMaxSpeed to allow higher speeds—the car will accelerate and top out at a higher velocity.
  2. Make cop cars easier to evade — Reduce the number of cops spawned per chunk, giving you more breathing room.
  3. Get 5 stars faster — Lower the time thresholds so each star level comes up sooner—test the tank police without waiting 5 minutes.
  4. Make tanks less dangerous — Lower the tank's shooting range so they must be very close to fire—easier to escape.
  5. Create a denser city — Add more buildings and NPCs to each chunk for a busier environment.
  6. Speed up camera rotation sensitivity — Increase the car's turning speed so steering feels more responsive.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a playable GTA-inspired 3D world using three.js, complete with a drivable car, AI-controlled cop vehicles, a star-based wanted level system, and the ability to exit your car and shoot NPCs. What makes it visually striking is the combination of responsive controls, real-time chunk-based world generation, dynamic lighting and shadows, and escalating police opposition that grows more threatening as your wanted level increases. The core techniques powering this are three.js 3D rendering, raycasting for shooting detection, AI pathfinding with rotation smoothing, and a spatial chunking system that manages the infinite repeating world.

The code is organized into several layers: initialization functions (setup, initThreeJS) that configure the three.js scene and world, model creation functions (createCar, createPlayer, createCopCarModel, etc.) that build 3D objects from reusable geometries and materials, game logic functions (animate, updateChunks, onKeyDown) that handle movement and interactions, and AI functions (cop car chasing, tank shooting) that give the police units intelligent behavior. By studying this sketch, you will learn how to structure a 3D game loop, manage performance through geometry/material pooling, implement a chunk system for infinite worlds, and coordinate multiple AI entities with the player.

⚙️ How It Works

  1. When the sketch loads, setup() initializes p5.js (but hides its canvas), then initThreeJS() creates the three.js scene with a camera, renderer, lighting, and reusable 3D geometries and materials—this happens once for performance. The car, player, and initial chunks of the world are created and positioned at the origin.
  2. Every frame, the animate() loop updates the game state: car or player movement is calculated based on keyboard input, the camera follows the active character, and chunks are generated or removed as the player moves through the world.
  3. When driving (playerState === 'driving'), WASD keys control the car's speed and rotation. The car accelerates smoothly with carAcceleration, decelerates with carDeceleration, and has a maximum speed cap. The camera tracks behind and above the car, and collision detection checks if cop cars touch the player for 7 continuous seconds (game over).
  4. Cop cars use simple AI: they calculate the direction to the player, rotate smoothly toward that angle using atan2 for correct bearing, and chase forward. SWAT trucks at 2+ stars move faster, and tank police at 5 stars can rotate turrets and shoot projectiles that end the game on impact.
  5. When pressing 'e' to exit the car (playerState === 'onFoot'), the player switches to a first-person shooter view where the left mouse button shoots. Raycasting detects NPC hits, removes them from the scene, and plays a synth gunshot sound via p5.Oscillator.
  6. The survival timer increments every frame, and when it passes thresholds (1/2/3/4/5 minutes), the star level increases, spawning more dangerous police units. The world wraps at worldSize edges, making it feel infinite while reusing the same chunks. When the player collides with a cop car for 7 seconds or is hit by a tank projectile, gameOver becomes true and the loop stops updating game logic.

🎓 Concepts You'll Learn

three.js 3D scene and rendering3D camera following and first-person viewObject transformation (position, rotation, quaternion)Raycasting for intersection detectionAI pathfinding with angle smoothingChunk-based infinite world generationGeometry and material reuse for performanceGame state machine (driving vs on-foot)Collision detection and game-over conditions

📝 Code Breakdown

preload()

preload() is a p5.js function that runs before setup(), making it the right place to initialize sounds and load resources. Using a synthetic p5.Oscillator guarantees audio playback without relying on external URLs.

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 oscillator with a triangle wave shape—this generates the gunshot sound without needing an external audio file.
gunshotSound.freq(3000);
Sets the oscillator's frequency to 3000 Hz, a high pitch that sounds sharp and gun-like.
gunshotSound.amp(0.5);
Sets the initial amplitude (volume) to 0.5 so the sound doesn't blast too loudly.
gunshotSound.start();
Starts the oscillator running (though we immediately set its volume to 0, so it's silent until a shot is fired).
gunshotSound.amp(0, 0);
Sets amplitude to 0 with a fade time of 0, ensuring the oscillator is silent after starting.

setup()

setup() is called once when the sketch starts. It initializes DOM elements, hides p5.js's default canvas, and hands control to three.js. The mouse coordinates are set to (0, 0) which represents the center of the screen for raycasting.

function setup() {
  createCanvas(windowWidth, windowHeight);
  noCanvas();
  crosshairElement = document.getElementById('crosshair');
  crosshairElement.style.display = 'none';
  starsElement = document.getElementById('starsDisplay');
  timerElement = document.getElementById('timerDisplay');
  copMeterElement = document.getElementById('copMeterDisplay');
  updateStarDisplay();
  timerElement.innerHTML = `Time: ${nf(survivalTimer / 1000, 1, 0)}s`;
  copMeterElement.innerHTML = `Cops: ${copCars.length}`;
  initThreeJS();
  animate();
  mouse.x = 0;
  mouse.y = 0;
}
Line-by-line explanation (5 lines)
createCanvas(windowWidth, windowHeight);
Creates a p5.js canvas at full window size, but we'll immediately hide it because three.js will render instead.
noCanvas();
Hides the p5.js canvas so only the three.js renderer is visible.
crosshairElement = document.getElementById('crosshair');
Grabs the HTML crosshair element (a div styled in CSS) so we can show/hide it when switching to on-foot mode.
initThreeJS();
Calls the main three.js initialization function that sets up the scene, camera, renderer, lighting, and initial world chunks.
animate();
Starts the three.js animation loop, which runs requestAnimationFrame continuously.

initThreeJS()

initThreeJS() is where the entire 3D rendering pipeline is set up. It creates the scene, camera, and renderer, configures lighting and shadows, and creates all geometries and materials once to be reused later. This function runs once during initialization, not every frame, making it the right place for expensive setup operations.

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.near = 0.5;
  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);
  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);
  swatTruckBodyGeometry = new THREE.BoxGeometry(4, 1.5, 2);
  swatTruckBodyMaterial = new THREE.MeshLambertMaterial({ color: 0x222222 });
  swatTruckCabinGeometry = new THREE.BoxGeometry(3, 1.2, 1.8);
  swatTruckCabinMaterial = new THREE.MeshLambertMaterial({ color: 0x111111 });
  swatTruckWheelGeometry = new THREE.CylinderGeometry(0.7, 0.7, 0.6, 16);
  swatTruckWheelMaterial = new THREE.MeshLambertMaterial({ color: 0x000000 });
  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 (9 lines)

🔧 Subcomponents:

initialization Scene, Camera, Renderer Setup scene = new THREE.Scene(); scene.background = new THREE.Color(0x87ceeb);

Creates the three.js scene container with a sky blue background color.

initialization Lighting Configuration const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1);

Adds soft ambient light for overall brightness and a directional light with shadows for depth.

initialization Reusable Geometry and Material Creation groundGeometry = new THREE.PlaneGeometry(chunkSize, chunkSize); groundMaterial = new THREE.MeshLambertMaterial({ color: 0x33aa33 });

Creates shared geometries and materials once, then reuses them for all instances (car, cop cars, buildings, etc.) to save memory and improve performance.

scene = new THREE.Scene();
Creates the three.js scene—the container that holds all 3D objects, lights, and cameras.
scene.background = new THREE.Color(0x87ceeb);
Sets the background color to sky blue (0x87ceeb) so the world looks like an outdoor environment.
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
Creates a perspective camera with a 75-degree field of view that matches the window's aspect ratio. Objects between 0.1 and 1000 units away are visible.
renderer = new THREE.WebGLRenderer({ antialias: true });
Creates a WebGL renderer with antialiasing enabled for smooth edges on 3D objects.
renderer.setPixelRatio(window.devicePixelRatio);
Adjusts the render resolution for retina/high-DPI displays so the image remains sharp.
directionalLight.castShadow = true;
Enables shadow casting for the directional light, making the scene look more realistic with realistic shadows on the ground.
groundGeometry = new THREE.PlaneGeometry(chunkSize, chunkSize);
Creates a single plane geometry of chunkSize×chunkSize that will be reused for all ground tiles to save memory.
groundMaterial = new THREE.MeshLambertMaterial({ color: 0x33aa33 });
Creates a single green material that will be reused for all ground meshes, avoiding redundant material instances.
createCar(); createPlayer(); createGun();
Calls the model creation functions to build the car, on-foot player, and gun in the scene.

createChunkContent()

createChunkContent() generates the repeating world tile that loads as you move. It creates ground, roads, buildings, NPCs, and police cars within a single 200×200-unit chunk. Each chunk is stored in a global map and removed when you move far away, keeping memory usage constant even in an infinite world.

🔬 This loop creates random building heights between 5 and 25 units. What happens if you change it to const height = 50 + i * 2 to make buildings progressively taller, or const height = 10 to make all buildings the same height?

  for (let i = 0; i < numBuildingsPerChunk; i++) {
    const height = Math.random() * 20 + 5;
function createChunkContent(chunkX, chunkZ) {
  const chunkGroup = new THREE.Group();
  const offsetX = chunkX * chunkSize;
  const offsetZ = chunkZ * chunkSize;
  const halfChunk = chunkSize / 2;
  const spawnRadius = 2;
  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;
  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 };
  }
  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 };
    }
  }
  if (stars >= 2 && stars < 5 &&
      Math.abs(chunkX - currentChunkX) <= spawnRadius &&
      Math.abs(chunkZ - currentChunkZ) <= spawnRadius) {
    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);
      swatTruck.isSwatTruck = true;
      chunkGroup.add(swatTruck);
      copCars.push(swatTruck);
      swatTruck.chunkCoords = { x: chunkX, z: chunkZ };
    }
  }
  return chunkGroup;
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

loop Ground and Road Creation const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2;

Creates a flat ground plane rotated 90 degrees so it lies horizontal, then adds three parallel road meshes across the chunk.

for-loop Building Generation Loop for (let i = 0; i < numBuildingsPerChunk; i++) { const height = Math.random() * 20 + 5;

Loops 15 times to create randomly-sized and randomly-positioned buildings, cloning the base car body geometry and scaling it to different heights.

for-loop NPC Spawn Loop for (let i = 0; i < numNpcsPerChunk; i++) { const npcHeight = 1.8;

Loops 8 times to create NPC characters (green cylinders) scattered around the chunk, avoiding main roads.

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

Conditionally spawns regular cop cars only in nearby chunks and only when wanted level is below 5 stars.

const offsetX = chunkX * chunkSize;
Calculates the world-space X position of this chunk by multiplying its chunk index by the chunk size.
const halfChunk = chunkSize / 2;
Pre-calculates half the chunk size, used to center random positions around the chunk's middle.
ground.rotation.x = -Math.PI / 2;
Rotates the ground plane 90 degrees around the X axis so it becomes horizontal (normally planes are vertical).
ground.position.y = -0.5;
Moves the ground slightly below Y=0 so it sits beneath the buildings and roads.
const height = Math.random() * 20 + 5;
Generates a random building height between 5 and 25 units, creating visual variety in the skyline.
const building = new THREE.Mesh(carBodyGeometry.clone(), buildingMaterial);
Reuses the car body box geometry by cloning it, then reapplies the shared building material. Cloning is necessary because we'll scale each building differently.
} while (x > -5 && x < 5);
This do-while loop repeats the random position generation until it finds a spot outside the main road (between -5 and +5 on the X axis).
npcs.push(npc);
Adds the NPC to the global npcs array so it can be detected by raycasting when the player shoots.
npc.chunkCoords = { x: chunkX, z: chunkZ };
Stores which chunk this NPC belongs to, allowing the system to remove it when that chunk is unloaded.

createCar()

createCar() builds the player's drivable car from reusable geometries and materials. It uses a THREE.Group to organize the body, cabin, and four wheels as a single object that can be moved and rotated together. This structure makes it easy to control the car's position and rotation in the animate loop.

function createCar() {
  car = new THREE.Group();
  const carBody = new THREE.Mesh(carBodyGeometry, carBodyMaterial);
  carBody.castShadow = true;
  carBody.receiveShadow = true;
  car.add(carBody);
  const carCabin = new THREE.Mesh(carCabinGeometry, carCabinMaterial);
  carCabin.position.set(0, 0.9, 0);
  carCabin.castShadow = true;
  carCabin.receiveShadow = true;
  car.add(carCabin);
  const wheelPositions = [
    { x: -1, y: -0.5, z: 0.8 },
    { x: 1, y: -0.5, z: 0.8 },
    { x: -1, y: -0.5, z: -0.8 },
    { x: 1, y: -0.5, z: -0.8 },
  ];
  wheelPositions.forEach(pos => {
    const wheel = new THREE.Mesh(carWheelGeometry, carWheelMaterial);
    wheel.position.set(pos.x, pos.y, pos.z);
    wheel.rotation.x = Math.PI / 2;
    wheel.castShadow = true;
    wheel.receiveShadow = true;
    car.add(wheel);
  });
  scene.add(car);
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

for-loop Wheel Creation Loop wheelPositions.forEach(pos => { const wheel = new THREE.Mesh(carWheelGeometry, carWheelMaterial);

Loops through four wheel positions and creates a wheel mesh at each, then rotates it 90 degrees to stand vertical.

car = new THREE.Group();
Creates an empty container (Group) that will hold the car body, cabin, and wheels together as one unit.
const carBody = new THREE.Mesh(carBodyGeometry, carBodyMaterial);
Creates a red box mesh (3×1×1.5 units) using the shared car body geometry and material.
carBody.castShadow = true;
Enables this mesh to cast a shadow on the ground, making the car look like it's sitting on the surface rather than floating.
carCabin.position.set(0, 0.9, 0);
Positions the cabin 0.9 units above the body so it sits on top like a real car.
wheel.rotation.x = Math.PI / 2;
Rotates the wheel 90 degrees around the X axis so the circular face points outward (the cylinder stands vertical).
car.add(carBody);
Adds the body mesh to the car group so they move together as one object.
scene.add(car);
Adds the entire car group to the scene so it is visible and can be rendered.

createCopCarModel()

createCopCarModel() builds the AI-controlled police car. It reuses shared geometries and materials where possible (body, cabin, wheels) but creates unique ones for the white stripe and siren lights, since these are specific to police cars. The function returns the completed cop car group, which gets positioned randomly in chunks and added to the global copCars array.

function createCopCarModel() {
  const copCar = new THREE.Group();
  const carBody = new THREE.Mesh(copCarBodyGeometry, copCarBodyMaterial);
  carBody.castShadow = true;
  carBody.receiveShadow = true;
  copCar.add(carBody);
  const whiteStripeGeometry = new THREE.BoxGeometry(3.1, 0.5, 1.6);
  const whiteStripeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
  const whiteStripe = new THREE.Mesh(whiteStripeGeometry, whiteStripeMaterial);
  whiteStripe.position.set(0, 0, 0);
  whiteStripe.castShadow = true;
  whiteStripe.receiveShadow = true;
  copCar.add(whiteStripe);
  const carCabin = new THREE.Mesh(copCarCabinGeometry, copCarCabinMaterial);
  carCabin.position.set(0, 0.9, 0);
  carCabin.castShadow = true;
  carCabin.receiveShadow = true;
  copCar.add(carCabin);
  const wheelPositions = [
    { x: -1, y: -0.5, z: 0.8 },
    { x: 1, y: -0.5, z: 0.8 },
    { x: -1, y: -0.5, z: -0.8 },
    { x: 1, y: -0.5, z: -0.8 },
  ];
  wheelPositions.forEach(pos => {
    const wheel = new THREE.Mesh(copCarWheelGeometry, copCarWheelMaterial);
    wheel.position.set(pos.x, pos.y, pos.z);
    wheel.rotation.x = Math.PI / 2;
    wheel.castShadow = true;
    wheel.receiveShadow = true;
    copCar.add(wheel);
  });
  const redSiren = new THREE.Mesh(sirenLightGeometry, redSirenMaterial);
  redSiren.position.set(-0.5, 1.4, 0);
  redSiren.rotation.x = Math.PI / 2;
  copCar.add(redSiren);
  const blueSiren = new THREE.Mesh(sirenLightGeometry, blueSirenMaterial);
  blueSiren.position.set(0.5, 1.4, 0);
  blueSiren.rotation.x = Math.PI / 2;
  copCar.add(blueSiren);
  copCar.position.y = 0;
  return copCar;
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

initialization Police Livery Stripe const whiteStripeGeometry = new THREE.BoxGeometry(3.1, 0.5, 1.6); const whiteStripeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });

Creates a unique white stripe overlay to distinguish the police car visually from regular cars.

initialization Red and Blue Siren Lights const redSiren = new THREE.Mesh(sirenLightGeometry, redSirenMaterial); redSiren.position.set(-0.5, 1.4, 0);

Adds red and blue cylinder meshes on top of the car to represent flashing siren lights.

const copCar = new THREE.Group();
Creates a container group for the cop car's body, cabin, wheels, and siren lights.
const whiteStripeGeometry = new THREE.BoxGeometry(3.1, 0.5, 1.6);
Creates a unique white stripe geometry (not shared) that overlays on the blue body to create the classic police car livery stripe.
const whiteStripeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
Creates a unique white material for the stripe (not shared because it's specific to this function and relatively lightweight).
const redSiren = new THREE.Mesh(sirenLightGeometry, redSirenMaterial);
Creates a red cylinder on the left side of the car roof to represent a siren light.
redSiren.position.set(-0.5, 1.4, 0);
Positions the red siren at (-0.5, 1.4, 0)—to the left of center, high up on the car roof.

createSwatTruckModel()

createSwatTruckModel() creates the SWAT truck enemy that spawns at 2+ stars. Unlike regular cops, SWAT trucks are larger, darker, and carry more visual weight to signal increased difficulty. They use the same chase AI as regular cops but with different speed parameters.

function createSwatTruckModel() {
  const swatTruck = new THREE.Group();
  swatTruck.speed = swatTruckSpeed;
  swatTruck.turnSpeed = swatTruckTurnSpeed;
  const truckBody = new THREE.Mesh(swatTruckBodyGeometry, swatTruckBodyMaterial);
  truckBody.castShadow = true;
  truckBody.receiveShadow = true;
  swatTruck.add(truckBody);
  const truckCabin = new THREE.Mesh(swatTruckCabinGeometry, swatTruckCabinMaterial);
  truckCabin.position.set(0, 1.4, 0);
  truckCabin.castShadow = true;
  truckCabin.receiveShadow = true;
  swatTruck.add(truckCabin);
  const wheelPositions = [
    { x: -1.5, y: -0.75, z: 1.2 },
    { x: 1.5, y: -0.75, z: 1.2 },
    { x: -1.5, y: -0.75, z: -1.2 },
    { x: 1.5, y: -0.75, z: -1.2 },
  ];
  wheelPositions.forEach(pos => {
    const wheel = new THREE.Mesh(swatTruckWheelGeometry, swatTruckWheelMaterial);
    wheel.position.set(pos.x, pos.y, pos.z);
    wheel.rotation.x = Math.PI / 2;
    wheel.castShadow = true;
    wheel.receiveShadow = true;
    swatTruck.add(wheel);
  });
  swatTruck.position.y = 0;
  return swatTruck;
}
Line-by-line explanation (4 lines)
swatTruck.speed = swatTruckSpeed;
Stores the SWAT truck's speed (3 units per frame) as a property on the truck object, allowing the animate() function to use it when the truck is chasing the player.
const truckBody = new THREE.Mesh(swatTruckBodyGeometry, swatTruckBodyMaterial);
Creates a large dark grey truck body (4×1.5×2 units) that is bigger and more imposing than regular police cars.
truckCabin.position.set(0, 1.4, 0);
Positions the cabin higher (1.4 units) than regular cars because SWAT trucks are taller.
{ x: -1.5, y: -0.75, z: 1.2 },
SWAT truck wheels are positioned wider (±1.5 instead of ±1) and lower (-0.75 instead of -0.5) because the truck is larger and has bigger wheels.

createTankCopCarModel()

createTankCopCarModel() creates the tank enemy that spawns at 5 stars. Tanks are the most dangerous opponent: they move at speed 8 (faster than other cops), have a rotating turret and cannon, and shoot projectiles that instantly end the game. The parent-child relationship between turret and cannon is key: it allows the turret to aim while the cannon moves with it automatically.

function createTankCopCarModel() {
  const tank = new THREE.Group();
  tank.speed = tankCopCarSpeed;
  tank.turnSpeed = tankCopCarTurnSpeed;
  tank.shootingTimer = 0;
  const tankBody = new THREE.Mesh(tankBodyGeometry, tankBodyMaterial);
  tankBody.castShadow = true;
  tankBody.receiveShadow = true;
  tank.add(tankBody);
  const tankTurret = new THREE.Mesh(turretGeometry, turretMaterial);
  tankTurret.position.set(0, 1.5, 0);
  tankTurret.rotation.x = Math.PI / 2;
  tankTurret.castShadow = true;
  tankTurret.receiveShadow = true;
  tank.add(tankTurret);
  const tankCannon = new THREE.Mesh(cannonGeometry, cannonMaterial);
  tankCannon.position.set(0, 0, -1.5);
  tankCannon.rotation.x = Math.PI / 2;
  tankCannon.castShadow = true;
  tankCannon.receiveShadow = true;
  tankTurret.add(tankCannon);
  const wheelPositions = [
    { x: -1.7, y: -0.5, z: 1.2 },
    { x: 1.7, y: -0.5, z: 1.2 },
    { x: -1.7, y: -0.5, z: -1.2 },
    { x: 1.7, y: -0.5, z: -1.2 },
  ];
  wheelPositions.forEach(pos => {
    const wheel = new THREE.Mesh(tankWheelGeometry, tankWheelMaterial);
    wheel.position.set(pos.x, pos.y, pos.z);
    wheel.castShadow = true;
    wheel.receiveShadow = true;
    tank.add(wheel);
  });
  tank.position.y = 0;
  return tank;
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

initialization Turret and Cannon Hierarchy tank.add(tankTurret); const tankCannon = new THREE.Mesh(cannonGeometry, cannonMaterial); tankCannon.position.set(0, 0, -1.5); tankTurret.add(tankCannon);

Creates a parent-child relationship where the cannon is attached to the turret, so when the turret rotates to aim, the cannon rotates with it.

tank.speed = tankCopCarSpeed;
Stores the tank's speed (8 units per frame) so animate() can use it when the tank chases the player.
tank.shootingTimer = 0;
Initializes a timer to 0 for the tank's shooting cooldown, tracking when the tank last fired so it doesn't shoot too frequently.
const tankTurret = new THREE.Mesh(turretGeometry, turretMaterial);
Creates a grey cylinder that sits on top of the tank body and can rotate independently to aim at the player.
tankTurret.add(tankCannon);
Adds the cannon as a child of the turret. This creates a parent-child hierarchy so rotating the turret also rotates the cannon.
const wheelPositions = [ { x: -1.7, y: -0.5, z: 1.2 },
Tank wheels are positioned wider (±1.7) than regular car wheels because the tank is wider and more imposing.

createPlayer()

createPlayer() builds the on-foot player character. It's much simpler than createCar() because the player is just a single green cylinder with no wheels or cabin. The player is initially hidden (player.visible = false) until the player presses 'e' to exit the car.

function createPlayer() {
  player = new THREE.Group();
  const playerBody = new THREE.Mesh(playerBodyGeometry, playerBodyMaterial);
  playerBody.position.y = 0.9;
  playerBody.castShadow = true;
  playerBody.receiveShadow = true;
  player.add(playerBody);
  scene.add(player);
}
Line-by-line explanation (3 lines)
player = new THREE.Group();
Creates an empty container for the player's body and any attachments (like the gun).
const playerBody = new THREE.Mesh(playerBodyGeometry, playerBodyMaterial);
Creates a green cylinder (0.5 radius, 1.8 height) representing the player's on-foot character.
playerBody.position.y = 0.9;
Positions the cylinder's center at Y=0.9, which places the bottom of the 1.8-unit-tall cylinder at ground level (0.9 - 0.9 = 0).

createGun()

createGun() creates the gun mesh and attaches it to the camera as a child object. By adding it to the camera, the gun stays in the same position relative to the player's view at all times—a classic first-person game technique. The gun is initially invisible and shown only when playerState === 'onFoot'.

function createGun() {
  gun = new THREE.Mesh(gunGeometry, gunMaterial);
  gun.position.set(0.5, -0.3, -1);
  gun.rotation.x = Math.PI / 2;
  gun.castShadow = true;
  gun.receiveShadow = true;
  gun.visible = false;
  camera.add(gun);
}
Line-by-line explanation (5 lines)
gun = new THREE.Mesh(gunGeometry, gunMaterial);
Creates a dark grey cylinder (0.1–0.2 radius, 0.8 height) that represents the player's gun.
gun.position.set(0.5, -0.3, -1);
Positions the gun at (0.5, -0.3, -1) relative to the camera, placing it to the right, slightly below the center, and in front of the view.
gun.rotation.x = Math.PI / 2;
Rotates the gun 90 degrees so its barrel points forward (in the camera's forward direction).
camera.add(gun);
Adds the gun as a child of the camera. This makes the gun move with the camera in first-person view—you always see it in the same spot relative to your view.
gun.visible = false;
Initially hides the gun because the player starts in driving mode; it becomes visible when switching to on-foot mode.

createProjectile()

createProjectile() builds a red sphere that represents a tank's fired projectile. Each projectile stores its starting position and a velocity vector (direction). The animate() loop moves projectiles each frame and checks for collisions with the player's car or world boundaries.

function createProjectile(startPosition, direction) {
    const projectileGeometry = new THREE.SphereGeometry(projectileRadius, 8, 8);
    const projectileMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
    const projectile = new THREE.Mesh(projectileGeometry, projectileMaterial);
    projectile.position.copy(startPosition);
    projectile.velocity = direction.clone();
    return projectile;
}
Line-by-line explanation (4 lines)
const projectileGeometry = new THREE.SphereGeometry(projectileRadius, 8, 8);
Creates a red sphere geometry (radius 0.5, 8 width segments, 8 height segments) to represent a tank projectile.
const projectileMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
Creates a red material using MeshBasicMaterial (no lighting), making the projectile appear as a bright red ball.
projectile.velocity = direction.clone();
Stores the projectile's movement direction as a velocity property, allowing the animate() loop to move it each frame.
return projectile;
Returns the completed projectile mesh so animate() can add it to the scene and the projectiles array.

onKeyDown()

onKeyDown() is called every time a key is pressed. It stores which key is held in the keys object (used by animate()), and detects when 'e' is pressed to toggle between driving and on-foot mode. When switching modes, the player and car swap visibility and position.

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 (8 lines)

🔧 Subcomponents:

conditional Exit/Enter Car Logic if (event.key.toLowerCase() === 'e') {

When 'e' is pressed, toggle between driving and on-foot mode, swap visibility of car and player, and show/hide the crosshair.

if (gameOver) return;
Prevents any input from registering if the game is already over, stopping further actions.
keys[event.key.toLowerCase()] = true;
Stores the pressed key in the keys object so the animate() loop can check which keys are held down each frame.
if (event.key.toLowerCase() === 'e') {
Detects when the 'e' key specifically is pressed, triggering the exit-car/on-foot toggle.
playerState = 'onFoot';
Changes the game state to on-foot, which tells animate() to use on-foot controls instead of car controls.
player.position.copy(car.position);
Moves the player to the car's current position so they don't teleport when exiting.
player.rotation.copy(car.rotation);
Copies the car's rotation to the player so they face the same direction they were driving.
car.visible = false;
Hides the car mesh from rendering, leaving only the player visible.
gun.visible = true;
Shows the gun mesh, which is now visible in first-person view as the player aims.

onKeyUp()

onKeyUp() is called every time a key is released. It updates the keys object to reflect that the key is no longer pressed, allowing the animate() loop to stop applying movement forces tied to that key.

function onKeyUp(event) {
  keys[event.key.toLowerCase()] = false;
}
Line-by-line explanation (1 lines)
keys[event.key.toLowerCase()] = false;
When a key is released, marks it as no longer pressed in the keys object. The animate() loop checks this object to know which keys are held down.

onMouseDown()

onMouseDown() detects left mouse clicks. It only allows shooting when the player is on foot and checks a cooldown timer to prevent rapid-fire shots. The cooldown is set to 300 milliseconds, meaning the player can fire at most 3–4 times per second.

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

🔧 Subcomponents:

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

Prevents spam shooting by only allowing shots when the cooldown (300 milliseconds) has elapsed since the last shot.

if (gameOver) return;
Prevents shooting after the game is over.
if (playerState === 'onFoot' && event.button === 0) {
Only allows shooting when on foot (playerState === 'onFoot') and when the left mouse button (button 0) is clicked.
const currentTime = Date.now();
Gets the current time in milliseconds since the epoch (January 1, 1970).
if (currentTime - lastShotTime > shootingCooldown) {
Checks if enough time (300 milliseconds) has passed since the last shot. If yes, allows a new shot.
lastShotTime = currentTime;
Records the current time as the last shot time, starting a new 300-millisecond cooldown.

shoot()

shoot() is the core of the on-foot shooting mechanic. It uses raycasting to detect what the player is aiming at, plays a synthesized gunshot sound, removes the hit NPC, and creates temporary visual effects (hit flash and muzzle flash). All temporary geometries and materials are disposed of after they disappear to prevent memory leaks.

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 intersects = raycaster.intersectObjects(npcs);
  if (intersects.length > 0) {
    const hitNpc = intersects[0].object;
    console.log('Hit NPC:', hitNpc);
    scene.remove(hitNpc);
    npcs = npcs.filter(npc => npc !== hitNpc);
    hitNpc.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 (12 lines)

🔧 Subcomponents:

calculation Raycasting Setup raycaster.setFromCamera(mouse, camera);

Creates a ray from the camera through the center of the screen to detect what the player is aiming at.

conditional NPC Hit Detection const intersects = raycaster.intersectObjects(npcs); if (intersects.length > 0) {

Checks if the ray hit any NPCs, and if yes, removes the first hit NPC and creates a visual hit effect.

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

Creates a temporary orange cylinder in front of the gun to simulate a muzzle flash, which disappears after 50 milliseconds.

raycaster.setFromCamera(mouse, camera);
Creates an invisible ray starting from the camera and pointing through the mouse's screen position. Since mouse.x and mouse.y are (0, 0), the ray points through the center of the screen.
if (gunshotSound instanceof p5.Oscillator) {
Checks if the gunshot sound is a p5.Oscillator (synthetic) or a loaded audio file, then plays it accordingly.
gunshotSound.amp(0, 0.01);
Sets the oscillator's amplitude to 0 over 10 milliseconds (quick fade-out from any previous sound).
gunshotSound.amp(0.5, 0.05);
Ramps the amplitude up to 0.5 over 50 milliseconds, creating an attack phase for the sound.
gunshotSound.amp(0.2, 0.05);
Reduces the amplitude to 0.2 over 50 milliseconds, creating a decay phase.
gunshotSound.amp(0, 0.2);
Fades the amplitude to 0 over 200 milliseconds, creating a release phase that lets the sound tail off naturally.
const intersects = raycaster.intersectObjects(npcs);
Checks all NPCs to see if the ray intersects any of them, returning an array of hit objects sorted by distance.
const hitNpc = intersects[0].object;
Gets the closest (first) hit NPC—intersects[0] is the nearest intersection.
npcs = npcs.filter(npc => npc !== hitNpc);
Removes the hit NPC from the global npcs array by filtering out the one that matches hitNpc.
hitNpc.geometry.dispose();
Deallocates the hit NPC's cloned geometry from GPU memory to prevent memory leaks.
hitEffect.position.copy(intersects[0].point);
Positions the hit effect at the exact point where the ray hit the NPC.
setTimeout(() => { scene.remove(hitEffect); ... }, 100);
Removes the hit effect mesh after 100 milliseconds, cleaning up the temporary visual effect.

updateChunks()

updateChunks() is the engine of the infinite world system. It calculates which chunk grid cell the player is in, loads all chunks in a 3×3 radius around them, and unloads chunks that are far away. By tracking shared geometries and materials, it prevents disposing GPU resources that are used elsewhere, preventing crashes. This technique allows the game world to feel infinite while keeping memory usage constant.

🔬 This nested loop generates a 3×3 grid of chunks (with visibleRadius = 1) centered on the player. What happens if you change visibleRadius to 2 in the loop? This will create a 5×5 grid. Test it by changing -visibleRadius to -2 and +visibleRadius to +2 in BOTH loops. What happens to performance?

  for (let cx = currentChunkX - visibleRadius; cx <= currentChunkX + visibleRadius; cx++) {
      for (let cz = currentChunkZ - visibleRadius; cz <= currentChunkZ + visibleRadius; cz++) {
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,
        swatTruckBodyGeometry, swatTruckCabinGeometry, swatTruckWheelGeometry
    ]);
    const sharedMaterials = new Set([
        groundMaterial, roadMaterial, buildingMaterial, npcMaterial,
        redSirenMaterial, blueSirenMaterial,
        carBodyMaterial, carCabinMaterial, carWheelMaterial,
        copCarBodyMaterial, copCarCabinMaterial, copCarWheelMaterial,
        playerBodyMaterial, gunMaterial,
        tankBodyMaterial, turretMaterial, cannonMaterial, tankWheelMaterial,
        swatTruckBodyMaterial, swatTruckCabinMaterial, swatTruckWheelMaterial
    ]);
    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 !== chunkX || npc.chunkCoords.z !== chunkZ));
        copCars = copCars.filter(copCar => copCar.chunkCoords && (copCar.chunkCoords.x !== chunkX || copCar.chunkCoords.z !== chunkZ));
        chunks.delete(key);
      }
    });
    console.timeEnd('Chunk Update');
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

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

Divides the car's world position by the chunk size to determine which chunk grid cell it's in.

for-loop Active Chunk Generation Loop for (let cx = currentChunkX - visibleRadius; cx <= currentChunkX + visibleRadius; cx++) {

Loops through a 3×3 grid of chunks centered on the player (or 5×5 if visibleRadius = 2) and generates any chunks that don't exist yet.

for-loop Old Chunk Cleanup chunks.forEach((chunkGroup, key) => { if (!activeChunkKeys.has(key)) {

Iterates all stored chunks and removes those that are no longer in the active set, disposing of their geometries and materials to free memory.

const newChunkX = Math.floor(car.position.x / chunkSize);
Calculates which chunk grid X cell the car is in by dividing its world X position by the chunk size (200) and rounding down.
if (newChunkX !== currentChunkX || newChunkZ !== currentChunkZ) {
Only updates chunks if the car has moved to a new chunk grid cell, avoiding unnecessary work every frame.
currentChunkX = newChunkX;
Updates the global current chunk position to reflect the player's new location.
const activeChunkKeys = new Set();
Creates a Set to track which chunks should be active (loaded) around the player.
for (let cx = currentChunkX - visibleRadius; cx <= currentChunkX + visibleRadius; cx++) {
Loops from (currentChunkX - 1) to (currentChunkX + 1), generating a 3×3 grid of chunks centered on the player (assuming visibleRadius = 1).
const key = `${cx},${cz}`;
Creates a string key like '0,5' to uniquely identify each chunk by its grid coordinates.
if (!chunks.has(key)) {
Checks if the chunk already exists; if not, creates it by calling createChunkContent().
const sharedGeometries = new Set([...]);
Creates a Set of all geometries that are shared across multiple objects. These will NOT be disposed when chunks are removed.
if (!sharedGeometries.has(object.geometry)) {
Before disposing a geometry, checks if it's in the shared set. Only non-shared (cloned) geometries are disposed.
npcs = npcs.filter(npc => npc.chunkCoords && (npc.chunkCoords.x !== chunkX || npc.chunkCoords.z !== chunkZ));
Removes NPCs from the global array if they belong to a chunk that is being unloaded.

wrapPosition()

wrapPosition() wraps the player and cop cars around the world edges using modulo arithmetic. If an object goes beyond worldSize / 2 units in any direction, it wraps to the opposite side. This keeps positions in a manageable range and makes the world feel infinite.

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 (1 lines)
position.x = (position.x + worldSize / 2) % worldSize - worldSize / 2;
Uses modulo arithmetic to wrap the position around a finite world space. When the car moves past one edge, it reappears on the opposite edge, creating a seamless infinite-feeling world.

updateStarDisplay()

updateStarDisplay() is a simple helper function that updates the on-screen UI to show the current wanted level. It's called whenever the stars variable increases.

function updateStarDisplay() {
    starsElement.innerHTML = `Stars: ${stars}`;
}
Line-by-line explanation (1 lines)
starsElement.innerHTML = `Stars: ${stars}`;
Updates the HTML content of the stars display element (the div with id 'starsDisplay') to show the current wanted level as a number.

animate()

animate() is the main game loop that runs 60 times per second. It handles all gameplay logic: player input, car and player movement, cop AI chasing, tank shooting, collision detection, and projectile updates. The function branches into driving mode (car controls, cop AI) or on-foot mode (first-person movement and shooting). It also updates the UI displays and renders the scene each frame.

function animate() {
  requestAnimationFrame(animate);
  if (gameOver) {
    renderer.render(scene, camera);
    return;
  }
  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!`);
          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);
                  tankCar.isTank = true;
                  tankCar.shootingTimer = 0;
                  scene.add(tankCar);
                  copCars.push(tankCar);
              }
          }
          break;
      }
  }
  copMeterElement.innerHTML = `Cops: ${copCars.length}`;
  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 copCar of copCars) {
      let playerPos = car.position;
      let copCarPos = copCar.position;
      let currentCopSpeed = copCarSpeed;
      let currentCopTurnSpeed = copCarTurnSpeed;
      if (copCar.isSwatTruck) {
        currentCopSpeed = swatTruckSpeed;
        currentCopTurnSpeed = swatTruckTurnSpeed;
      } else if (copCar.isTank) {
        currentCopSpeed = tankCopCarSpeed;
        currentCopTurnSpeed = tankCopCarTurnSpeed;
      }
      let direction = playerPos.clone().sub(copCarPos);
      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 = copCar.rotation.y;
      let angleDiff = desiredAngle - currentAngle;
      if (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
      if (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
      copCar.rotation.y += Math.sign(angleDiff) * Math.min(Math.abs(angleDiff), currentCopTurnSpeed);
      copCar.position.addScaledVector(direction, currentCopSpeed);
      copCar.position.y = 0;
      wrapPosition(copCar.position, worldSize);
      if (copCar.isTank) {
        const distanceToPlayer = copCar.position.distanceTo(car.position);
        if (distanceToPlayer < tankShootingRange && carSpeed !== 0) {
            const tankTurret = copCar.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;
            const currentTime = Date.now();
            if (currentTime - copCar.shootingTimer > tankShootingCooldown) {
                copCar.shootingTimer = currentTime;
                const projectile = createProjectile(tankTurretPos, tankDirection);
                projectiles.push(projectile);
                scene.add(projectile);
            }
        }
      }
    }
    inCollision = false;
    for (const copCar of copCars) {
      if (car.position.distanceTo(copCar.position) < collisionDistance) {
        inCollision = true;
        break;
      }
    }
    if (inCollision) {
      if (collisionTimer === 0) {
        collisionTimer = Date.now();
        console.log('Collision started. Timer at 0.');
      } else {
        const elapsed = Date.now() - collisionTimer;
        console.log('Collision ongoing. Elapsed:', elapsed, 'ms');
        if (elapsed >= 7000) {
          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 cop car for 7 seconds!');
          return;
        }
      }
    } else {
      collisionTimer = 0;
      console.log('Collision ended. Timer reset.');
    }
    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);
        }
    }
  } 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)));
  }
  renderer.render(scene, camera);
}
Line-by-line explanation (33 lines)

🔧 Subcomponents:

conditional Survival Timer and Star Milestone Logic for (let i = 0; i < survivalMilestones.length; i++) { if (survivalTimer >= survivalMilestones[i] && stars <= i) {

Increments the wanted level as the player survives longer, spawning tank police at 5 stars.

conditional Car Acceleration and Deceleration if (keys['w']) { carSpeed += carAcceleration;

Handles WASD input to accelerate, brake, and reverse the car.

calculation Car Position Update const forwardVector = new THREE.Vector3(0, 0, -1); forwardVector.applyQuaternion(car.quaternion); car.position.addScaledVector(forwardVector, carSpeed);

Moves the car forward along its local forward direction (Z-axis rotated by the car's orientation).

for-loop Cop Car Chase AI Loop for (const copCar of copCars) { let playerPos = car.position;

Loops through all cop cars and makes them turn toward and chase the player using angle calculations and world wrapping.

conditional Tank Turret Aiming and Shooting if (copCar.isTank) { const distanceToPlayer = copCar.position.distanceTo(car.position);

If the cop car is a tank, rotates its turret to aim at the player and fires projectiles periodically.

conditional Cop Car Collision Detection (7-Second Timer) if (inCollision) { if (collisionTimer === 0) {

Detects when the player's car touches a cop car for 7+ seconds and triggers game over.

for-loop Projectile Movement and Collision for (let i = projectiles.length - 1; i >= 0; i--) {

Updates each projectile's position and checks if it hit the player's car or went out of bounds.

conditional On-Foot Movement Controls } else if (playerState === 'onFoot') { const playerForwardVector = new THREE.Vector3(0, 0, -1);

When on foot, uses WASD to move and rotate the player character in first-person view.

requestAnimationFrame(animate);
Schedules animate() to run again next frame, creating the continuous animation loop.
if (gameOver) { renderer.render(scene, camera); return;
If the game is over, still render the scene (showing the dark screen and game over image) but stop all game logic.
survivalTimer += deltaTime;
Increments the survival timer by deltaTime (milliseconds since last frame), used by p5.js to track elapsed time.
if (survivalTimer >= survivalMilestones[i] && stars <= i) {
Checks if the survival timer has reached a milestone (60 seconds, 120 seconds, etc.) and the current star count is below that milestone. If both true, increments stars.
if (stars === 5) {
At 5 stars (5 minutes), spawns 5 tank police cars immediately.
carSpeed += carAcceleration;
When 'w' is held, increases the car's speed by carAcceleration (0.05) each frame, up to the carMaxSpeed cap.
carSpeed = Math.min(carSpeed, carMaxSpeed);
Caps the car's speed to never exceed carMaxSpeed (5 units/frame).
if (carSpeed > 0) { carSpeed -= carBrakeDeceleration;
When 's' is held and the car is moving forward, applies strong braking deceleration (0.1).
const forwardVector = new THREE.Vector3(0, 0, -1);
Creates a vector pointing in the car's local forward direction (negative Z in car space).
forwardVector.applyQuaternion(car.quaternion);
Rotates the forward vector to match the car's current world orientation so the car moves in the direction it's facing.
car.position.addScaledVector(forwardVector, carSpeed);
Moves the car forward by adding (forwardVector × carSpeed) to its position.
let direction = playerPos.clone().sub(copCarPos);
Calculates a vector from the cop car to the player by subtracting the cop car's position from the player's.
if (Math.abs(direction.x) > worldSize / 2) { direction.x -= Math.sign(direction.x) * worldSize;
If the cop car is far from the player (likely on opposite sides of the world wrap), adjusts the direction vector to account for wrapping.
direction.normalize();
Normalizes the direction vector to length 1, so the cop car can move at a consistent speed in that direction.
let desiredAngle = Math.atan2(direction.x, direction.z);
Calculates the angle the cop car should face to aim at the player using atan2, which returns an angle in radians.
let angleDiff = desiredAngle - currentAngle; if (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
Calculates the shortest angular distance to rotate. If greater than 180 degrees, it's shorter to rotate the other way.
copCar.rotation.y += Math.sign(angleDiff) * Math.min(Math.abs(angleDiff), currentCopTurnSpeed);
Rotates the cop car gradually toward the desired angle, turning at most currentCopTurnSpeed radians per frame.
const tankTurret = copCar.children[1];
Gets the turret, which is the second child of the tank (index 1, after the body at index 0).
tankTurret.getWorldPosition(tankTurretPos);
Calculates the turret's position in world space (accounting for its parent tank's position and rotation).
if (currentTime - copCar.shootingTimer > tankShootingCooldown) {
Checks if enough time (1500 milliseconds) has passed since the tank last shot. If yes, fires again.
if (car.position.distanceTo(copCar.position) < collisionDistance) {
Measures the distance between the car and cop car. If less than 3.5 units, they're colliding.
collisionTimer = Date.now();
Records the current time as the start of the collision, beginning the 7-second timer.
const elapsed = Date.now() - collisionTimer;
Calculates how many milliseconds have passed since the collision started.
if (elapsed >= 7000) {
Checks if 7000 milliseconds (7 seconds) or more have passed. If yes, triggers game over.
scene.background = new THREE.Color(0x000000);
Changes the scene background to black to darken the screen, signaling game over.
projectile.position.addScaledVector(projectile.velocity, projectileSpeed);
Moves the projectile each frame by adding (velocity × projectileSpeed) to its position.
if (projectile.position.distanceTo(car.position) < (projectileRadius + collisionDistance / 2)) {
Checks if the projectile (0.5 radius) is within collision distance of the car. If yes, the projectile hit and game ends.
const playerForwardVector = new THREE.Vector3(0, 0, -1); playerForwardVector.applyQuaternion(player.quaternion);
Calculates the player's forward direction in world space by rotating the local forward vector by the player's rotation.
player.position.addScaledVector(playerForwardVector, playerSpeed);
Moves the player forward at a fixed speed (0.5 units/frame) when 'w' is held.
player.rotation.y += playerTurnSpeed;
When 'a' is held, rotates the player left by adding playerTurnSpeed (0.05 radians) to their rotation.
const playerCameraOffset = new THREE.Vector3(0, 1.6, 0);
Positions the camera 1.6 units above the player's position (eye level) for first-person view.
camera.lookAt(player.position.clone().add(playerForwardVector.multiplyScalar(10)));
Makes the camera look forward in the direction the player is facing, 10 units ahead.
renderer.render(scene, camera);
Renders the entire scene to the WebGL canvas using the current camera view.

draw()

draw() is a p5.js function that normally runs every frame, but in this sketch it's empty because three.js's animate() function handles all rendering and game logic. Keeping it empty avoids conflict between p5.js and three.js.

function draw() {
  // three.js handles rendering via its animate() loop
}
Line-by-line explanation (1 lines)
// three.js handles rendering via its animate() loop
This comment explains that p5.js's draw() function is empty because three.js has its own animate() loop that handles rendering.

windowResized()

windowResized() is a p5.js function called automatically whenever the window is resized. It updates the three.js camera and renderer to match the new window dimensions, ensuring the 3D scene scales correctly on different screen sizes.

function windowResized() {
  if (camera && renderer) {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  }
}
Line-by-line explanation (4 lines)
if (camera && renderer) {
Checks that the camera and renderer exist before updating them, preventing errors if this function is called before initialization.
camera.aspect = window.innerWidth / window.innerHeight;
Updates the camera's aspect ratio to match the new window dimensions so the view doesn't appear stretched.
camera.updateProjectionMatrix();
Recalculates the camera's projection matrix to apply the new aspect ratio.
renderer.setSize(window.innerWidth, window.innerHeight);
Resizes the three.js renderer canvas to match the new window size.

📦 Key Variables

scene THREE.Scene

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

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

The 3D camera that defines the player's viewpoint and frustum (viewing area).

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

The WebGL renderer that draws the scene to the canvas each frame.

renderer = new THREE.WebGLRenderer({ antialias: true });
car THREE.Group

The player's drivable car, a group containing the body, cabin, and wheels.

car = new THREE.Group();
player THREE.Group

The player's on-foot character, a green cylinder that appears when exiting the car.

player = new THREE.Group();
gun THREE.Mesh

The gun model in first-person view, attached to the camera when on foot.

gun = new THREE.Mesh(gunGeometry, gunMaterial);
npcs array

Global array of all NPC characters (green cylinders) in the world that can be shot.

let npcs = [];
copCars array

Global array of all cop cars, SWAT trucks, and tanks chasing the player.

let copCars = [];
projectiles array

Global array of active projectiles fired by tank police.

let projectiles = [];
playerState string

Current game mode: either 'driving' (in car) or 'onFoot' (walking and shooting).

let playerState = 'driving';
keys object

Tracks which keys are currently pressed (e.g., keys['w'] = true when 'w' is held).

let keys = {};
carSpeed number

The car's current forward speed (units per frame), affected by acceleration and deceleration.

let carSpeed = 0;
stars number

The current wanted level (0–5 stars), increasing as the player survives longer.

let stars = 0;
survivalTimer number

Milliseconds elapsed since the game started, used to award stars at milestones.

let survivalTimer = 0;
gameOver boolean

True when the game has ended (either caught or hit by tank projectile), stops all game logic.

let gameOver = false;
collisionTimer number

Timestamp when continuous collision with a cop car started, used for the 7-second game-over timer.

let collisionTimer = 0;
inCollision boolean

True if the player's car is currently touching a cop car, used to track collision state each frame.

let inCollision = false;
currentChunkX number

The X grid coordinate of the chunk the player is currently in.

let currentChunkX = 0;
currentChunkZ number

The Z grid coordinate of the chunk the player is currently in.

let currentChunkZ = 0;
chunks Map

A Map storing all active chunks keyed by 'x,z' coordinates, allowing fast lookup and removal.

let chunks = new Map();
lastShotTime number

Timestamp of the last shot fired, used to enforce the shooting cooldown.

let lastShotTime = 0;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG updateChunks() - chunk removal filter

The filter that removes NPCs and cop cars from the global arrays uses undefined variable names (chunkX, chunkZ) instead of the map key, causing all NPCs/cops to be removed when any chunk unloads.

💡 Parse the key string to extract chunk coordinates: const [chunkX, chunkZ] = key.split(',').map(Number); before using them in the filter conditions.

PERFORMANCE createChunkContent()

Building geometries are cloned with applyMatrix4, which is slower than scaling the shared geometry. With 15 buildings per chunk, this adds unnecessary cloning work.

💡 Use geometry.scale() at creation time instead of cloning per building, or pre-scale a few building height variants and reuse them.

PERFORMANCE animate() - cop car chase loop

The cop car AI runs every frame for all cop cars, including those far off-screen. With many cops this can impact frame rate.

💡 Only update cop cars within a reasonable distance of the player (e.g., if (copCar.position.distanceTo(car.position) < 300) { ... }) so distant cops don't drain performance.

STYLE global scope

Many global variables (carAcceleration, carDeceleration, carMaxSpeed, etc.) are scattered throughout the file without clear grouping.

💡 Organize related constants into objects: const carConfig = { acceleration: 0.05, deceleration: 0.02, maxSpeed: 5, turnSpeed: 0.03 }; makes the code easier to navigate and modify.

FEATURE shoot()

Shooting only works at the center of the screen; the raycaster's mouse position never changes.

💡 Add a mousemove listener to update mouse.x and mouse.y based on the actual cursor position, enabling raycast-based aiming that follows the cursor.

BUG shoot() - NPC removal

When an NPC is removed, its cloned geometry is disposed, but if multiple NPCs share a cloned geometry (which shouldn't happen but might), the geometry is disposed prematurely.

💡 Either ensure each NPC has a unique geometry (as currently done), or use a reference counter to dispose only when the last NPC using the geometry is removed.

🔄 Code Flow

Code flow showing preload, setup, initthreejs, createchunkcontent, createcar, createcopcarmodel, createswatruckmodel, createtankcarcmodel, createplayer, creategun, createprojectile, onkeydown, onkeyup, onmousedown, shoot, updatechunks, wrapposition, updatestardisplay, animate, draw, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> scene_camera_renderer_setup[Scene, Camera, Renderer Setup] draw --> lighting_setup[Lighting Configuration] draw --> geometry_material_pooling[Reusable Geometry and Material Creation] draw --> ground_and_roads[Ground and Road Creation] draw --> building_generation_loop[Building Generation Loop] draw --> npc_generation_loop[NPC Spawn Loop] draw --> cop_car_spawn[Cop Car Spawning] draw --> wheel_loop[Wheel Creation Loop] draw --> exit_car_conditional[Exit/Enter Car Logic] draw --> shooting_cooldown_check[Shooting Cooldown Timer] draw --> raycasting[Raycasting Setup] draw --> npc_hit_detection[NPC Hit Detection] draw --> muzzle_flash[Muzzle Flash Effect] draw --> chunk_position_calculation[Chunk Index Calculation] draw --> chunk_generation_loop[Active Chunk Generation Loop] draw --> chunk_cleanup_loop[Old Chunk Cleanup] draw --> survival_timer_and_stars[Survival Timer and Star Milestone Logic] draw --> car_controls[Car Acceleration and Deceleration] draw --> car_movement[Car Position Update] draw --> cop_ai_chase[Cop Car Chase AI Loop] draw --> tank_shooting[Tank Turret Aiming and Shooting] draw --> collision_detection[Cop Car Collision Detection] draw --> projectile_updates[Projectile Movement and Collision] draw --> on_foot_controls[On-Foot Movement Controls] click setup href "#fn-setup" click draw href "#fn-draw" click scene_camera_renderer_setup href "#sub-scene-camera-renderer-setup" click lighting_setup href "#sub-lighting-setup" click geometry_material_pooling href "#sub-geometry-material-pooling" click ground_and_roads href "#sub-ground-and-roads" click building_generation_loop href "#sub-building-generation-loop" click npc_generation_loop href "#sub-npc-generation-loop" click cop_car_spawn href "#sub-cop-car-spawn" click wheel_loop href "#sub-wheel-loop" click exit_car_conditional href "#sub-exit-car-conditional" click shooting_cooldown_check href "#sub-shooting-cooldown-check" click raycasting href "#sub-raycasting" click npc_hit_detection href "#sub-npc-hit-detection" click muzzle_flash href "#sub-muzzle-flash" click chunk_position_calculation href "#sub-chunk-position-calculation" click chunk_generation_loop href "#sub-chunk-generation-loop" click chunk_cleanup_loop href "#sub-chunk-cleanup-loop" click survival_timer_and_stars href "#sub-survival-timer-and-stars" click car_controls href "#sub-car-controls" click car_movement href "#sub-car-movement" click cop_ai_chase href "#sub-cop-ai-chase" click tank_shooting href "#sub-tank-shooting" click collision_detection href "#sub-collision-detection" click projectile_updates href "#sub-projectile-updates" click on_foot_controls href "#sub-on-foot-controls"

❓ Frequently Asked Questions

What visual experience does the 'gta 6 current state' p5.js sketch provide?

The sketch creates a dynamic 3D environment inspired by the Grand Theft Auto series, featuring a player character that can drive a car or walk on foot, along with NPCs and cop cars in a procedurally generated map.

How can users interact with the 'gta 6 current state' coding sketch?

Users can control the player character's movement and actions using the keyboard, allowing them to drive vehicles, walk around, and shoot in the virtual environment.

What creative coding concepts are showcased in the 'gta 6 current state' sketch?

This sketch demonstrates concepts such as 3D rendering with Three.js, procedural generation of game elements, and input handling for interactive gameplay.

Preview

gta 6 current state - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of gta 6 current state - Code flow showing preload, setup, initthreejs, createchunkcontent, createcar, createcopcarmodel, createswatruckmodel, createtankcarcmodel, createplayer, creategun, createprojectile, onkeydown, onkeyup, onmousedown, shoot, updatechunks, wrapposition, updatestardisplay, animate, draw, windowresized
Code Flow Diagram