🎮ArcadeLab

GTA6 DEMO FOR THE IMPATIENT

by ElectricKnight11
891 lines25.2 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GTA6 DEMO FOR THE IMPATIENT</title>

<style>
html, body {
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #05000a;
  font-family: "Segoe UI", sans-serif;
  user-select: none;
}

canvas {
  display: block;
}

#hud {
  position: fixed;
  top: 20px;
  left: 20px;
  z-index: 10;
  color: #00ffff;
  font-weight: bold;
  text-shadow: 0 0 10px rgba(0,255,255,.7);
  pointer-events: none;
}

#game-title {
  font-size: 14px;
  letter-spacing: 3px;
  color: #ff00aa;
  text-shadow: 0 0 8px rgba(255,0,170,.8);
  margin-bottom: 4px;
}

#character {
  font-size: 28px;
  letter-spacing: 3px;
}

#status {
  margin-top: 4px;
  color: white;
  font-size: 14px;
  letter-spacing: 1px;
}

#wanted {
  margin-top: 6px;
  color: #ff4444;
  letter-spacing: 3px;
  font-size: 18px;
}

#barBack {
  width: 260px;
  height: 12px;
  margin-top: 8px;
  border: 1px solid #00ffff;
  background: rgba(10,10,20,.85);
  border-radius: 4px;
  overflow: hidden;
}

#bar {
  width: 0%;
  height: 100%;
  background: linear-gradient(90deg,#00ffff,#ff00aa);
}

#minimap-container {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 10;
  width: 150px;
  height: 150px;
  border: 2px solid #00ffff;
  border-radius: 50%;
  background: rgba(5,5,15,.85);
  overflow: hidden;
  pointer-events: none;
}

#minimap-canvas {
  width: 100%;
  height: 100%;
}

#controls {
  position: fixed;
  bottom: 20px;
  left: 20px;
  z-index: 10;
  color: #e0e0e0;
  background: rgba(5,0,15,.88);
  border: 1px solid rgba(0,255,255,.35);
  border-radius: 6px;
  padding: 12px 18px;
  font-size: 13px;
  pointer-events: none;
}

#message {
  position: fixed;
  bottom: 100px;
  left: 50%;
  transform: translateX(-50%);
  color: white;
  background: rgba(0,0,0,.75);
  padding: 10px 18px;
  border-radius: 5px;
  opacity: 0;
  transition: opacity .2s;
  pointer-events: none;
  z-index: 20;
}
</style>
</head>

<body>

<div id="hud">
  <div id="game-title">GTA6 DEMO FOR THE IMPATIENT</div>
  <div id="character">LUCIA</div>
  <div id="status">ANONYMOUS</div>
  <div id="wanted">WANTED: ☆☆☆☆☆</div>
  <div id="barBack">
    <div id="bar"></div>
  </div>
</div>

<div id="minimap-container">
  <canvas id="minimap-canvas" width="150" height="150"></canvas>
</div>

<div id="controls">
  <b>WASD</b> Move / Drive |
  <b>SHIFT</b> Sprint / Nitro |
  <b>E</b> Enter / Exit |
  <b>F</b> Action |
  <b>TAB</b> Switch Character |
  <b>SPACE</b> Ability
</div>

<div id="message"></div>

<!-- External Three.js Dependency -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

<script>
/* =========================================================
   UI HELPERS
========================================================= */
let messageTimer = null;
function showMessage(text) {
  const msgEl = document.getElementById("message");
  msgEl.innerText = text;
  msgEl.style.opacity = "1";
  if (messageTimer) clearTimeout(messageTimer);
  messageTimer = setTimeout(() => {
    msgEl.style.opacity = "0";
  }, 2000);
}

/* =========================================================
   SCENE & CAMERA & RENDERER
========================================================= */
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x05000e);
scene.fog = new THREE.FogExp2(0x05000e, 0.011);

const camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 900);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);

const miniCanvas = document.getElementById("minimap-canvas");
const miniCtx = miniCanvas.getContext("2d");

/* =========================================================
   LIGHTING
========================================================= */
scene.add(new THREE.AmbientLight(0x302040, 1.2));

