APK FET
Urban Havoc 3D is a high-octane, endless driving and combat game set on a sprawling, sun-drenched coastal highway. You take control of a high-performance sports car, maneuvering through heavy traffic and urban obstacles while fending off waves of hostile vehicles.
Key Features:
Dynamic Combat: Use your vehicle's mounted weaponry to clear the road and rack up points.
Responsive Controls: Includes a custom virtual joystick for smooth mobile steering and physical keyboard support for desktop play.
Persistent Challenge: Navigate a procedurally generated environment where the density of enemies and obstacles increases as you progress.
Sleek UI: Monitor your "Hull Integrity" and score via a minimalist, modern glassmorphism HUD.
Immersive Atmosphere: Features 3D lighting, real-time shadows, and a stylish low-poly urban aesthetic.
How to Play:
Desktop: Use W/A/S/D or Arrow Keys to drive and Spacebar to fire.
Mobile: Use the left-hand Joystick to steer and the right-hand Gas, ABS (Brake), and Fire buttons to control your speed and weapons.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Urban Havoc 3D: High Noon</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { margin: 0; overflow: hidden; background: #87CEEB; font-family: 'Segoe UI', sans-serif; touch-action: none; }
canvas { display: block; }
#ui-layer { pointer-events: none; }
.touch-btn { pointer-events: auto; user-select: none; -webkit-tap-highlight-color: transparent; }
#joystick-container { width: 140px; height: 140px; background: rgba(0,0,0,0.05); border-radius: 50%; border: 2px solid rgba(0,0,0,0.1); position: relative; }
#joystick-knob { width: 50px; height: 50px; background: rgba(30,58,138,0.4); border-radius: 50%; position: absolute; left: 45px; top: 45px; box-shadow: 0 4px 10px rgba(0,0,0,0.2); }
</style>
</head>
<body>
<!-- HUD Overlay -->
<div id="ui-layer" class="absolute inset-0 flex flex-col justify-between p-4 z-10 text-slate-800">
<div class="flex justify-between items-start pt-2">
<div class="bg-white/80 backdrop-blur-md p-3 rounded-xl border border-white shadow-lg">
<div class="text-[10px] uppercase tracking-widest text-blue-600 mb-1 font-bold">Hull Integrity</div>
<div class="w-40 h-2 bg-slate-200 rounded-full overflow-hidden">
<div id="health-bar" class="h-full bg-gradient-to-r from-green-400 to-blue-500 transition-all duration-300" style="width: 100%"></div>
</div>
</div>
<div class="bg-white/80 backdrop-blur-md p-3 rounded-xl border border-white shadow-lg text-right">
<div class="text-[10px] uppercase tracking-widest text-blue-600 font-bold">Score</div>
<div id="score-val" class="text-2xl font-black font-mono tracking-tighter italic">00000</div>
</div>
</div>
<!-- Mobile Controls -->
<div class="flex justify-between items-end mb-8 px-2">
<div class="touch-btn" id="joystick-zone">
<div id="joystick-container">
<div id="joystick-knob"></div>
</div>
</div>
<div class="flex flex-col gap-3">
<div class="flex gap-3">
<div id="btn-brake" class="touch-btn w-16 h-16 rounded-full bg-red-500/80 border-4 border-white flex items-center justify-center text-[10px] font-black text-white active:bg-red-700 shadow-md">ABS</div>
<div id="btn-gas" class="touch-btn w-20 h-20 rounded-full bg-blue-600/80 border-4 border-white flex items-center justify-center text-lg font-black italic text-white active:scale-95 shadow-lg">GAS</div>
</div>
<div id="btn-fire" class="touch-btn w-full h-16 rounded-xl bg-amber-500/90 border-4 border-white flex items-center justify-center text-xl font-black italic text-white active:bg-amber-600 shadow-lg">FIRE</div>
</div>
</div>
</div>
<script>
let scene, camera, renderer, clock;
let player, worldGroup;
let enemies = [], bullets = [], scenery = [];
let score = 0, health = 100;
const input = { forward: 0, brake: 0, turn: 0, fire: false };
const MAX_SPEED = 1.4, ACCEL = 0.04, FRICTION = 0.97;
let velocity = 0;
window.onload = init;
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky Blue
scene.fog = new THREE.Fog(0x87CEEB, 20, 300);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
clock = new THREE.Clock();
worldGroup = new THREE.Group();
scene.add(worldGroup);
// Bright Sunlight
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
const sun = new THREE.DirectionalLight(0xfff5e1, 1.2);
sun.position.set(100, 200, 100);
sun.castShadow = true;
scene.add(sun);
createGround();
createPlayer();
setupControls();
for(let i=0; i<30; i++) spawnScenery(i * -20);
animate();
}
function createGround() {
// Main Road
const roadGeo = new THREE.PlaneGeometry(30, 4000);
const roadMat = new THREE.MeshPhongMaterial({ color: 0x444444 });
const road = new THREE.Mesh(roadGeo, roadMat);
road.rotation.x = -Math.PI / 2;
road.receiveShadow = true;
worldGroup.add(road);
// Sidewalks/Sand
const sandGeo = new THREE.PlaneGeometry(1000, 4000);
const sandMat = new THREE.MeshPhongMaterial({ color: 0xdbca9a });
const sand = new THREE.Mesh(sandGeo, sandMat);
sand.rotation.x = -Math.PI / 2;
sand.position.y = -0.1;
worldGroup.add(sand);
// Markings
for(let i=0; i<150; i++) {
const markGeo = new THREE.PlaneGeometry(0.6, 5);
const markMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
const mark = new THREE.Mesh(markGeo, markMat);
mark.rotation.x = -Math.PI / 2;
mark.position.set(0, 0.05, i * -25);
worldGroup.add(mark);
}
}
function spawnScenery(zPos) {
const side = Math.random() > 0.5 ? 1 : -1;
const xOffset = side * (25 + Math.random() * 15);
if (Math.random() > 0.4) {
// Modern Building
const h = 15 + Math.random() * 50;
const w = 10 + Math.random() * 10;
const geo = new THREE.BoxGeometry(w, h, w);
const colors = [0xffffff, 0xe0e0e0, 0xd0d0d0, 0xb0c4de];
const mat = new THREE.MeshPhongMaterial({ color: colors[Math.floor(Math.random()*colors.length)] });
const b = new THREE.Mesh(geo, mat);
b.position.set(xOffset, h/2, zPos);
b.castShadow = true;
b.receiveShadow = true;
// Windows (Indented boxes or simple dark panes)
const winRowGeo = new THREE.BoxGeometry(w + 0.2, 1, w + 0.2);
const winMat = new THREE.MeshPhongMaterial({ color: 0x333344 });
for(let j=1; j<h/5; j++) {
const win = new THREE.Mesh(winRowGeo, winMat);
win.position.y = (j * 5) - h/2;
b.add(win);
}
scenery.push(b);
worldGroup.add(b);
} else {
// Summer Tree
const tree = new THREE.Group();
const trunkGeo = new THREE.CylinderGeometry(0.5, 0.7, 5);
const trunkMat = new THREE.MeshPhongMaterial({ color: 0x5d4037 });
const trunk = new THREE.Mesh(trunkGeo, trunkMat);
trunk.castShadow = true;
tree.add(trunk);
const foliageGeo = new THREE.SphereGeometry(3, 8, 8);
const foliageMat = new THREE.MeshPhongMaterial({ color: 0x2e7d32 });
const fol = new THREE.Mesh(foliageGeo, foliageMat);
fol.position.y = 4;
fol.castShadow = true;
tree.add(fol);
tree.position.set(side * 20, 2.5, zPos);
scenery.push(tree);
worldGroup.add(tree);
}
}
function createPlayer() {
player = new THREE.Group();
// Sports Car Body
const body = new THREE.Mesh(
new THREE.BoxGeometry(1.8, 0.7, 3.5),
new THREE.MeshPhongMaterial({ color: 0xcc0000, shininess: 100 })
);
body.castShadow = true;
player.add(body);
// Roof
const roof = new THREE.Mesh(
new THREE.BoxGeometry(1.4, 0.5, 1.8),
new THREE.MeshPhongMaterial({ color: 0x111111 })
);
roof.position.set(0, 0.5, 0.2);
player.add(roof);
// Wheels
const wheelGeo = new THREE.CylinderGeometry(0.5, 0.5, 0.5, 16);
const wheelMat = new THREE.MeshPhongMaterial({ color: 0x222222 });
const wheelPos = [[1, -0.2, 1.2], [-1, -0.2, 1.2], [1, -0.2, -1.2], [-1, -0.2, -1.2]];
wheelPos.forEach(p => {
const w = new THREE.Mesh(wheelGeo, wheelMat);
w.rotation.z = Math.PI / 2;
w.position.set(...p);
player.add(w);
});
player.position.y = 0.7;
scene.add(player);
}
function spawnEnemy() {
if (enemies.length < 15 && Math.random() < 0.04) {
const e = new THREE.Group();
const body = new THREE.Mesh(
new THREE.BoxGeometry(2, 1.2, 4),
new THREE.MeshPhongMaterial({ color: 0xffffff })
);
e.add(body);
// Windows
const glass = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.5, 2), new THREE.MeshPhongMaterial({color: 0x333333}));
glass.position.set(0, 0.6, 0.5);
e.add(glass);
e.position.set((Math.random() - 0.5) * 25, 0.8, player.position.z - 200);
e.speed = 0.4 + Math.random() * 0.6;
e.castShadow = true;
enemies.push(e);
scene.add(e);
}
}
function setupControls() {
// Keyboard
window.addEventListener('keydown', (e) => {
if (e.code === 'KeyW' || e.code === 'ArrowUp') input.forward = 1;
if (e.code === 'KeyS' || e.code === 'ArrowDown') input.brake = 1;
if (e.code === 'KeyA' || e.code === 'ArrowLeft') input.turn = -1;
if (e.code === 'KeyD' || e.code === 'ArrowRight') input.turn = 1;
if (e.code === 'Space') input.fire = true;
});
window.addEventListener('keyup', (e) => {
if (e.code === 'KeyW' || e.code === 'ArrowUp') input.forward = 0;
if (e.code === 'KeyS' || e.code === 'ArrowDown') input.brake = 0;
if (e.code === 'KeyA' || e.code === 'ArrowLeft' || e.code === 'KeyD' || e.code === 'ArrowRight') input.turn = 0;
if (e.code === 'Space') input.fire = false;
});
// Touch
const btnGas = document.getElementById('btn-gas');
const btnBrake = document.getElementById('btn-brake');
const btnFire = document.getElementById('btn-fire');
btnGas.ontouchstart = (e) => { e.preventDefault(); input.forward = 1; };
btnGas.ontouchend = () => input.forward = 0;
btnBrake.ontouchstart = (e) => { e.preventDefault(); input.brake = 1; };
btnBrake.ontouchend = () => input.brake = 0;
btnFire.ontouchstart = (e) => { e.preventDefault(); input.fire = true; };
btnFire.ontouchend = () => input.fire = false;
const knob = document.getElementById('joystick-knob');
const container = document.getElementById('joystick-container');
const rect = container.getBoundingClientRect();
const center = rect.width / 2;
container.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
const dx = Math.max(-50, Math.min(50, touch.clientX - (rect.left + center)));
knob.style.transform = `translateX(${dx}px)`;
input.turn = dx / 50;
});
container.addEventListener('touchend', () => {
knob.style.transform = `translateX(0px)`;
input.turn = 0;
});
}
function animate() {
requestAnimationFrame(animate);
const dt = clock.getDelta();
// Physics
if (input.forward) velocity += ACCEL;
if (input.brake) velocity -= ACCEL * 2;
velocity *= FRICTION;
velocity = Math.max(-0.2, Math.min(velocity, MAX_SPEED));
player.rotation.y -= input.turn * 0.05 * (velocity + 0.1);
player.translateZ(-velocity);
// Camera
const targetCam = player.position.clone().add(new THREE.Vector3(0, 4, 10).applyQuaternion(player.quaternion));
camera.position.lerp(targetCam, 0.1);
camera.lookAt(player.position.clone().add(new THREE.Vector3(0, 1, -5).applyQuaternion(player.quaternion)));
spawnEnemy();
if (Math.random() < 0.1) spawnScenery(player.position.z - 250);
// Projectiles
if (input.fire && clock.elapsedTime % 0.2 < 0.05) {
const b = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.3, 1), new THREE.MeshBasicMaterial({color: 0x000000}));
b.position.copy(player.position).add(new THREE.Vector3(0, 0.5, -2).applyQuaternion(player.quaternion));
b.velocity = new THREE.Vector3(0,0,-4).applyQuaternion(player.quaternion);
bullets.push(b);
scene.add(b);
}
for(let i=bullets.length-1; i>=0; i--) {
bullets[i].position.add(bullets[i].velocity);
if (bullets[i].position.distanceTo(player.position) > 200) {
scene.remove(bullets[i]);
bullets.splice(i, 1);
}
}
// Enemies
for(let i=enemies.length-1; i>=0; i--) {
const e = enemies[i];
e.position.z += e.speed;
bullets.forEach((b, bi) => {
if (b.position.distanceTo(e.position) < 3) {
scene.remove(e);
enemies.splice(i, 1);
scene.remove(b);
bullets.splice(bi, 1);
score += 100;
document.getElementById('score-val').innerText = score.toString().padStart(5, '0');
}
});
if (e.position.distanceTo(player.position) < 2.5) {
health -= 1;
document.getElementById('health-bar').style.width = Math.max(0, health) + '%';
// Simple knockback away from player
const bounceDir = e.position.x > player.position.x ? 1 : -1;
e.position.x += bounceDir * 5;
}
if (e.position.z > player.position.z + 100) {
scene.remove(e);
enemies.splice(i, 1);
}
}
// Cleanup scenery
for(let i=scenery.length-1; i>=0; i--) {
if (scenery[i].position.z > player.position.z + 100) {
worldGroup.remove(scenery[i]);
scenery.splice(i, 1);
}
}
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>