const moon = new THREE.DirectionalLight(0x8090ff, 1.25);
moon.position.set(-70, 110, 50);
moon.castShadow = true;
moon.shadow.mapSize.set(2048, 2048);
scene.add(moon);

const pink = new THREE.PointLight(0xff0088, 6, 140);
pink.position.set(45, 18, 45);
scene.add(pink);

const cyan = new THREE.PointLight(0x00ffff, 6, 140);
cyan.position.set(-45, 18, -45);
scene.add(cyan);

/* =========================================================
   WORLD (GROUND, ROADS, BUILDINGS)
========================================================= */
const ground = new THREE.Mesh(
  new THREE.PlaneGeometry(600, 600),
  new THREE.MeshStandardMaterial({ color: 0x08080f, roughness: .85 })
);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);

const grid = new THREE.GridHelper(600, 120, 0xff00aa, 0x14141f);
grid.position.y = .015;
scene.add(grid);

function createRoad(x, z, width, depth) {
  const road = new THREE.Mesh(
    new THREE.BoxGeometry(width, .05, depth),
    new THREE.MeshStandardMaterial({ color: 0x101018, roughness: .55 })
  );
  road.position.set(x, .03, z);
  road.receiveShadow = true;
  scene.add(road);
}

for (let z = -220; z <= 220; z += 50) createRoad(0, z, 520, 16);
for (let x = -220; x <= 220; x += 50) createRoad(x, 0, 16, 520);

function createBuilding(x, z) {
  const width = 8 + Math.random() * 12;
  const depth = 8 + Math.random() * 12;
  const height = 5 + Math.random() * 30;
  const colors = [0x101020, 0x181025, 0x10182b, 0x211020, 0x151515];

  const building = new THREE.Mesh(
    new THREE.BoxGeometry(width, height, depth),
    new THREE.MeshStandardMaterial({
      color: colors[Math.floor(Math.random() * colors.length)],
      roughness: .8
    })
  );
  building.position.set(x, height / 2, z);
  building.castShadow = true;
  building.receiveShadow = true;
  scene.add(building);

  if (Math.random() < .35) {
    const sign = new THREE.Mesh(
      new THREE.BoxGeometry(width * .7, .12, .12),
      new THREE.MeshBasicMaterial({ color: Math.random() < .5 ? 0xff0088 : 0x00ffff })
    );
    sign.position.set(x, height * .65, z - depth / 2 - .08);
    scene.add(sign);
  }
}

for (let x = -240; x <= 240; x += 30) {
  for (let z = -240; z <= 240; z += 30) {
    if (Math.abs(x % 50) < 10 || Math.abs(z % 50) < 10) continue;
    createBuilding(x + THREE.MathUtils.randFloat(-6, 6), z + THREE.MathUtils.randFloat(-6, 6));
  }
}

/* =========================================================
   HUMANOID CHARACTER
========================================================= */
function createHumanoidModel(type) {
  const group = new THREE.Group();

  const skinMat = new THREE.MeshStandardMaterial({ color: 0xe0a080, roughness: .6 });
  const hairMat = new THREE.MeshStandardMaterial({ color: type === "lucia" ? 0x221100 : 0x553311 });
  const topMat = new THREE.MeshStandardMaterial({ color: type === "lucia" ? 0xff0055 : 0x00aaff });
  const pantsMat = new THREE.MeshStandardMaterial({ color: type === "lucia" ? 0x111122 : 0x333333 });
  const shoeMat = new THREE.MeshStandardMaterial({ color: 0x111111 });

  const pelvis = new THREE.Mesh(new THREE.BoxGeometry(.45, .2, .3), pantsMat);
  pelvis.position.y = .95;
  group.add(pelvis);

  const torso = new THREE.Mesh(new THREE.BoxGeometry(.5, .65, .3), topMat);
  torso.position.y = 1.35;
  torso.castShadow = true;
  group.add(torso);

  const headGroup = new THREE.Group();
  headGroup.position.y = 1.85;

  const head = new THREE.Mesh(new THREE.SphereGeometry(.2, 16, 16), skinMat);
  head.castShadow = true;
  headGroup.add(head);

  if (type === "lucia") {
    const hair = new THREE.Mesh(new THREE.SphereGeometry(.22, 12, 12), hairMat);
    hair.position.set(0, .03, -.02);
    headGroup.add(hair);

    const ponytail = new THREE.Mesh(new THREE.CylinderGeometry(.06, .02, .4), hairMat);
    ponytail.position.set(0, -.1, -.22);
    ponytail.rotation.x = -.5;
    headGroup.add(ponytail);
  } else {
    const hair = new THREE.Mesh(new THREE.BoxGeometry(.42, .12, .42), hairMat);
    hair.position.y = .14;
    headGroup.add(hair);
  }
  group.add(headGroup);

  function createArm(left) {
    const arm = new THREE.Group();
    arm.position.set(left ? -.32 : .32, 1.6, 0);

    const upper = new THREE.Mesh(new THREE.CylinderGeometry(.07, .06, .35), topMat);
    upper.position.y = -.17;
    arm.add(upper);

    const lower = new THREE.Mesh(new THREE.CylinderGeometry(.06, .05, .35), skinMat);
    lower.position.y = -.48;
    arm.add(lower);
    return arm;
  }

  function createLeg(left) {
    const leg = new THREE.Group();
    leg.position.set(left ? -.15 : .15, .95, 0);

    const upper = new THREE.Mesh(new THREE.CylinderGeometry(.09, .07, .45), pantsMat);
    upper.position.y = -.22;
    leg.add(upper);

    const lower = new THREE.Mesh(new THREE.CylinderGeometry(.07, .06, .45), pantsMat);
    lower.position.y = -.62;
    leg.add(lower);

    const shoe = new THREE.Mesh(new THREE.BoxGeometry(.14, .1, .28), shoeMat);
    shoe.position.set(0, -.88, .05);
    leg.add(shoe);
    return leg;
  }

  const leftArm = createArm(true);
  const rightArm = createArm(false);
  const leftLeg = createLeg(true);
  const rightLeg = createLeg(false);

  group.add(leftArm);
  group.add(rightArm);
  group.add(leftLeg);
  group.add(rightLeg);

  return {
    group,
    limbs: { leftArm, rightArm, leftLeg, rightLeg }
  };
}

/* Player Entity Setup */
const playerGroup = new THREE.Group();
playerGroup.position.set(0, 0, 25);
scene.add(playerGroup);

const luciaModel = createHumanoidModel("lucia");
const jasonModel = createHumanoidModel("jason");
playerGroup.add(luciaModel.group);
playerGroup.add(jasonModel.group);
jasonModel.group.visible = false;

/* =========================================================
   CAR CREATOR & VEHICLES
========================================================= */
function createCar(color) {
  const car = new THREE.Group();

  const body = new THREE.Mesh(
    new THREE.BoxGeometry(2.3, .7, 4.6),
    new THREE.MeshStandardMaterial({ color, metalness: .75, roughness: .25 })
  );
  body.position.y = .65;
  body.castShadow = true;
  car.add(body);

  const cabin = new THREE.Mesh(
    new THREE.BoxGeometry(1.85, .65, 2.25),
    new THREE.MeshStandardMaterial({ color: 0x101018, metalness: .9, roughness: .08 })
  );
  cabin.position.set(0, 1.2, -.2);
  car.add(cabin);

  const wheels = [];
  function wheel(x, z) {
    const w = new THREE.Mesh(
      new THREE.CylinderGeometry(.38, .38, .22, 16),
      new THREE.MeshStandardMaterial({ color: 0x050505 })
    );
    w.rotation.z = Math.PI / 2;
    w.position.set(x, .38, z);
    car.add(w);
    wheels.push(w);
  }
  wheel(-1.15, -1.45);
  wheel(1.15, -1.45);
  wheel(-1.15, 1.45);
  wheel(1.15, 1.45);

  for (const x of [-.75, .75]) {
    const light = new THREE.Mesh(
      new THREE.BoxGeometry(.35, .18, .08),
      new THREE.MeshBasicMaterial({ color: 0xffffff })
    );
    light.position.set(x, .78, -2.32);
    car.add(light);
  }

  for (const x of [-.75, .75]) {
    const light = new THREE.Mesh(
      new THREE.BoxGeometry(.35, .18, .08),
      new THREE.MeshBasicMaterial({ color: 0xff0011 })
    );
    light.position.set(x, .78, 2.32);
    car.add(light);
  }

  return { group: car, wheels };
}

/* Player Car */
const playerCar = createCar(0xffaa00);
const carGroup = playerCar.group;
carGroup.position.set(6, 0, 20);
scene.add(carGroup);

/* Traffic System */
const trafficCars = [];
const trafficColors = [0xff2222, 0x2266ff, 0xffffff, 0x111111, 0x22cc66, 0xff66cc, 0xffff22, 0x8844ff, 0xff8800];

function spawnTrafficCar(x, z, direction) {
  const data = createCar(trafficColors[Math.floor(Math.random() * trafficColors.length)]);
  const car = data.group;
  car.position.set(x, 0, z);
  car.rotation.y = direction === 0 ? (Math.random() < .5 ? Math.PI / 2 : -Math.PI / 2) : (Math.random() < .5 ? 0 : Math.PI);
  scene.add(car);
  trafficCars.push({
    group: car,
    wheels: data.wheels,
    speed: .08 + Math.random() * .08
  });
}

for (let z = -200; z <= 200; z += 50) {
  for (let i = 0; i < 5; i++) spawnTrafficCar(THREE.MathUtils.randFloat(-200, 200), z + THREE.MathUtils.randFloat(-3, 3), 0);
}
for (let x = -200; x <= 200; x += 50) {
  for (let i = 0; i < 5; i++) spawnTrafficCar(x + THREE.MathUtils.randFloat(-3, 3), THREE.MathUtils.randFloat(-200, 200), 1);
}

function updateTraffic() {
  for (const traffic of trafficCars) {
    traffic.group.translateZ(-traffic.speed);
    if (traffic.group.position.x > 260) traffic.group.position.x = -260;
    if (traffic.group.position.x < -260) traffic.group.position.x = 260;
    if (traffic.group.position.z > 260) traffic.group.position.z = -260;
    if (traffic.group.position.z < -260) traffic.group.position.z = 260;
    for (const wheel of traffic.wheels) wheel.rotation.x -= .12;
  }
}

/* =========================================================
   NPC SYSTEM
========================================================= */
const npcs = [];
function spawnNPC(x, z) {
  const model = createHumanoidModel(Math.random() < .5 ? "lucia" : "jason");
  const npc = model.group;
  npc.scale.set(.9, .9, .9);
  npc.position.set(x, 0, z);
  scene.add(npc);

  npcs.push({
    mesh: npc,
    health: 100,
    angle: Math.random() * Math.PI * 2,
    speed: .012 + Math.random() * .025,
    fleeing: false,
    fleeTimer: 0,
    dead: false
  });
}

for (let i = 0; i < 70; i++) spawnNPC(THREE.MathUtils.randFloat(-230, 230), THREE.MathUtils.randFloat(-230, 230));

function updateNPCs() {
  const target = inCar ? carGroup.position : playerGroup.position;
  for (const npc of npcs) {
    if (npc.dead) continue;
    const distance = npc.mesh.position.distanceTo(target);

    if (wantedLevel > 0 && distance < 14) {
      npc.fleeing = true;
      npc.fleeTimer = 240;
    }

    if (npc.fleeing) {
      const direction = npc.mesh.position.clone().sub(target);
      direction.y = 0;
      if (direction.length() > 0) direction.normalize();
      npc.mesh.position.x += direction.x * .09;
      npc.mesh.position.z += direction.z * .09;
      npc.fleeTimer--;
      if (npc.fleeTimer <= 0) npc.fleeing = false;
    } else {
      npc.angle += (Math.random() - .5) * .025;
      npc.mesh.position.x += Math.sin(npc.angle) * npc.speed;
      npc.mesh.position.z += Math.cos(npc.angle) * npc.speed;
    }

    if (Math.abs(npc.mesh.position.x) > 240) npc.angle += Math.PI;
    if (Math.abs(npc.mesh.position.z) > 240) npc.angle += Math.PI;
  }
}

/* =========================================================
   POLICE SYSTEM
========================================================= */
const police = [];
let wantedLevel = 0;

function createPoliceCar() {
  const data = createCar(0xffffff);
  const car = data.group;

  const red = new THREE.Mesh(
    new THREE.BoxGeometry(.55, .14, .28),
    new THREE.MeshBasicMaterial({ color: 0xff0033 })
  );
  red.position.set(-.3, 1.65, 0);
  car.add(red);

  const blue = new THREE.Mesh(
    new THREE.BoxGeometry(.55, .14, .28),
    new THREE.MeshBasicMaterial({ color: 0x0066ff })
  );
  blue.position.set(.3, 1.65, 0);
  car.add(blue);

  return { group: car, wheels: data.wheels };
}

function spawnPolice() {
  if (police.length >= wantedLevel) return;
  const target = inCar ? carGroup.position : playerGroup.position;
  const angle = Math.random() * Math.PI * 2;
  const distance = 40;
  const data = createPoliceCar();
  const car = data.group;

  car.position.set(
    target.x + Math.cos(angle) * distance,
    0,
    target.z + Math.sin(angle) * distance
  );
  scene.add(car);

  police.push({
    mesh: car,
    wheels: data.wheels,
    speed: .1 + wantedLevel * .025
  });
}

function updatePolice() {
  const target = inCar ? carGroup.position : playerGroup.position;
  for (const cop of police) {
    const car = cop.mesh;
    const direction = target.clone().sub(car.position);
    direction.y = 0;

    if (direction.length() > 1) {
      direction.normalize();
      const angle = Math.atan2(direction.x, direction.z);
      car.rotation.y = THREE.MathUtils.lerp(car.rotation.y, angle, .06);
      car.translateZ(-cop.speed);
    }

    for (const wheel of cop.wheels) wheel.rotation.x -= .15;

    if (car.position.distanceTo(target) < 4) {
      notoriety = Math.min(notoriety + .25, 100);
    }
  }
}

function updateWantedSystem() {
  if (wantedLevel > 0 && notoriety < 15) {
    wantedLevel--;
  }
  if (wantedLevel > 0 && police.length < wantedLevel) {
    spawnPolice();
  }
}

/* =========================================================
   GAME ACTION (PROJECTILE)
========================================================= */
let actionCooldown = 0;

function fireAction() {
  if (actionCooldown > 0) return;
  actionCooldown = 12;

  const projectile = new THREE.Mesh(
    new THREE.SphereGeometry(.07, 8, 8),
    new THREE.MeshBasicMaterial({ color: 0xffff00 })
  );

  const origin = inCar ? carGroup.position.clone() : playerGroup.position.clone();
  origin.y += 1.2;
  projectile.position.copy(origin);
  scene.add(projectile);

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

  let distance = 0;
  function moveProjectile() {
    projectile.position.addScaledVector(direction, 1.5);
    distance += 1.5;

    for (const npc of npcs) {
      if (npc.dead) continue;
      if (projectile.position.distanceTo(npc.mesh.position) < 1) {
        npc.health -= 50;
        npc.fleeing = true;
        npc.fleeTimer = 300;
        wantedLevel = Math.min(wantedLevel + 1, 5);
        notoriety = Math.min(notoriety + 20, 100);

        if (npc.health <= 0) {
          npc.dead = true;
          npc.mesh.rotation.x = Math.PI / 2;
          npc.mesh.position.y = .2;
        }
        scene.remove(projectile);
        return;
      }
    }

    if (distance < 100) {
      requestAnimationFrame(moveProjectile);
    } else {
      scene.remove(projectile);
    }
  }
  moveProjectile();
}

/* =========================================================
   CONTROLS & INPUT
========================================================= */
const keys = {};
let isLucia = true;
let inCar = false;
let notoriety = 0;
let carSpeed = 0;
let animTimer = 0;

window.addEventListener("keydown", e => {
  keys[e.code] = true;

  if (e.code === "Tab") {
    e.preventDefault();
    if (!inCar) {
      isLucia = !isLucia;
      luciaModel.group.visible = isLucia;
      jasonModel.group.visible = !isLucia;
      document.getElementById("character").innerText = isLucia ? "LUCIA" : "JASON";
    }
  }

  if (e.code === "KeyE") {
    const distance = playerGroup.position.distanceTo(carGroup.position);
    if (!inCar && distance < 4) {
      inCar = true;
      playerGroup.visible = false;
      showMessage("ENTERED VEHICLE");
    } else if (inCar) {
      inCar = false;
      playerGroup.visible = true;
      playerGroup.position.copy(carGroup.position).add(new THREE.Vector3(2.5, 0, 0));
      showMessage("EXITED VEHICLE");
    }
  }

  if (e.code === "Space" && !e.repeat) {
    notoriety = Math.min(notoriety + 15, 100);
    showMessage(isLucia ? "EMP PULSE!" : "ADRENALINE!");
  }

  if (e.code === "KeyF" && !e.repeat) {
    fireAction();
  }
});

window.addEventListener("keyup", e => {
  keys[e.code] = false;
});

/* =========================================================
   ANIMATION & PHYSICS UPDATES
========================================================= */
function animateHumanoid(model, moving, sprinting) {
  const limbs = model.limbs;
  if (moving) {
    animTimer += .016 * (sprinting ? 16 : 10);
    const angle = Math.sin(animTimer) * (sprinting ? .75 : .45);
    limbs.leftLeg.rotation.x = angle;
    limbs.rightLeg.rotation.x = -angle;
    limbs.leftArm.rotation.x = -angle;
    limbs.rightArm.rotation.x = angle;
  } else {
    limbs.leftLeg.rotation.x = THREE.MathUtils.lerp(limbs.leftLeg.rotation.x, 0, .2);
    limbs.rightLeg.rotation.x = THREE.MathUtils.lerp(limbs.rightLeg.rotation.x, 0, .2);
    limbs.leftArm.rotation.x = THREE.MathUtils.lerp(limbs.leftArm.rotation.x, 0, .2);
    limbs.rightArm.rotation.x = THREE.MathUtils.lerp(limbs.rightArm.rotation.x, 0, .2);
  }
}

function updatePhysics() {
  const activeModel = isLucia ? luciaModel : jasonModel;
  const moveVector = new THREE.Vector3();

  if (!inCar) {
    const sprinting = keys["ShiftLeft"] || keys["ShiftRight"];
    const speed = sprinting ? .35 : .18;

    if (keys["KeyW"]) moveVector.z -= 1;
    if (keys["KeyS"]) moveVector.z += 1;
    if (keys["KeyA"]) moveVector.x -= 1;
    if (keys["KeyD"]) moveVector.x += 1;

    const moving = moveVector.length() > 0;
    if (moving) {
      moveVector.normalize();
      playerGroup.position.addScaledVector(moveVector, speed);
      const targetAngle = Math.atan2(moveVector.x, moveVector.z);
      playerGroup.rotation.y = THREE.MathUtils.lerp(playerGroup.rotation.y, targetAngle, .2);
      notoriety += .02;
    }

    animateHumanoid(activeModel, moving, sprinting);
  } else {
    const sprinting = keys["ShiftLeft"] || keys["ShiftRight"];

    if (keys["KeyW"]) {
      carSpeed = THREE.MathUtils.lerp(carSpeed, sprinting ? .85 : .5, .05);
    } else if (keys["KeyS"]) {
      carSpeed = THREE.MathUtils.lerp(carSpeed, -.25, .05);
    } else {
      carSpeed = THREE.MathUtils.lerp(carSpeed, 0, .05);
    }

    if (keys["KeyA"]) carGroup.rotation.y += .04 * (carSpeed >= 0 ? 1 : -1);
    if (keys["KeyD"]) carGroup.rotation.y -= .04 * (carSpeed >= 0 ? 1 : -1);

    carGroup.translateZ(-carSpeed);

    if (Math.abs(carSpeed) > .05) notoriety += .04;
    for (const wheel of playerCar.wheels) wheel.rotation.x -= carSpeed * 3;
  }

  notoriety = Math.max(0, notoriety - .02);
  if (actionCooldown > 0) actionCooldown--;
}

function updateCamera() {
  const focus = inCar ? carGroup.position : playerGroup.position;
  const target = new THREE.Vector3(
    focus.x,
    focus.y + (inCar ? 7 : 5),
    focus.z + (inCar ? 14 : 10)
  );

  camera.position.lerp(target, .1);
  camera.lookAt(focus.x, focus.y + 1, focus.z);
}

function updateHUD() {
  notoriety = THREE.MathUtils.clamp(notoriety, 0, 100);
  document.getElementById("bar").style.width = notoriety + "%";

  const status = document.getElementById("status");
  if (notoriety > 70) {
    status.innerText = "🚨 VIRAL — POLICE ALERT";
    status.style.color = "#ff2222";
  } else if (notoriety > 35) {
    status.innerText = "📱 TRENDING";
    status.style.color = "#ffff00";
  } else {
    status.innerText = "ANONYMOUS";
    status.style.color = "white";
  }

  document.getElementById("wanted").innerText =
    "WANTED: " + "★".repeat(wantedLevel) + "☆".repeat(5 - wantedLevel);
}

/* =========================================================
   MINIMAP RENDERER
========================================================= */
function renderMinimap() {
  miniCtx.clearRect(0, 0, 150, 150);
  const active = inCar ? carGroup.position : playerGroup.position;
  const mapScale = 0.35;

  // Background Circle Grid
  miniCtx.strokeStyle = "rgba(0, 255, 255, 0.2)";
  miniCtx.lineWidth = 1;
  miniCtx.beginPath();
  miniCtx.arc(75, 75, 70, 0, Math.PI * 2);
  miniCtx.stroke();

  // Traffic Cars
  miniCtx.fillStyle = "#ffaa00";
  for (const traffic of trafficCars) {
    const relX = 75 + (traffic.group.position.x - active.x) * mapScale;
    const relZ = 75 + (traffic.group.position.z - active.z) * mapScale;
    if (Math.hypot(relX - 75, relZ - 75) < 70) {
      miniCtx.fillRect(relX - 2, relZ - 2, 4, 4);
    }
  }

  // Police Units
  miniCtx.fillStyle = "#ff0033";
  for (const cop of police) {
    const relX = 75 + (cop.mesh.position.x - active.x) * mapScale;
    const relZ = 75 + (cop.mesh.position.z - active.z) * mapScale;
    if (Math.hypot(relX - 75, relZ - 75) < 70) {
      miniCtx.beginPath();
      miniCtx.arc(relX, relZ, 3, 0, Math.PI * 2);
      miniCtx.fill();
    }
  }

  // Player Marker
  miniCtx.fillStyle = "#00ffff";
  miniCtx.beginPath();
  miniCtx.arc(75, 75, 4, 0, Math.PI * 2);
  miniCtx.fill();
}

/* =========================================================
   MAIN LOOP
========================================================= */
function animate() {
  requestAnimationFrame(animate);

  updatePhysics();
  updateTraffic();
  updateNPCs();
  updatePolice();
  updateWantedSystem();
  updateCamera();
  updateHUD();
  renderMinimap();

  const time = performance.now() * 0.0005;
  pink.position.x = Math.sin(time) * 80;
  pink.position.z = Math.cos(time) * 80;
  cyan.position.x = Math.cos(time) * 80;
  cyan.position.z = Math.sin(time) * 80;

  renderer.render(scene, camera);
}

animate();

window.addEventListener("resize", () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>

Game Source: GTA6 DEMO FOR THE IMPATIENT

Creator: ElectricKnight11

Libraries: three

Complexity: complex (891 lines, 25.2 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: gta6-demo-for-the-impatient-electricknight11" to link back to the original. Then publish at arcadelab.ai/publish.