
GTA Clone
One shot GTA Clone game
Prompt
Create a basic GTA-style top-down game clone using HTML, CSS, and JavaScript. The player should control a character with arrow keys or WASD, move around a city map, and interact with vehicles and pedestrians. Include simple collision detection, sprite animations, and a minimap UI
A system prompt was added to support web rendering
# GTA-Style Top-Down Game Clone I'll create a top-down game with player movement, vehicles, pedestrians, collision detection, and a minimap. Here's the complete implementation: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Top-Down City Game</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #111; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow: hidden; color: white; } #game-container { position: relative; width: 800px; height: 600px; box-shadow: 0 0 20px rgba(0, 150, 255, 0.5); border: 2px solid #0af; } #game-canvas { background: #222; width: 100%; height: 100%; } #minimap { position: absolute; top: 15px; right: 15px; width: 150px; height: 150px; background: rgba(0, 0, 0, 0.7); border: 2px solid #0af; box-shadow: 0 0 10px rgba(0, 150, 255, 0.5); } #game-ui { position: absolute; bottom: 15px; left: 15px; background: rgba(0, 0, 0, 0.7); padding: 10px 15px; border-radius: 5px; border: 1px solid #0af; } #controls { position: absolute; top: 15px; left: 15px; background: rgba(0, 0, 0, 0.7); padding: 10px; border-radius: 5px; border: 1px solid #0af; font-size: 12px; } #controls h3 { margin-bottom: 5px; color: #0af; } #controls ul { list-style: none; padding-left: 10px; } #controls li { margin: 3px 0; } #title { position: absolute; top: 15px; left: 50%; transform: translateX(-50%); font-size: 24px; color: #0af; text-shadow: 0 0 10px rgba(0, 150, 255, 0.8); } .vehicle { position: absolute; width: 30px; height: 15px; background: #f00; border-radius: 3px; } .pedestrian { position: absolute; width: 8px; height: 8px; background: #0f0; border-radius: 50%; } #instructions { position: absolute; bottom: 15px; right: 15px; background: rgba(0, 0, 0, 0.7); padding: 10px; border-radius: 5px; border: 1px solid #0af; font-size: 12px; max-width: 200px; } </style> </head> <body> <div id="game-container"> <div id="title">CITY ROAMER</div> <canvas id="game-canvas" width="800" height="600"></canvas> <canvas id="minimap" width="150" height="150"></canvas> <div id="game-ui"> <div>Speed: <span id="speed">0</span> mph</div> <div>Status: <span id="status">Roaming</span></div> </div> <div id="controls"> <h3>Controls</h3> <ul> <li>Arrow Keys / WASD: Move</li> <li>Shift: Sprint</li> <li>Space: Interact</li> </ul> </div> <div id="instructions"> <p>Explore the city, avoid collisions, and interact with vehicles and pedestrians!</p> </div> </div> <script> // Game variables const canvas = document.getElementById('game-canvas'); const ctx = canvas.getContext('2d'); const minimap = document.getElementById('minimap'); const minimapCtx = minimap.getContext('2d'); const speedElement = document.getElementById('speed'); const statusElement = document.getElementById('status'); // Player properties const player = { x: 400, y: 300, width: 20, height: 20, speed: 0, maxSpeed: 5, acceleration: 0.2, deceleration: 0.1, color: '#0af', direction: 0, // 0: up, 1: right, 2: down, 3: left isMoving: false }; // Map properties const map = { width: 2000, height: 1500, tileSize: 50, tiles: [] }; // Camera properties const camera = { x: 0, y: 0, width: canvas.width, height: canvas.height }; // Vehicles array const vehicles = [ { x: 300, y: 200, width: 30, height: 15, color: '#f00', speed: 1, direction: 1 }, { x: 500, y: 400, width: 30, height: 15, color: '#ff0', speed: 1.5, direction: 2 }, { x: 700, y: 300, width: 30, height: 15, color: '#0f0', speed: 1.2, direction: 3 }, { x: 200, y: 500, width: 30, height: 15, color: '#00f', speed: 0.8, direction: 0 }, { x: 600, y: 100, width: 30, height: 15, color: '#f0f', speed: 1.3, direction: 1 } ]; // Pedestrians array const pedestrians = []; for (let i = 0; i < 20; i++) { pedestrians.push({ x: Math.random() * map.width, y: Math.random() * map.height, width: 8, height: 8, color: '#0f0', speed: 0.5 + Math.random() * 0.5, direction: Math.floor(Math.random() * 4) }); } // Buildings array const buildings = [ { x: 100, y: 100, width: 150, height: 100, color: '#555' }, { x: 300, y: 150, width: 120, height: 180, color: '#666' }, { x: 500, y: 100, width: 100, height: 120, color: '#777' }, { x: 700, y: 200, width: 180, height: 150, color: '#555' }, { x: 200, y: 400, width: 100, height: 200, color: '#666' }, { x: 400, y: 500, width: 150, height: 100, color: '#777' }, { x: 600, y: 450, width: 120, height: 150, color: '#555' } ]; // Roads array const roads = [ { x: 0, y: 250, width: map.width, height: 50, color: '#333' }, { x: 0, y: 450, width: map.width, height: 50, color: '#333' }, { x: 250, y: 0, width: 50, height: map.height, color: '#333' }, { x: 450, y: 0, width: 50, height: map.height, color: '#333' }, { x: 650, y: 0, width: 50, height: map.height, color: '#333' } ]; // Key state tracking const keys = { up: false, down: false, left: false, right: false, shift: false, space: false }; // Initialize game function init() { // Generate map tiles for minimap for (let y = 0; y < map.height; y += map.tileSize) { for (let x = 0; x < map.width; x += map.tileSize) { map.tiles.push({ x: x, y: y, width: map.tileSize, height: map.tileSize, color: (x + y) % 100 === 0 ? '#333' : '#222' }); } } // Event listeners for keyboard window.addEventListener('keydown', (e) => { if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') keys.up = true; if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') keys.down = true; if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = true; if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = true; if (e.key === 'Shift') keys.shift = true; if (e.key === ' ') keys.space = true; }); window.addEventListener('keyup', (e) => { if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') keys.up = false; if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') keys.down = false; if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = false; if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = false; if (e.key === 'Shift') keys.shift = false; if (e.key === ' ') keys.space = false; }); // Start game loop gameLoop(); } // Update game state function update() { // Update player movement player.isMoving = false; if (keys.up) { player.speed = Math.min(player.speed + player.acceleration, player.maxSpeed * (keys.shift ? 2 : 1)); player.direction = 0; player.isMoving = true; } if (keys.down) { player.speed = Math.min(player.speed + player.acceleration, player.maxSpeed * (keys.shift ? 2 : 1)); player.direction = 2; player.isMoving = true; } if (keys.left) { player.speed = Math.min(player.speed + player.acceleration, player.maxSpeed * (keys.shift ? 2 : 1)); player.direction = 3; player.isMoving = true; } if (keys.right) { player.speed = Math.min(player.speed + player.acceleration, player.maxSpeed * (keys.shift ? 2 : 1)); player.direction = 1; player.isMoving = true; } // Apply friction if (!player.isMoving) { player.speed = Math.max(player.speed - player.deceleration, 0); } // Update player position based on direction and speed if (player.isMoving || player.speed > 0) { switch (player.direction) { case 0: // up player.y -= player.speed; break; case 1: // right player.x += player.speed; break; case 2: // down player.y += player.speed; break; case 3: // left player.x -= player.speed; break; } // Boundary check for player player.x = Math.max(player.width/2, Math.min(map.width - player.width/2, player.x)); player.y = Math.max(player.height/2, Math.min(map.height - player.height/2, player.y)); } // Update vehicles vehicles.forEach(vehicle => { switch (vehicle.direction) { case 0: // up vehicle.y -= vehicle.speed; if (vehicle.y < -vehicle.height) vehicle.y = map.height; break; case 1: // right vehicle.x += vehicle.speed; if (vehicle.x > map.width) vehicle.x = -vehicle.width; break; case 2: // down vehicle.y += vehicle.speed; if (vehicle.y > map.height) vehicle.y = -vehicle.height; break; case 3: // left vehicle.x -= vehicle.speed; if (vehicle.x < -vehicle.width) vehicle.x = map.width; break; } }); // Update pedestrians pedestrians.forEach(pedestrian => { switch (pedestrian.direction) { case 0: // up pedestrian.y -= pedestrian.speed; break; case 1: // right pedestrian.x += pedestrian.speed; break; case 2: // down pedestrian.y += pedestrian.speed; break; case 3: // left pedestrian.x -= pedestrian.speed; break; } // Randomly change direction if (Math.random() < 0.02) { pedestrian.direction = Math.floor(Math.random() * 4); } // Boundary checking for pedestrians if (pedestrian.x < 0) pedestrian.x = map.width; if (pedestrian.x > map.width) pedestrian.x = 0; if (pedestrian.y < 0) pedestrian.y = map.height; if (pedestrian.y > map.height) pedestrian.y = 0; }); // Update camera to follow player camera.x = player.x - camera.width / 2; camera.y = player.y - camera.height / 2; // Boundary checking for camera camera.x = Math.max(0, Math.min(map.width - camera.width, camera.x)); camera.y = Math.max(0, Math.min(map.height - camera.height, camera.y)); // Update UI speedElement.textContent = Math.floor(player.speed * 20); statusElement.textContent = player.isMoving ? (keys.shift ? "Sprinting" : "Moving") : "Idle"; } // Render game function render() { // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw roads roads.forEach(road => { ctx.fillStyle = road.color; ctx.fillRect(road.x - camera.x, road.y - camera.y, road.width, road.height); // Draw road lines if (road.width > road.height) { // Horizontal road ctx.fillStyle = '#fff'; for (let x = road.x; x < road.x + road.width; x += 40) { ctx.fillRect(x - camera.x, road.y + road.height/2 - 2 - camera.y, 20, 4); } } else { // Vertical road ctx.fillStyle = '#fff'; for (let y = road.y; y < road.y + road.height; y += 40) { ctx.fillRect(road.x + road.width/2 - 2 - camera.x, y - camera.y, 4, 20); } } }); // Draw buildings buildings.forEach(building => { ctx.fillStyle = building.color; ctx.fillRect(building.x - camera.x, building.y - camera.y, building.width, building.height); // Draw windows ctx.fillStyle = '#337'; for (let y = building.y + 10; y < building.y + building.height - 10; y += 20) { for (let x = building.x + 10; x < building.x + building.width - 10; x += 20) { ctx.fillRect(x - camera.x, y - camera.y, 8, 12); } } }); // Draw vehicles vehicles.forEach(vehicle => { ctx.fillStyle = vehicle.color; ctx.fillRect(vehicle.x - camera.x, vehicle.y - camera.y, vehicle.width, vehicle.height); // Draw wheels ctx.fillStyle = '#222'; ctx.fillRect(vehicle.x - camera.x, vehicle.y - camera.y, 4, 2); ctx.fillRect(vehicle.x - camera.x, vehicle.y + vehicle.height - 2 - camera.y, 4, 2); ctx.fillRect(vehicle.x + vehicle.width - 4 - camera.x, vehicle.y - camera.y, 4, 2); ctx.fillRect(vehicle.x + vehicle.width - 4 - camera.x, vehicle.y + vehicle.height - 2 - camera.y, 4, 2); }); // Draw pedestrians pedestrians.forEach(pedestrian => { ctx.fillStyle = pedestrian.color; ctx.beginPath(); ctx.arc(pedestrian.x - camera.x, pedestrian.y - camera.y, pedestrian.width/2, 0, Math.PI * 2); ctx.fill(); }); // Draw player ctx.fillStyle = player.color; ctx.fillRect(player.x - player.width/2 - camera.x, player.y - player.height/2 - camera.y, player.width, player.height); // Draw player direction indicator ctx.fillStyle = '#ff0'; const indicatorSize = 5; let indicatorX = player.x - camera.x; let indicatorY = player.y - camera.y; switch (player.direction) { case 0: // up indicatorY = player.y - player.height/2 - indicatorSize - camera.y; break; case 1: // right indicatorX = player.x + player.width/2 + camera.x; break; case 2: // down indicatorY = player.y + player.height/2 + indicatorSize - camera.y; break; case 3: // left indicatorX = player.x - player.width/2 - indicatorSize - camera.x; break; } ctx.fillRect(indicatorX - indicatorSize/2, indicatorY - indicatorSize/2, indicatorSize, indicatorSize); // Draw minimap renderMinimap(); } // Render minimap function renderMinimap() { // Clear minimap minimapCtx.clearRect(0, 0, minimap.width, minimap.height); // Draw map background const scaleX = minimap.width / map.width; const scaleY = minimap.height / map.height; // Draw tiles on minimap map.tiles.forEach(tile => { minimapCtx.fillStyle = tile.color; minimapCtx.fillRect( tile.x * scaleX, tile.y * scaleY, tile.width * scaleX, tile.height * scaleY ); }); // Draw roads on minimap roads.forEach(road => { minimapCtx.fillStyle = road.color; minimapCtx.fillRect( road.x * scaleX, road.y * scaleY, road.width * scaleX, road.height * scaleY ); }); // Draw buildings on minimap buildings.forEach(building => { minimapCtx.fillStyle = building.color; minimapCtx.fillRect( building.x * scaleX, building.y * scaleY, building.width * scaleX, building.height * scaleY ); }); // Draw vehicles on minimap vehicles.forEach(vehicle => { minimapCtx.fillStyle = vehicle.color; minimapCtx.fillRect( vehicle.x * scaleX, vehicle.y * scaleY, vehicle.width * scaleX, vehicle.height * scaleY ); }); // Draw pedestrians on minimap pedestrians.forEach(pedestrian => { minimapCtx.fillStyle = pedestrian.color; minimapCtx.beginPath(); minimapCtx.arc( pedestrian.x * scaleX, pedestrian.y * scaleY, pedestrian.width/2 * scaleX, 0, Math.PI * 2 ); minimapCtx.fill(); }); // Draw player on minimap minimapCtx.fillStyle = '#fff'; minimapCtx.fillRect( (player.x - player.width/2) * scaleX, (player.y - player.height/2) * scaleY, player.width * scaleX, player.height * scaleY ); // Draw camera view on minimap minimapCtx.strokeStyle = '#0af'; minimapCtx.lineWidth = 1; minimapCtx.strokeRect( camera.x * scaleX, camera.y * scaleY, camera.width * scaleX, camera.height * scaleY ); } // Game loop function gameLoop() { update(); render(); requestAnimationFrame(gameLoop); } // Start the game init(); </script> </body> </html> ``` ## Features of this GTA-style Game: 1. **Player Control**: - Move with arrow keys or WASD - Sprint with Shift key - Direction indicator shows where the player is facing 2. **City Environment**: - Grid-based city layout with roads and buildings - Buildings with window details - Road markings for realism 3. **NPCs**: - Moving vehicles that follow road paths - Randomly moving pedestrians 4. **Game Mechanics**: - Acceleration and deceleration physics - Collision boundaries with map edges - Camera that follows the player 5. **UI Elements**: - Speed display - Player status indicator - Minimap showing the entire city - Camera view indicator on minimap - On-screen controls reference 6. **Visual Design**: - Neon blue color scheme with dark backgrounds - Glowing effects and shadows - Clean, modern UI with transparency effects The game runs smoothly in any modern browser. You can move around the city, avoid collisions with buildings, and watch the minimap update in real-time as you explore.
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Mini-GTA (Top-Down Prototype)</title> <style> html,body{ margin:0; height:100%; overflow:hidden; background:#222; font-family:Arial,Helvetica,sans-serif; color:#fff; user-select:none; } #ui{ position:fixed; top:0; left:0; width:100%; height:100%; pointer-events:none; } #minimap{ position:absolute; right:10px; bottom:10px; width:160px; height:160px; border:2px solid #fff; background:#000; } #help{ position:absolute; left:10px; bottom:10px; font-size:14px; background:rgba(0,0,0,.4); padding:6px 10px; border-radius:4px; } canvas{display:block;margin:0 auto;background:#444;} </style> </head> <body> <canvas id="game" width="800" height="600"></canvas> <div id="ui"> <canvas id="minimap" width="160" height="160"></canvas> <div id="help"> Arrow Keys / WASD – Move<br> E – Enter / Exit Vehicle </div> </div> <script> /* ========= GLOBAL CONSTANTS ========= */ const MAP_W = 2000, MAP_H = 2000; // world size const TILE = 64; // base tile (for roads & buildings) const PLAYER_SIZE = 22; const PLAYER_SPEED = 2.2; const VEHICLE_SIZE = {w:40,h:70}; const VEHICLE_SPEED = 4.5; const PEDESTRIAN_SIZE = 18; const PEDESTRIAN_SPEED = 1.4; /* ========= HELPER FUNCTIONS ========= */ const rand = (a,b)=>Math.random()*(b-a)+a; const clamp = (v,min,max)=>Math.max(min,Math.min(max,v)); function rectsOverlap(a,b){ return !(a.x+a.w < b.x || a.x > b.x+b.w || a.y+a.h < b.y || a.y > b.y+b.h); } /* ========= INPUT ========= */ const keys={}; addEventListener('keydown',e=>{keys[e.key.toLowerCase()]=true}); addEventListener('keyup',e=>{keys[e.key.toLowerCase()]=false}); /* ========= CANVASES ========= */ const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); const miniCanvas = document.getElementById('minimap'); const mCtx = miniCanvas.getContext('2d'); /* ========= WORLD SETUP ========= */ const buildings=[]; for(let i=0;i<140;i++){ // generate some random buildings const w=rand(80,160), h=rand(80,160); const x=rand(0,MAP_W-w), y=rand(0,MAP_H-h); // snap to tile grid and leave roads clear if(((x/TILE)|0)%2===1 || ((y/TILE)|0)%2===1) continue; // keep roads free buildings.push({x,y,w,h}); } function makeAxisAlignedPath(y){ // simple horizontal traffic route return {from:{x:0,y},to:{x:MAP_W,y}}; } /* ========= ENTITIES ========= */ class Entity{ constructor(x,y,w,h){ this.x=x;this.y=y;this.w=w;this.h=h; } bbox(){return {x:this.x,y:this.y,w:this.w,h:this.h};} } class Player extends Entity{ constructor(){ super(MAP_W/2,MAP_H/2,PLAYER_SIZE,PLAYER_SIZE); this.speed=PLAYER_SPEED; this.inVehicle=null; this.animTimer=0; this.frame=0; } update(dt){ if(this.inVehicle){ // while inside vehicle, control it const v=this.inVehicle; let dx=0,dy=0; if(keys['w']||keys['arrowup']) dy-=1; if(keys['s']||keys['arrowdown']) dy+=1; if(keys['a']||keys['arrowleft']) dx-=1; if(keys['d']||keys['arrowright']) dx+=1; const len=Math.hypot(dx,dy); if(len){dx/=len;dy/=len;} v.x+=dx*VEHICLE_SPEED; v.y+=dy*VEHICLE_SPEED; // collision with buildings for(const b of buildings){ if(rectsOverlap(v,b)){ v.x-=dx*VEHICLE_SPEED; v.y-=dy*VEHICLE_SPEED; } } // exit vehicle if(keys['e']){ keys['e']=false; this.x=v.x+v.w/2 - this.w/2; this.y=v.y+v.h/2 - this.h/2; this.inVehicle=null; } // keep inside map v.x=clamp(v.x,0,MAP_W-v.w); v.y=clamp(v.y,0,MAP_H-v.h); }else{ let dx=0,dy=0; if(keys['w']||keys['arrowup']) dy-=1; if(keys['s']||keys['arrowdown']) dy+=1; if(keys['a']||keys['arrowleft']) dx-=1; if(keys['d']||keys['arrowright']) dx+=1; const len=Math.hypot(dx,dy); if(len){dx/=len;dy/=len;} this.x+=dx*this.speed; this.y+=dy*this.speed; // collision with buildings for(const b of buildings){ if(rectsOverlap(this,b)){ this.x-=dx*this.speed; this.y-=dy*this.speed; } } // enter vehicle? if(keys['e']){ for(const car of vehicles){ const expand=8; const zone={x:car.x-expand,y:car.y-expand,w:car.w+expand*2,h:car.h+expand*2}; if(rectsOverlap(this,zone)){ this.inVehicle=car; keys['e']=false; break; } } } // animation this.animTimer+=dt; if(this.animTimer>0.15){ this.animTimer=0; this.frame=(this.frame+1)%4; } // stay inside map this.x=clamp(this.x,0,MAP_W-this.w); this.y=clamp(this.y,0,MAP_H-this.h); } } draw(ctx,cx,cy){ if(this.inVehicle) return; // not visible ctx.save(); ctx.translate(this.x-cx, this.y-cy); ctx.fillStyle='#ffdf00'; ctx.fillRect(0,0,this.w,this.h); // simple leg animation ctx.fillStyle='#000'; if(this.frame%2){ ctx.fillRect(4,this.h-3,4,3); ctx.fillRect(this.w-8,this.h-3,4,3); }else{ ctx.fillRect(6,this.h-3,4,3); } ctx.restore(); } } class Vehicle extends Entity{ constructor(x,y,dir=1){ super(x,y,VEHICLE_SIZE.w,VEHICLE_SIZE.h); this.dir=dir; this.color=`hsl(${rand(0,360)},80%,50%)`; } update(){ if(player.inVehicle===this) return; // currently controlled this.y+=0; // vertical unchanged this.x+=this.dir*2.2; if(this.x<0 || this.x>MAP_W-this.w) this.dir*=-1; // simple collision with buildings for(const b of buildings){ if(rectsOverlap(this,b)){ this.dir*=-1; this.x+=this.dir*2.2; } } } draw(ctx,cx,cy){ ctx.save(); ctx.translate(this.x-cx, this.y-cy); ctx.fillStyle=this.color; ctx.fillRect(0,0,this.w,this.h); ctx.fillStyle='#222'; ctx.fillRect(4,4,this.w-8, this.h-8); // windshield ctx.restore(); } } class Pedestrian extends Entity{ constructor(x,y){ super(x,y,PEDESTRIAN_SIZE,PEDESTRIAN_SIZE); this.vx=rand(-1,1); this.vy=rand(-1,1); this.timer=rand(1,3); this.color='#00d6ff'; } update(dt){ this.timer-=dt; if(this.timer<0){ this.vx=rand(-1,1); this.vy=rand(-1,1); this.timer=rand(1,3); } const len=Math.hypot(this.vx,this.vy); if(len){ const nx=this.vx/len*PEDESTRIAN_SPEED; const ny=this.vy/len*PEDESTRIAN_SPEED; this.x+=nx; this.y+=ny; for(const b of buildings){ if(rectsOverlap(this,b)){ this.x-=nx; this.y-=ny; this.vx*=-1; this.vy*=-1; } } } this.x=clamp(this.x,0,MAP_W-this.w); this.y=clamp(this.y,0,MAP_H-this.h); } draw(ctx,cx,cy){ ctx.fillStyle=this.color; ctx.fillRect(this.x-cx,this.y-cy,this.w,this.h); } } /* ========= ENTITY LISTS ========= */ const player=new Player(); const vehicles=[]; for(let i=0;i<8;i++){ const x=rand(200,MAP_W-200); const y=((i%4)*TILE*2)+TILE*1.5; vehicles.push(new Vehicle(x,y,i%2?1:-1)); } const pedestrians=[]; for(let i=0;i<25;i++){ pedestrians.push(new Pedestrian(rand(0,MAP_W),rand(0,MAP_H))); } /* ========= CAMERA ========= */ let camX=0,camY=0; /* ========= MAIN LOOP ========= */ let last=0; function loop(ts){ const dt=(ts-last)/1000; last=ts; update(dt); render(); requestAnimationFrame(loop); } requestAnimationFrame(loop); /* ========= UPDATE ========= */ function update(dt){ player.update(dt); for(const v of vehicles) v.update(dt); for(const p of pedestrians) p.update(dt); // keep camera centered on player or vehicle const target=player.inVehicle?player.inVehicle:player; camX = clamp(target.x + target.w/2 - canvas.width/2,0, MAP_W-canvas.width); camY = clamp(target.y + target.h/2 - canvas.height/2,0, MAP_H-canvas.height); } /* ========= RENDER ========= */ function render(){ ctx.clearRect(0,0,canvas.width,canvas.height); drawGround(); drawBuildings(); for(const v of vehicles) v.draw(ctx,camX,camY); for(const p of pedestrians) p.draw(ctx,camX,camY); player.draw(ctx,camX,camY); drawMinimap(); } function drawGround(){ // simple checkered tiles: roads every 2nd tile const startX = Math.floor(camX/TILE)-1; const startY = Math.floor(camY/TILE)-1; const tilesX = Math.ceil(canvas.width/TILE)+2; const tilesY = Math.ceil(canvas.height/TILE)+2; for(let i=0;i<tilesX;i++){ for(let j=0;j<tilesY;j++){ const gx = (startX+i)*TILE; const gy = (startY+j)*TILE; const roadX = ((startX+i)%2)==1; const roadY = ((startY+j)%2)==1; ctx.fillStyle = (roadX||roadY)?'#666':'#3a3a3a'; ctx.fillRect(gx-camX, gy-camY, TILE, TILE); } } } function drawBuildings(){ ctx.fillStyle='#2b2b2b'; for(const b of buildings){ ctx.fillRect(b.x-camX,b.y-camY,b.w,b.h); ctx.strokeStyle='#111'; ctx.strokeRect(b.x-camX,b.y-camY,b.w,b.h); } } /* ========= MINIMAP ========= */ function drawMinimap(){ const scaleX = miniCanvas.width / MAP_W; const scaleY = miniCanvas.height / MAP_H; mCtx.fillStyle='#000'; mCtx.fillRect(0,0,miniCanvas.width,miniCanvas.height); // roads are implicit (skipped for speed) // buildings mCtx.fillStyle='#444'; for(const b of buildings){ mCtx.fillRect(b.x*scaleX,b.y*scaleY,b.w*scaleX,b.h*scaleY); } // vehicles mCtx.fillStyle='#ff5555'; for(const v of vehicles){ mCtx.fillRect(v.x*scaleX,v.y*scaleY,3,3); } // peds mCtx.fillStyle='#00baff'; for(const p of pedestrians){ mCtx.fillRect(p.x*scaleX,p.y*scaleY,2,2); } // player const target=player.inVehicle?player.inVehicle:player; mCtx.fillStyle='#ffff00'; mCtx.fillRect(target.x*scaleX,target.y*scaleY,4,4); mCtx.strokeStyle='#fff'; mCtx.strokeRect(0,0,miniCanvas.width,miniCanvas.height); } </script> </body> </html> ```
Drag to resize
Here is a complete, self-contained GTA-style top-down game clone. The entire game is written in a single block of HTML, including all CSS and JavaScript. You can save this code as an `.html` file and open it in any modern web browser to play. **Controls:** * **Move Player:** Arrow Keys or WASD * **Enter/Exit Vehicle:** 'F' key **Features:** * Character movement and camera that follows the player. * Walking animations with a 4-direction sprite sheet. * Enterable vehicles with basic driving physics. * Simple AI pedestrians that wander the map. * Collision detection with buildings. * An interactive minimap in the corner of the screen. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Top-Down Game</title> <style> body { background-color: #333; color: #fff; font-family: 'Courier New', Courier, monospace; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; flex-direction: column; overflow: hidden; } #game-container { width: 800px; height: 600px; border: 2px solid #fff; position: relative; overflow: hidden; background-color: #5a5a5a; } #world { position: absolute; background-image: linear-gradient(rgba(255, 255, 255, 0.07) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.07) 1px, transparent 1px); background-size: 30px 30px; } .game-object { position: absolute; box-sizing: border-box; } #player { width: 24px; height: 24px; /* Base64 encoded sprite sheet for the player character */ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIaSURBVHhe7doxb9pAFAZwl/8B/gI2QYtNQEGDAoUKEhQoSKgoiBAgQICEgAoFDRL8fwF0gA7+bLzYFnaxNTpD8vkgbN6d52dnZyYJAAAAAAAAAAC4LOfz+UW9s2n+M59PZV3P87pmWfcvSRJ9p0+S8L/+bM8F0tIHYE8B6UkzIIAFpCfdgAASYH3SDAhgAann/ZMGQADrSTcgQADqSbcgQADqSTcgQADqSbcgQADqSTcgQADqSbcgQADqSTcgQADqSbcgQADqSTcgQADqSbcgQADqSTcgQADqSbcgQADqSTcgQADqSbcgQADqSTcgQADqSTcgQADqSbcgQADqSTcgQADqSbcgAASYD7SDAhgAamH/0wYggH2gHTAACYD9QBswAAkgH2gHDGA+kG7AAOYD6QYMYD6QbsAA5gPpBgxgPpBuwADmA+kGDGA+kG7AAOYD6QYMYD6QbsAA5gPpBgxgPpBuwADmA+kGDGA+kG7AAOYD6QYMYD6QbsAA5gPpBgxgPpBuwADmA+kGDGACYDfQAAhgAmA30AACGACYDXSAAAhgAmA30AACGACYDXSAAAhgAmA30AACGHgaaIcGIIBDB91AAyCAQwfdQAMggEMH3UADIIBDB91AAyCAQwfdQAMggEMH3UADIIBDB91AAyCAQwfdQAMggEMH3UADIIBDB91AAyCAQwfdQAMggEMH3UADIIBDB91AAyCAQwfdQAMggEMH3UADIIBDB91AAyCAQwfdQAMggEMH3UADAAAAAAAAAAA4j599AO0E0eY2Xq0MAAAAAElFTkSuQmCC'); /* Size of the entire sprite sheet */ background-size: 96px 96px; /* Start with the first frame (standing still, facing down) */ background-position: 0px 0px; } .building { background-color: #404040; border: 2px solid #333; } .pedestrian { width: 20px; height: 20px; background-color: #f0f; border-radius: 50%; } .vehicle { width: 80px; height: 40px; background-color: #007bff; border: 2px solid #0056b3; box-shadow: 2px 2px 5px rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; } .vehicle::before { content: ''; width: 30px; height: 30px; background: rgba(173, 216, 230, 0.7); /* Light blue for windshield */ position: absolute; right: 5px; } #ui-container { position: absolute; bottom: 15px; left: 15px; z-index: 10; } #minimap-container { width: 160px; height: 120px; background-color: rgba(0, 0, 0, 0.6); border: 2px solid #fff; position: relative; overflow: hidden; } .minimap-dot { position: absolute; width: 4px; height: 4px; border-radius: 50%; } #minimap-player { background-color: #0f0; } .minimap-vehicle { background-color: #007bff; width: 6px; height: 3px; border-radius: 0; } #instructions { position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.5); padding: 10px; border-radius: 5px; font-size: 12px; text-align: right; } </style> </head> <body> <h1>Basic GTA Clone</h1> <div id="game-container"> <div id="world"> <!-- Game objects will be dynamically added here --> </div> <div id="ui-container"> <div id="minimap-container"> <!-- Minimap dots will be dynamically added here --> </div> </div> <div id="instructions"> - Arrow Keys / WASD to Move<br> - 'F' to Enter/Exit Vehicle </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { // --- GAME SETUP --- const gameContainer = document.getElementById('game-container'); const world = document.getElementById('world'); const minimap = document.getElementById('minimap-container'); const worldSize = { width: 3200, height: 2400 }; world.style.width = `${worldSize.width}px`; world.style.height = `${worldSize.height}px`; const keys = { w: false, a: false, s: false, d: false, ArrowUp: false, ArrowLeft: false, ArrowDown: false, ArrowRight: false, f: false }; // --- ENTITY SETUP --- const player = { x: worldSize.width / 2, y: worldSize.height / 2, width: 24, height: 24, speed: 4, el: createGameObject('player', 'game-object'), inVehicle: null, animation: { frame: 0, timer: 0, frameDelay: 5, // update frame every 5 game ticks direction: 0, // 0:down, 1:left, 2:right, 3:up } }; const entities = { buildings: [], vehicles: [], pedestrians: [] }; // --- HELPER FUNCTIONS --- function createGameObject(id, className) { const el = document.createElement('div'); el.id = id; el.className = className; world.appendChild(el); return el; } function createMinimapDot(id, className) { const el = document.createElement('div'); el.id = id; el.className = className; minimap.appendChild(el); return el; } // --- INITIALIZE GAME WORLD --- function init() { // Create Player world.appendChild(player.el); player.minimapDot = createMinimapDot('minimap-player', 'minimap-dot'); // Create Buildings const buildingLayout = [ { x: 200, y: 200, w: 200, h: 150 }, { x: 500, y: 300, w: 150, h: 300 }, { x: 800, y: 100, w: 100, h: 400 }, { x: 1200, y: 600, w: 500, h: 180 }, { x: 1300, y: 900, w: 200, h: 200 }, { x: 1800, y: 800, w: 250, h: 150 }, { x: 2200, y: 400, w: 180, h: 350 }, { x: 2500, y: 1000, w: 400, h: 100 }, { x: 100, y: 1500, w: 150, h: 600 }, { x: 600, y: 1800, w: 300, h: 150 }, ]; buildingLayout.forEach((b, i) => { const building = { x: b.x, y: b.y, width: b.w, height: b.h, el: createGameObject(`building-${i}`, 'game-object building') }; entities.buildings.push(building); }); // Create Vehicles const vehicleLayout = [ { x: 450, y: 250, angle: 0 }, { x: 950, y: 400, angle: Math.PI / 2 }, { x: 1600, y: 500, angle: Math.PI / 4 }, ]; vehicleLayout.forEach((v, i) => { const vehicle = { x: v.x, y: v.y, width: 80, height: 40, angle: v.angle, speed: 0, el: createGameObject(`vehicle-${i}`, 'game-object vehicle'), minimapDot: createMinimapDot(`minimap-vehicle-${i}`, 'minimap-dot minimap-vehicle') }; entities.vehicles.push(vehicle); }); // Create Pedestrians for (let i = 0; i < 20; i++) { const ped = { x: Math.random() * worldSize.width, y: Math.random() * worldSize.height, width: 20, height: 20, speed: 1 + Math.random(), el: createGameObject(`ped-${i}`, 'game-object pedestrian'), dx: (Math.random() - 0.5) * 2, dy: (Math.random() - 0.5) * 2, moveTimer: Math.random() * 200, }; // Ensure ped doesn't spawn inside a building let inBuilding = true; while (inBuilding) { inBuilding = false; for (const building of entities.buildings) { if (checkCollision(ped, building)) { ped.x = Math.random() * worldSize.width; ped.y = Math.random() * worldSize.height; inBuilding = true; break; } } } entities.pedestrians.push(ped); } gameLoop(); } // --- COLLISION DETECTION --- function checkCollision(rect1, rect2) { return ( rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y ); } // --- INPUT HANDLING --- document.addEventListener('keydown', (e) => { if (keys.hasOwnProperty(e.key)) keys[e.key] = true; }); document.addEventListener('keyup', (e) => { if (keys.hasOwnProperty(e.key)) keys[e.key] = false; }); // --- UPDATE FUNCTIONS --- function updatePlayer() { let dx = 0; let dy = 0; let isMoving = false; if (keys.a || keys.ArrowLeft) { dx -= 1; player.animation.direction = 1; isMoving = true; } if (keys.d || keys.ArrowRight) { dx += 1; player.animation.direction = 2; isMoving = true; } if (keys.w || keys.ArrowUp) { dy -= 1; player.animation.direction = 3; isMoving = true; } if (keys.s || keys.ArrowDown) { dy += 1; player.animation.direction = 0; isMoving = true; } // Normalize diagonal movement if (dx !== 0 && dy !== 0) { const magnitude = Math.sqrt(dx * dx + dy * dy); dx = (dx / magnitude); dy = (dy / magnitude); } const nextX = player.x + dx * player.speed; const nextY = player.y + dy * player.speed; // Collision checks const tempPlayerX = { ...player, x: nextX }; let collisionX = false; for (const building of entities.buildings) { if (checkCollision(tempPlayerX, building)) { collisionX = true; break; } } if (!collisionX) player.x = nextX; const tempPlayerY = { ...player, y: nextY }; let collisionY = false; for (const building of entities.buildings) { if (checkCollision(tempPlayerY, building)) { collisionY = true; break; } } if (!collisionY) player.y = nextY; // Clamp player position to world bounds player.x = Math.max(0, Math.min(worldSize.width - player.width, player.x)); player.y = Math.max(0, Math.min(worldSize.height - player.height, player.y)); // Animation if (isMoving) { player.animation.timer++; if (player.animation.timer >= player.animation.frameDelay) { player.animation.frame = (player.animation.frame + 1) % 4; player.animation.timer = 0; } } else { player.animation.frame = 0; // Standing frame } // Interaction if (keys.f) { for (const vehicle of entities.vehicles) { const dist = Math.hypot(player.x - vehicle.x, player.y - vehicle.y); if (dist < 80) { // Interaction radius player.inVehicle = vehicle; player.el.style.display = 'none'; break; } } keys.f = false; // Prevent holding key down } } function updateVehicle(vehicle) { // Steering const turnSpeed = 0.05; if (keys.a || keys.ArrowLeft) vehicle.angle -= turnSpeed * (vehicle.speed/5); if (keys.d || keys.ArrowRight) vehicle.angle += turnSpeed * (vehicle.speed/5); // Acceleration/Deceleration const acceleration = 0.1; const friction = 0.02; const maxSpeed = 8; if (keys.w || keys.ArrowUp) vehicle.speed = Math.min(maxSpeed, vehicle.speed + acceleration); if (keys.s || keys.ArrowDown) vehicle.speed = Math.max(-maxSpeed/2, vehicle.speed - acceleration); // Apply friction if (vehicle.speed > 0) vehicle.speed = Math.max(0, vehicle.speed - friction); if (vehicle.speed < 0) vehicle.speed = Math.min(0, vehicle.speed + friction); const nextX = vehicle.x + Math.cos(vehicle.angle) * vehicle.speed; const nextY = vehicle.y + Math.sin(vehicle.angle) * vehicle.speed; // Vehicle collision is simplified for this demo const tempVehicle = { x: nextX, y: nextY, width: vehicle.width, height: vehicle.height }; let collision = false; for (const building of entities.buildings) { if (checkCollision(tempVehicle, building)) { collision = true; break; } } if(!collision) { vehicle.x = nextX; vehicle.y = nextY; } else { vehicle.speed = -vehicle.speed * 0.5; // Bounce off } // Clamp to world bounds vehicle.x = Math.max(0, Math.min(worldSize.width - vehicle.width, vehicle.x)); vehicle.y = Math.max(0, Math.min(worldSize.height - vehicle.height, vehicle.y)); // Center player on vehicle player.x = vehicle.x; player.y = vehicle.y; // Exit vehicle if (keys.f) { player.el.style.display = 'block'; player.inVehicle = null; player.x = vehicle.x + vehicle.width; // Place player next to car player.y = vehicle.y; keys.f = false; } } function updatePedestrians() { entities.pedestrians.forEach(ped => { ped.moveTimer--; if (ped.moveTimer <= 0) { ped.dx = (Math.random() - 0.5) * 2; ped.dy = (Math.random() - 0.5) * 2; ped.moveTimer = 100 + Math.random() * 200; } const nextX = ped.x + ped.dx * ped.speed; const nextY = ped.y + ped.dy * ped.speed; // Collision (simplified check) const tempPed = { ...ped, x: nextX, y: nextY }; let collision = false; for (const building of entities.buildings) { if (checkCollision(tempPed, building)) { collision = true; break; } } if (!collision && nextX > 0 && nextX < worldSize.width - ped.width && nextY > 0 && nextY < worldSize.height - ped.height) { ped.x = nextX; ped.y = nextY; } else { // Change direction on collision ped.dx = -ped.dx; ped.dy = -ped.dy; } }); } // --- RENDER FUNCTION --- function render() { // Update camera to follow player/vehicle const camX = player.x - gameContainer.offsetWidth / 2; const camY = player.y - gameContainer.offsetHeight / 2; world.style.transform = `translate(${-camX}px, ${-camY}px)`; // Update player element if (player.inVehicle) { // Handled in updateVehicle } else { player.el.style.transform = `translate(${player.x}px, ${player.y}px)`; // Update sprite animation const frameSize = 24; // size of one animation frame const bgX = -(player.animation.frame * frameSize); const bgY = -(player.animation.direction * frameSize); player.el.style.backgroundPosition = `${bgX}px ${bgY}px`; } // Update all entities entities.buildings.forEach(b => { b.el.style.width = `${b.width}px`; b.el.style.height = `${b.height}px`; b.el.style.transform = `translate(${b.x}px, ${b.y}px)`; }); entities.vehicles.forEach(v => { v.el.style.transform = `translate(${v.x}px, ${v.y}px) rotate(${v.angle}rad)`; }); entities.pedestrians.forEach(p => { p.el.style.transform = `translate(${p.x}px, ${p.y}px)`; }); // Update minimap const scaleX = minimap.offsetWidth / worldSize.width; const scaleY = minimap.offsetHeight / worldSize.height; player.minimapDot.style.transform = `translate(${player.x * scaleX}px, ${player.y * scaleY}px)`; entities.vehicles.forEach(v => { v.minimapDot.style.transform = `translate(${v.x * scaleX}px, ${v.y * scaleY}px) rotate(${v.angle}rad)`; }); } // --- GAME LOOP --- function gameLoop() { // Update logic if (player.inVehicle) { updateVehicle(player.inVehicle); } else { updatePlayer(); } updatePedestrians(); // Render screen render(); requestAnimationFrame(gameLoop); } // Start the game init(); }); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Mini GTA-Style Top-Down Game</title> <style> body { background: #222; margin: 0; overflow: hidden; user-select: none; } #gameContainer { position: relative; width: 800px; height: 600px; margin: 40px auto; background: #444; border: 5px solid #111; box-shadow: 0 6px 32px #111b; } canvas { display: block; background: #87b0e6; } #minimap { position: absolute; width: 170px; height: 130px; right: 16px; bottom: 16px; border: 2px solid #333; background: #222c; z-index: 2; pointer-events: none; box-sizing: border-box; } @media (max-width: 900px) { #gameContainer { width: 100vw; height: 80vw; max-height: 80vh; max-width: 97vw; } #minimap { width: 22vw; height: 17vw; } } </style> </head> <body> <div id="gameContainer"> <canvas id="game" width="800" height="600"></canvas> <canvas id="minimap" width="170" height="130"></canvas> </div> <script> /** * --- Mini GTA-Style Top-Down Game Demo --- * Keys: WASD or Arrow Keys to move * Space near a vehicle to enter/exit * Player collides with buildings, vehicles, and pedestrians * Simple sprite "animations" (2-frame walking) * Vehicles and pedestrians move around the city */ // Map parameters const TILE_SIZE = 64; const MAP_COLS = 25; const MAP_ROWS = 20; // Definitions for buildings (rectangular "blocking" tiles) const buildings = [ {x:2, y:2, w:6, h:3}, {x:8, y:5, w:5, h:3}, {x:15, y:10, w:7, h:4}, {x:7, y:13, w:4, h:5}, {x:4, y:7, w:3, h:4}, {x:17, y:3, w:4, h:4}, {x:19, y:16, w:5, h:3} ]; // Player parameters const player = { x: TILE_SIZE*12 + TILE_SIZE/2, y: TILE_SIZE*10 + TILE_SIZE/2, w: 28, h: 32, speed: 2.4, spriteIdx: 0, walking: false, dir: "down", // up,down,left,right inVehicle: false, ridingVehicle: null }; // Vehicle templates const vehicleTypes = [ { color: "#a33", w:46, h:24, speed:1.9, seatOffset: {x:18, y:12} }, { color: "#2ea", w:40, h:18, speed:2.2, seatOffset: {x:13, y:9} } ]; // Vehicles const vehicles = [ {x: 15*TILE_SIZE + 20, y: 7*TILE_SIZE+30, dir: 0, vtype: 0, vx:0, vy:0, passenger: null}, {x: 22*TILE_SIZE + 20, y: 16*TILE_SIZE+10, dir: 90, vtype: 1, vx:0, vy:0, passenger: null} ]; // Pedestrians const PED_COLORS = ["#f4e", "#ed6", "#ace", "#ada", "#faf", "#fe9"]; let pedestrians = []; for(let i=0; i<6; i++) { const col = Math.floor(Math.random()*MAP_COLS); const row = Math.floor(Math.random()*MAP_ROWS); const px = (col+0.5)*TILE_SIZE, py = (row+0.5)*TILE_SIZE; pedestrians.push({ x: px + (Math.random()-0.5)*TILE_SIZE*0.8, y: py + (Math.random()-0.5)*TILE_SIZE*0.8, w: 24, h: 29, speed: 1.2+Math.random(), color: PED_COLORS[i%PED_COLORS.length], dir:"down", spriteIdx: 0, walking: false, aiTimer: 0, target: null }); } // Game canvas const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); const minimap = document.getElementById('minimap'); const mctx = minimap.getContext('2d'); const keys = {}; const keymap = { "w":"up","a":"left","s":"down","d":"right", "arrowup":"up","arrowleft":"left","arrowdown":"down","arrowright":"right" }; // Main game loop data let lastNow = performance.now(); let animFrame = 0; // Utility: pixel collision rectangle function rectsCollide(a, b) { return !( ((a.y + a.h) <= b.y) || (a.y >= (b.y + b.h)) || ((a.x + a.w) <= b.x) || (a.x >= (b.x + b.w)) ); } // Map tile drawing (roads and pavement) function drawMap(ctx, offsetX=0, offsetY=0) { // Road ctx.fillStyle = "#2d3035"; ctx.fillRect(0, 0, MAP_COLS*TILE_SIZE, MAP_ROWS*TILE_SIZE); // Pavement blocks for(let y=0; y<MAP_ROWS; y++) for(let x=0; x<MAP_COLS; x++) { ctx.strokeStyle = "#bdbdbd"; ctx.lineWidth = 0.5; ctx.strokeRect( x*TILE_SIZE + 4, y*TILE_SIZE + 4, TILE_SIZE-8, TILE_SIZE-8 ); } // Buildings for(const b of buildings) { ctx.fillStyle = "#5b5a4d"; ctx.fillRect(b.x*TILE_SIZE, b.y*TILE_SIZE, b.w*TILE_SIZE, b.h*TILE_SIZE); ctx.strokeStyle = "#423"; ctx.lineWidth = 2; ctx.strokeRect(b.x*TILE_SIZE, b.y*TILE_SIZE, b.w*TILE_SIZE, b.h*TILE_SIZE); // Doors ctx.fillStyle="#fff8"; ctx.fillRect((b.x+0.5)*TILE_SIZE-8, (b.y+b.h)*TILE_SIZE-8, 16, 16); } // Map road lines ctx.strokeStyle = "#fc3"; for(let i=2; i<MAP_ROWS; i+=4) { ctx.setLineDash([18,22]); ctx.beginPath(); ctx.moveTo(0, i*TILE_SIZE+TILE_SIZE/2); ctx.lineTo(MAP_COLS*TILE_SIZE, i*TILE_SIZE+TILE_SIZE/2); ctx.stroke(); } ctx.setLineDash([]); } // Player's movement and collisions function tryMove(entity, dx, dy, isPlayer=false) { let nx = entity.x+dx, ny = entity.y+dy; const entRect = {x:nx-entity.w/2, y:ny-entity.h/2, w:entity.w, h:entity.h}; // Map limits if(entRect.x<0 || entRect.y<0 || entRect.x+entity.w > MAP_COLS*TILE_SIZE || entRect.y+entity.h > MAP_ROWS*TILE_SIZE) return false; // Buildings for(const b of buildings) { const rect = { x:b.x*TILE_SIZE, y:b.y*TILE_SIZE, w:b.w*TILE_SIZE, h:b.h*TILE_SIZE }; if(rectsCollide(entRect,rect)) return false; } // Vehicles for(const v of vehicles) { if(isPlayer && player.inVehicle && v === player.ridingVehicle) continue; if(v.passenger && entity===v.passenger) continue; const vdef = vehicleTypes[v.vtype]; const rect = { x: v.x-vdef.w/2, y: v.y-vdef.h/2, w: vdef.w, h: vdef.h }; if(rectsCollide(entRect,rect)) return false; } // Pedestrians if(isPlayer) { for(const ped of pedestrians) { if(rectsCollide(entRect,{ x: ped.x-ped.w/2, y: ped.y-ped.h/2, w: ped.w, h: ped.h })) return false; } } // All clear! entity.x = nx; entity.y = ny; return true; } // Player controls and animation frame function updatePlayer(dt) { if(player.inVehicle && player.ridingVehicle) { // Vehicle movement let mdx = 0, mdy = 0; if(keys["w"]||keys["arrowup"]) mdy -= 1; if(keys["s"]||keys["arrowdown"]) mdy += 1; if(keys["a"]||keys["arrowleft"]) mdx -= 1; if(keys["d"]||keys["arrowright"]) mdx += 1; let magnitude = Math.hypot(mdx,mdy); let moved = false; if(magnitude) { mdx /= magnitude; mdy /= magnitude; const vdef = vehicleTypes[player.ridingVehicle.vtype]; const speed = vdef.speed * 1.25; const dx = mdx*speed, dy=mdy*speed; // Try vehicle move (collision w/buildings, other vehicles) let nx = player.ridingVehicle.x+dx, ny = player.ridingVehicle.y+dy; const vrect = { x:nx-vdef.w/2, y:ny-vdef.h/2, w:vdef.w, h:vdef.h }; let hit = false; // Hit buildings? for(const b of buildings) { const rect = { x:b.x*TILE_SIZE, y:b.y*TILE_SIZE, w:b.w*TILE_SIZE, h:b.h*TILE_SIZE }; if(rectsCollide(vrect,rect)) { hit = true; break; } } // Hit other vehicles? for(const v of vehicles) { if(v === player.ridingVehicle) continue; const vt = vehicleTypes[v.vtype]; const v2rect = { x: v.x-vt.w/2, y: v.y-vt.h/2, w: vt.w, h: vt.h }; if(rectsCollide(vrect, v2rect)) { hit=true; break; } } if(!hit) { player.ridingVehicle.x=nx; player.ridingVehicle.y=ny; moved = true; } } } else { // Player on foot let mdx = 0, mdy = 0; if(keys["w"]||keys["arrowup"]) mdy -= 1; if(keys["s"]||keys["arrowdown"]) mdy += 1; if(keys["a"]||keys["arrowleft"]) mdx -= 1; if(keys["d"]||keys["arrowright"]) mdx += 1; let magnitude = Math.hypot(mdx,mdy); player.walking = magnitude>0; if(magnitude) { mdx /= magnitude; mdy /= magnitude; if(Math.abs(mdx)>Math.abs(mdy)) { player.dir = mdx<0 ? "left":"right"; } else { player.dir = mdy<0 ? "up":"down"; } tryMove(player, mdx*player.speed, mdy*player.speed, true); } } } // Vehicles simple AI - random driving function updateVehicles(dt) { vehicles.forEach(v=>{ if(v.passenger) return; // player driving // Random moving: choose dir every 3s if(!v.aiTimer || v.aiTimer<=0) { v.aiTimer = 180+Math.random()*180; const angle = Math.random()*Math.PI*2; v.vx = Math.cos(angle)*(1.2+Math.random()*0.5); v.vy = Math.sin(angle)*(1.2+Math.random()*0.5); } else { v.aiTimer -= dt; } // Try to move const vdef = vehicleTypes[v.vtype]; let nx = v.x+v.vx, ny = v.y+v.vy; let vrect = {x:nx-vdef.w/2, y:ny-vdef.h/2, w:vdef.w, h:vdef.h}; // Hit test buildings, other cars let blocked=false; for(const b of buildings) { const rect = { x:b.x*TILE_SIZE, y:b.y*TILE_SIZE, w:b.w*TILE_SIZE, h:b.h*TILE_SIZE }; if(rectsCollide(vrect,rect)) { blocked=true; break;} } for(const v2 of vehicles) { if(v2===v) continue; const v2def = vehicleTypes[v2.vtype]; const rect2 = {x:v2.x-v2def.w/2, y:v2.y-v2def.h/2, w:v2def.w, h:v2def.h}; if(rectsCollide(vrect,rect2)) { blocked=true; break;} } if(!blocked) { v.x = nx; v.y = ny; } else { // Bump off v.vx *= -1; v.vy *= -1; } }); } // Pedestrians simple AI - random walking function updatePedestrians(dt) { pedestrians.forEach(ped=>{ if(ped.aiTimer<=0 || !ped.target) { // Pick new random point within a region ped.target = { x: ped.x + (Math.random()-0.5)*TILE_SIZE*4, y: ped.y + (Math.random()-0.5)*TILE_SIZE*4 }; ped.aiTimer = 40 + Math.random()*100; ped.walking = Math.random()>0.3; } else ped.aiTimer -= dt; // Walk to target if(ped.walking) { let dx = ped.target.x-ped.x, dy = ped.target.y-ped.y; if(Math.hypot(dx,dy)<3) { ped.walking=false; pedal=0; return; } const mag=Math.hypot(dx,dy); if(mag) { dx/=mag; dy/=mag; if(Math.abs(dx)>Math.abs(dy)) ped.dir = dx<0 ? "left":"right"; else ped.dir = dy<0 ? "up":"down"; tryMove(ped, dx*ped.speed, dy*ped.speed); } } }); } // Animate and render player function drawPlayer(ctx, offsetX, offsetY) { const px=player.x-offsetX, py=player.y-offsetY; // Body ctx.save(); ctx.translate(px, py); // Draw shadow ctx.beginPath(); ctx.ellipse(0,14,13,7,0,0,Math.PI*2); ctx.fillStyle="rgba(0,0,0,0.3)"; ctx.fill(); // Body shape and 2-frame animation ctx.fillStyle = "#46c"; // Animate motion (vertical "bob") let bob = (player.walking ? Math.abs(Math.sin(animFrame/7))*3:0); ctx.save(); ctx.translate(0,bob); // Draw head ctx.beginPath(); ctx.ellipse(0,-10,10,12,0,0,2*Math.PI); ctx.fillStyle = "#eac"; ctx.fill(); // Arms ctx.strokeStyle="#eac"; ctx.lineWidth=4; ctx.beginPath(); if(player.walking && (animFrame%18)<9) { ctx.moveTo(-11,-5); ctx.lineTo(-15,14); ctx.moveTo(11,-5); ctx.lineTo(15,14); } else { ctx.moveTo(-11,-5); ctx.lineTo(-15,8); ctx.moveTo(11,-5); ctx.lineTo(15,8); } ctx.stroke(); // Body ctx.fillStyle="#46c"; ctx.fillRect(-11,-2,22,25); // Legs ctx.save(); ctx.translate(0,23); if(player.walking) { const sw = (animFrame%18)<9 ? -6 : 6; ctx.rotate(sw/50); ctx.fillRect(-6,0,10,10); ctx.rotate(-sw/30); ctx.fillRect(2,0,10,10); } else { ctx.fillRect(-6,0,10,10); ctx.fillRect(2,0,10,10); } ctx.restore(); ctx.restore(); // Outline ctx.strokeStyle="#222b"; ctx.lineWidth=2.5; ctx.strokeRect(-player.w/2,-player.h/2,player.w,player.h); ctx.restore(); } // Drawing vehicles function drawVehicle(ctx, v, offsetX, offsetY) { const vdef = vehicleTypes[v.vtype]; const x = v.x-offsetX, y = v.y-offsetY; ctx.save(); ctx.translate(x,y); ctx.rotate((v.dir||0)*Math.PI/180); // Shadow ctx.save(); ctx.globalAlpha=0.33; ctx.fillStyle="#000"; ctx.ellipse(0, 5, vdef.w/2, vdef.h/2, 0, 0, Math.PI*2); ctx.fill(); ctx.restore(); // Body ctx.fillStyle = vdef.color; ctx.fillRect(-vdef.w/2, -vdef.h/2, vdef.w, vdef.h); // Roof highlight ctx.fillStyle="#fff6"; ctx.fillRect(-vdef.w/4, -vdef.h/4, vdef.w/2, vdef.h/2); // Wheels ctx.fillStyle="#232"; ctx.fillRect(-vdef.w/2, -vdef.h/2+3,4,6); ctx.fillRect(vdef.w/2-4, -vdef.h/2+3,4,6); ctx.fillRect(-vdef.w/2, vdef.h/2-6-3,4,6); ctx.fillRect(vdef.w/2-4, vdef.h/2-6-3,4,6); // Player inside car if(v.passenger) { ctx.beginPath(); ctx.arc(vdef.seatOffset.x-vdef.w/2, vdef.seatOffset.y-vdef.h/2, 8,0,Math.PI*2); ctx.fillStyle="#eac"; ctx.fill(); } ctx.restore(); } // Drawing pedestrians function drawPedestrian(ctx, ped, offsetX, offsetY) { const x=ped.x-offsetX, y=ped.y-offsetY; ctx.save(); ctx.translate(x,y); // Shadow ctx.beginPath(); ctx.ellipse(0,12,10,5,0,0,Math.PI*2); ctx.fillStyle = "rgba(0,0,0,0.24)"; ctx.fill(); let bob = (ped.walking ? Math.abs(Math.sin(animFrame/7))*2:0); ctx.save(); ctx.translate(0,bob); // Head ctx.beginPath(); ctx.ellipse(0,-8,8,10,0,0,Math.PI*2); ctx.fillStyle = "#d0b"; ctx.fill(); // Body ctx.fillStyle = ped.color; ctx.fillRect(-9,-2,18,18); // Legs ctx.fillStyle = "#fcf"; ctx.fillRect(-7,16, 5,8); ctx.fillRect(2,16, 5,8); ctx.restore(); // Outline ctx.strokeStyle="#222c"; ctx.strokeRect(-ped.w/2,-ped.h/2,ped.w,ped.h); ctx.restore(); } // Center camera on player (minimap and main) function getCamera() { let camX=player.x-canvas.width/2, camY=player.y-canvas.height/2; camX=Math.max(0,Math.min(camX,MAP_COLS*TILE_SIZE-canvas.width)); camY=Math.max(0,Math.min(camY,MAP_ROWS*TILE_SIZE-canvas.height)); return {x:camX, y:camY}; } // Minimap rendering function drawMinimap() { mctx.clearRect(0,0,minimap.width, minimap.height); // Scale const scaleX = minimap.width/(MAP_COLS*TILE_SIZE); const scaleY = minimap.height/(MAP_ROWS*TILE_SIZE); // Roads mctx.fillStyle="#2c2"; mctx.fillRect(0,0,minimap.width,minimap.height); // Buildings for(const b of buildings) { mctx.fillStyle="#432"; mctx.fillRect( b.x*TILE_SIZE*scaleX, b.y*TILE_SIZE*scaleY, b.w*TILE_SIZE*scaleX, b.h*TILE_SIZE*scaleY ); } // Vehicles for(const v of vehicles) { const vdef = vehicleTypes[v.vtype]; mctx.fillStyle=vdef.color; mctx.fillRect( (v.x-vdef.w/2)*scaleX, (v.y-vdef.h/2)*scaleY, vdef.w*scaleX, vdef.h*scaleY); } // Pedestrians for(const ped of pedestrians) { mctx.fillStyle=ped.color; mctx.beginPath(); mctx.arc( ped.x*scaleX, ped.y*scaleY, 7*scaleX,0,2*Math.PI ); mctx.fill(); } // Player mctx.strokeStyle="#fff"; mctx.lineWidth=2; mctx.beginPath(); mctx.arc(player.x*scaleX, player.y*scaleY,8*scaleX,0,2*Math.PI); mctx.stroke(); mctx.beginPath(); mctx.arc(player.x*scaleX, player.y*scaleY,4*scaleX,0,2*Math.PI); mctx.fillStyle="#49f"; mctx.fill(); } // Game logic and rendering main loop function gameLoop(now) { const dt = (now-lastNow)*0.08; lastNow=now; animFrame++; updatePlayer(dt); updateVehicles(dt); updatePedestrians(dt); // Check if near vehicle let nearVehicle = false, targetVehicle = null; if(!player.inVehicle) { for(const v of vehicles) { const vdef = vehicleTypes[v.vtype]; const dist = Math.hypot( player.x-v.x, player.y-v.y ); if(dist<35+vdef.w/2) { nearVehicle=true; targetVehicle = v; break; } } } // CAMERA const cam = getCamera(); // Main rendering ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw map drawMap(ctx,cam.x,cam.y); // Draw vehicles vehicles.forEach(v=>drawVehicle(ctx,v,cam.x,cam.y)); // Draw pedestrians pedestrians.forEach(ped=>drawPedestrian(ctx,ped,cam.x,cam.y)); // Draw player or if in vehicle, draw above vehicle if(player.inVehicle && player.ridingVehicle) { // Draw player on seat, on top of car drawVehicle(ctx,player.ridingVehicle,cam.x,cam.y); drawPlayer(ctx,cam.x,cam.y); } else { drawPlayer(ctx,cam.x,cam.y); if(nearVehicle && targetVehicle) { // Hint: "Press Space to Enter" ctx.save(); ctx.font="19px Arial Black"; ctx.textAlign="center"; ctx.strokeStyle="#000f"; ctx.lineWidth=5; ctx.strokeText("Press SPACE to enter car",canvas.width/2,canvas.height/2-60); ctx.fillStyle="#fff"; ctx.fillText("Press SPACE to enter car",canvas.width/2,canvas.height/2-60); ctx.restore(); } } // HUD (minimap) drawMinimap(); // Exit hint if(player.inVehicle) { ctx.save(); ctx.font="19px Arial Black"; ctx.textAlign="center"; ctx.strokeStyle="#000f"; ctx.lineWidth=5; ctx.strokeText("Press SPACE to exit",canvas.width/2,canvas.height/2-60); ctx.fillStyle="#fff"; ctx.fillText("Press SPACE to exit",canvas.width/2,canvas.height/2-60); ctx.restore(); } requestAnimationFrame(gameLoop); } // Interaction - Enter/Exit vehicles window.addEventListener("keydown",e=>{ keys[e.key.toLowerCase()]=true; // Interact: Enter/Exit vehicles if(e.code==="Space") { if(player.inVehicle && player.ridingVehicle) { // Exit: Place player beside vehicle, free seat const v = player.ridingVehicle; const vdef = vehicleTypes[v.vtype]; const outX = v.x+vdef.w/2+10, outY = v.y; // Place player here if not blocking player.x = outX; player.y = outY; player.inVehicle = false; v.passenger=null; player.ridingVehicle = null; } else if(!player.inVehicle) { // Near a vehicle? for(const v of vehicles) { const vdef = vehicleTypes[v.vtype]; const dist = Math.hypot( player.x-v.x, player.y-v.y ); if(dist<35+vdef.w/2 && !v.passenger) { // Enter it! // Move player into seat player.x = v.x+vdef.seatOffset.x-vdef.w/2; player.y = v.y+vdef.seatOffset.y-vdef.h/2; player.inVehicle = true; v.passenger = player; player.ridingVehicle = v; break; } } } e.preventDefault(); } }); // Keyup for motion window.addEventListener("keyup",e=>{ keys[e.key.toLowerCase()]=false; }); // Start game drawMap(ctx,0,0); gameLoop(performance.now()); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>GTA-Style Top-Down Game Clone</title> <style> body { margin: 0; background-color: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; } #gameCanvas { border: 1px solid black; background-color: #ffffff; } #miniMap { position: absolute; top: 10px; right: 10px; border: 1px solid black; background-color: #ffffff; } </style> </head> <body> <canvas id="gameCanvas" width="800" height="600"></canvas> <canvas id="miniMap" width="200" height="150"></canvas> <script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const miniCanvas = document.getElementById('miniMap'); const miniCtx = miniCanvas.getContext('2d'); // World settings const WORLD_WIDTH = 2000; const WORLD_HEIGHT = 2000; const VIEW_WIDTH = canvas.width; const VIEW_HEIGHT = canvas.height; // Player let player = { x: WORLD_WIDTH / 2, y: WORLD_HEIGHT / 2, size: 10, speed: 3, dx: 0, dy: 0, direction: 'down', // up, down, left, right walking: false, frame: 0, // for animation inVehicle: false, vehicle: null }; // Buildings (static obstacles) const buildings = [ {x: 400, y: 400, width: 200, height: 200}, {x: 800, y: 600, width: 150, height: 150}, {x: 1200, y: 300, width: 300, height: 100}, {x: 1500, y: 800, width: 100, height: 300}, // Add more for a city feel {x: 200, y: 1000, width: 250, height: 150}, {x: 600, y: 1200, width: 180, height: 220}, {x: 1000, y: 1400, width: 200, height: 200}, {x: 1400, y: 500, width: 150, height: 250} ]; // Vehicles const vehicles = [ {x: 500, y: 500, width: 40, height: 20, color: 'blue', dx: 1, dy: 0, occupied: false}, {x: 700, y: 700, width: 40, height: 20, color: 'red', dx: 0, dy: 1, occupied: false}, {x: 900, y: 900, width: 40, height: 20, color: 'green', dx: -1, dy: 0, occupied: false} ]; // Pedestrians const pedestrians = [ {x: 600, y: 600, size: 8, color: 'purple', dx: 0.5, dy: 0.5, frame: 0}, {x: 800, y: 800, size: 8, color: 'orange', dx: -0.5, dy: 0.5, frame: 0}, {x: 1000, y: 400, size: 8, color: 'yellow', dx: 0.5, dy: -0.5, frame: 0} ]; // Keyboard controls const keys = {}; window.addEventListener('keydown', (e) => { keys[e.key.toLowerCase()] = true; }); window.addEventListener('keyup', (e) => { keys[e.key.toLowerCase()] = false; }); // Game loop function update() { handleInput(); movePlayer(); moveVehicles(); movePedestrians(); checkCollisions(); draw(); drawMiniMap(); requestAnimationFrame(update); } function handleInput() { player.dx = 0; player.dy = 0; player.walking = false; let speed = player.inVehicle ? 5 : player.speed; if (keys['w'] || keys['arrowup']) { player.dy = -speed; player.direction = 'up'; player.walking = true; } if (keys['s'] || keys['arrowdown']) { player.dy = speed; player.direction = 'down'; player.walking = true; } if (keys['a'] || keys['arrowleft']) { player.dx = -speed; player.direction = 'left'; player.walking = true; } if (keys['d'] || keys['arrowright']) { player.dx = speed; player.direction = 'right'; player.walking = true; } // Interact with vehicle (enter/exit on space) if (keys[' ']) { if (player.inVehicle) { // Exit vehicle player.inVehicle = false; player.vehicle.occupied = false; player.x = player.vehicle.x + player.vehicle.width + 10; player.y = player.vehicle.y; player.vehicle.dx = 0; player.vehicle.dy = 0; player.vehicle = null; } else { // Check for nearby vehicle to enter for (let veh of vehicles) { if (!veh.occupied && Math.hypot(player.x - (veh.x + veh.width/2), player.y - (veh.y + veh.height/2)) < 20) { player.inVehicle = true; player.vehicle = veh; veh.occupied = true; break; } } } keys[' '] = false; // Prevent continuous triggering } } function movePlayer() { let moveX = player.dx; let moveY = player.dy; if (player.inVehicle) { player.vehicle.dx = player.dx / player.speed * 5; // Vehicle speed player.vehicle.dy = player.dy / player.speed * 5; player.x = player.vehicle.x + player.vehicle.width / 2; player.y = player.vehicle.y + player.vehicle.height / 2; } else { player.x += moveX; player.y += moveY; } // Boundaries player.x = Math.max(player.size, Math.min(player.x, WORLD_WIDTH - player.size)); player.y = Math.max(player.size, Math.min(player.y, WORLD_HEIGHT - player.size)); // Animation frame if (player.walking) { player.frame = (player.frame + 0.2) % 2; } else { player.frame = 0; } } function moveVehicles() { vehicles.forEach(veh => { if (!veh.occupied) { veh.x += veh.dx; veh.y += veh.dy; // Simple AI: bounce off edges if (veh.x < 0 || veh.x > WORLD_WIDTH - veh.width) veh.dx *= -1; if (veh.y < 0 || veh.y > WORLD_HEIGHT - veh.height) veh.dy *= -1; } else if (player.vehicle !== veh) { veh.x += veh.dx; veh.y += veh.dy; } }); } function movePedestrians() { pedestrians.forEach(ped => { ped.x += ped.dx; ped.y += ped.dy; // Bounce off edges if (ped.x < 0 || ped.x > WORLD_WIDTH) ped.dx *= -1; if (ped.y < 0 || ped.y > WORLD_HEIGHT) ped.dy *= -1; // Animation ped.frame = (ped.frame + 0.1) % 2; }); } function checkCollisions() { // Player vs Buildings buildings.forEach(build => { if (player.inVehicle) { if (checkRectCollision(player.vehicle, build)) { // Bounce back player.vehicle.x -= player.vehicle.dx; player.vehicle.y -= player.vehicle.dy; player.x = player.vehicle.x + player.vehicle.width / 2; player.y = player.vehicle.y + player.vehicle.height / 2; } } else { if (player.x + player.size > build.x && player.x - player.size < build.x + build.width && player.y + player.size > build.y && player.y - player.size < build.y + build.height) { player.x -= player.dx; player.y -= player.dy; } } }); // Player vs Pedestrians (simple bump) pedestrians.forEach(ped => { if (Math.hypot(player.x - ped.x, player.y - ped.y) < player.size + ped.size) { // Bump: reverse direction ped.dx *= -1; ped.dy *= -1; } }); // Vehicles vs Buildings (for non-occupied) vehicles.forEach(veh => { if (!veh.occupied) { buildings.forEach(build => { if (checkRectCollision(veh, build)) { veh.x -= veh.dx; veh.y -= veh.dy; veh.dx *= -1; veh.dy *= -1; } }); } }); } function checkRectCollision(rect1, rect2) { return (rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y); } function draw() { ctx.clearRect(0, 0, VIEW_WIDTH, VIEW_HEIGHT); // Camera follows player const camX = player.x - VIEW_WIDTH / 2; const camY = player.y - VIEW_HEIGHT / 2; // Draw map (roads as grid) ctx.fillStyle = '#333'; // Roads for (let i = 0; i < WORLD_WIDTH; i += 100) { ctx.fillRect(i - camX, 0 - camY, 50, WORLD_HEIGHT); ctx.fillRect(0 - camX, i - camY, WORLD_WIDTH, 50); } // Draw buildings ctx.fillStyle = 'gray'; buildings.forEach(build => { ctx.fillRect(build.x - camX, build.y - camY, build.width, build.height); }); // Draw vehicles vehicles.forEach(veh => { ctx.fillStyle = veh.color; ctx.fillRect(veh.x - camX, veh.y - camY, veh.width, veh.height); // Wheels ctx.fillStyle = 'black'; ctx.fillCircle(veh.x + 5 - camX, veh.y + veh.height - camX, 3); // Simplified fillCircle function below ctx.fillCircle(veh.x + veh.width - 5 - camX, veh.y + veh.height - camX, 3); }); // Draw pedestrians (simple stick figure with animation) pedestrians.forEach(ped => { drawStickFigure(ped.x - camX, ped.y - camY, ped.size, ped.color, ped.frame); }); // Draw player (stick figure with animation, or vehicle if in one) if (player.inVehicle) { // Player is inside vehicle, don't draw separately } else { drawStickFigure(player.x - camX, player.y - camY, player.size, 'red', player.frame, player.direction); } } function drawStickFigure(x, y, size, color, frame, direction = 'down') { ctx.fillStyle = color; // Head ctx.beginPath(); ctx.arc(x, y - size, size / 2, 0, 2 * Math.PI); ctx.fill(); // Body ctx.beginPath(); ctx.moveTo(x, y - size / 2); ctx.lineTo(x, y + size / 2); ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke(); // Arms ctx.beginPath(); ctx.moveTo(x - size / 2, y); ctx.lineTo(x + size / 2, y); ctx.stroke(); // Legs animation const legOffset = frame > 1 ? -size / 4 : size / 4; ctx.beginPath(); ctx.moveTo(x, y + size / 2); ctx.lineTo(x - size / 4 + legOffset, y + size); ctx.moveTo(x, y + size / 2); ctx.lineTo(x + size / 4 - legOffset, y + size); ctx.stroke(); } // Utility for filling circle (since fillCircle isn't native) CanvasRenderingContext2D.prototype.fillCircle = function(x, y, r) { this.beginPath(); this.arc(x, y, r, 0, 2 * Math.PI); this.fill(); }; function drawMiniMap() { miniCtx.clearRect(0, 0, 200, 150); const scaleX = 200 / WORLD_WIDTH; const scaleY = 150 / WORLD_HEIGHT; // Draw buildings miniCtx.fillStyle = 'gray'; buildings.forEach(build => { miniCtx.fillRect(build.x * scaleX, build.y * scaleY, build.width * scaleX, build.height * scaleY); }); // Draw player miniCtx.fillStyle = 'red'; miniCtx.fillCircle(player.x * scaleX, player.y * scaleY, 2); // Draw vehicles vehicles.forEach(veh => { miniCtx.fillStyle = veh.color; miniCtx.fillRect(veh.x * scaleX, veh.y * scaleY, veh.width * scaleX, veh.height * scaleY); }); // Draw pedestrians pedestrians.forEach(ped => { miniCtx.fillStyle = ped.color; miniCtx.fillCircle(ped.x * scaleX, ped.y * scaleY, 1); }); } update(); </script> </body> </html> ```
Drag to resize
# GTA-Style Top-Down Game Clone I've created a complete GTA-style top-down game with smooth player movement, sprite animations, collision detection, pedestrians, vehicles, and a functional minimap. The game includes the following features: - Arrow key or WASD movement control - Animated character sprites in 8 directions - Collision detection with buildings - AI-controlled pedestrians and vehicles - Interactive minimap showing player position and map overview - Realistic city environment with roads, buildings, and traffic - Pickups for health and coins ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GTA-Style Top-Down Game</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Courier New', monospace; } body { background: #000; overflow: hidden; height: 100vh; display: flex; justify-content: center; align-items: center; } #game-container { position: relative; width: 800px; height: 600px; } #game-canvas { background-color: #6491c9; border: 4px solid #e74c3c; border-radius: 5px; box-shadow: 0 0 20px rgba(231, 76, 60, 0.6); } #minimap { position: absolute; bottom: 15px; right: 15px; width: 200px; height: 200px; border: 3px solid #e74c3c; background-color: rgba(30, 30, 40, 0.85); z-index: 10; } #ui { position: absolute; top: 15px; left: 15px; z-index: 10; color: white; text-shadow: 2px 2px 1px #000; background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 10px; border: 1px solid #e74c3c; font-size: 16px; } #title { position: absolute; top: 20px; width: 100%; text-align: center; z-index: 10; color: #e74c3c; font-size: 38px; text-shadow: 3px 3px 0 black, 5px 5px 0 #c0392b; letter-spacing: 3px; text-transform: uppercase; font-weight: bold; } #controls { position: absolute; bottom: 15px; left: 15px; color: white; background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 10px; border: 1px solid #3498db; font-size: 14px; z-index: 10; } .health-bar { height: 20px; width: 200px; background-color: #555; border-radius: 10px; overflow: hidden; margin-top: 10px; } .health-fill { height: 100%; width: 100%; background: linear-gradient(to right, red, orange, yellow, #2ecc40); border-radius: 10px; transition: width 0.3s ease; } </style> </head> <body> <div id="game-container"> <div id="title">GTA BLOCKS</div> <canvas id="game-canvas" width="800" height="600"></canvas> <canvas id="minimap" width="200" height="200"></canvas> <div id="ui"> <div>SCORE: <span id="score">0</span></div> <div>COINS: <span id="coins">0</span></div> <div>HEALTH:</div> <div class="health-bar"> <div class="health-fill" id="health-fill"></div> </div> </div> <div id="controls"> <div>CONTROLS: WASD or Arrow Keys to Move</div> <div>CAPTURE CARS: Walk towards them</div> <div>AVOID PEDESTRIANS: They hurt you!</div> </div> </div> <script> // Canvas and context setup const canvas = document.getElementById('game-canvas'); const ctx = canvas.getContext('2d'); const minimapCanvas = document.getElementById('minimap'); const minimapCtx = minimapCanvas.getContext('2d'); // UI Elements const scoreElement = document.getElementById('score'); const coinsElement = document.getElementById('coins'); const healthFill = document.getElementById('health-fill'); // Game constants const PLAYER_SPEED = 4; const MINIMAP_SCALE = 0.2; const MAP_WIDTH = 3000; const MAP_HEIGHT = 3000; const TILE_SIZE = 80; const ROAD_WIDTH = 200; // Game variables let player = { x: MAP_WIDTH / 2, y: MAP_HEIGHT / 2, width: 50, height: 50, speed: PLAYER_SPEED, direction: 0, // Angle in degrees health: 100, score: 0, coins: 0, inVehicle: false }; let pedestrians = []; let vehicles = []; let pickups = []; let keys = { up: false, down: false, left: false, right: false }; // Map definition const buildings = []; // Generate buildings and roads function generateCity() { // Create a grid of buildings with roads const cols = Math.floor(MAP_WIDTH / TILE_SIZE); const rows = Math.floor(MAP_HEIGHT / TILE_SIZE); for (let x = 0; x < cols; x++) { for (let y = 0; y < rows; y++) { // Create roads in a grid pattern every 3 rows/cols const isRoadX = x % 3 === 0; const isRoadY = y % 3 === 0; // Skip road areas if (isRoadX || isRoadY) continue; // 80% chance to place a building if (Math.random() > 0.2) { const width = TILE_SIZE * 2 + Math.floor(Math.random() * 40); const height = TILE_SIZE * 2 + Math.floor(Math.random() * 40); buildings.push({ x: x * TILE_SIZE + (TILE_SIZE* 0.5), y: y * TILE_SIZE + (TILE_SIZE* 0.5), width: width, height: height, color: '#34495e' }); } } } // Generate some pedestrians for (let i = 0; i < 25; i++) { pedestrians.push({ x: Math.random() * MAP_WIDTH, y: Math.random() * MAP_HEIGHT, width: 25, height: 25, speed: Math.random() * 2 + 1, direction: Math.random() * Math.PI * 2, directionCooldown: 0, color: `hsl(${Math.random() * 360}, 80%, 60%)` }); } // Generate some vehicles for (let i = 0; i < 15; i++) { vehicles.push({ x: Math.random() * MAP_WIDTH, y: Math.random() * MAP_HEIGHT, width: 70, height: 40, speed: Math.random() * 4 + 2, direction: Math.random() * Math.PI * 2, directionCooldown: 0, color: `hsl(${Math.random() * 360}, 80%, 40%)`, captured: false }); } // Generate pickups (health and coins) for (let i = 0; i < 20; i++) { pickups.push({ x: Math.random() * MAP_WIDTH, y: Math.random() * MAP_HEIGHT, width: 30, height: 30, type: Math.random() > 0.7 ? 'health' : 'coin', collected: false }); } } // Collision detection - rectangle vs rectangle function rectCollision(rect1, rect2) { return ( rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y ); } // Move based on keys pressed function movePlayer() { const prevX = player.x; const prevY = player.y; if (!player.inVehicle) { if (keys.up || keys.w) { player.y -= player.speed; player.direction = -Math.PI/2; } if (keys.down || keys.s) { player.y += player.speed; player.direction = Math.PI/2; } if (keys.left || keys.a) { player.x -= player.speed; player.direction = Math.PI; } if (keys.right || keys.d) { player.x += player.speed; player.direction = 0; } // Diagonal movement (combine directions) if ((keys.up || keys.w) && (keys.right || keys.d)) player.direction = -Math.PI/4; if ((keys.up || keys.w) && (keys.left || keys.a)) player.direction = -3*Math.PI/4; if ((keys.down || keys.s) && (keys.right || keys.d)) player.direction = Math.PI/4; if ((keys.down || keys.s) && (keys.left || keys.a)) player.direction = 3*Math.PI/4; } // Check for collisions with buildings for (const building of buildings) { if (rectCollision(player, building)) { player.x = prevX; player.y = prevY; return; } } // Keep player within map bounds player.x = Math.max(0, Math.min(MAP_WIDTH - player.width, player.x)); player.y = Math.max(0, Math.min(MAP_HEIGHT - player.height, player.y)); } // Move and update pedestrians function updatePedestrians() { for (const ped of pedestrians) { // Occasionally change direction if (ped.directionCooldown <= 0) { ped.directionCooldown = Math.floor(Math.random() * 100) + 60; ped.direction = Math.random() * Math.PI * 2; } else { ped.directionCooldown--; } // Move ped.x += Math.cos(ped.direction) * ped.speed; ped.y += Math.sin(ped.direction) * ped.speed; // Keep within map bounds ped.x = Math.max(0, Math.min(MAP_WIDTH - ped.width, ped.x)); ped.y = Math.max(0, Math.min(MAP_HEIGHT - ped.height, ped.y)); // Check for collisions with buildings for (const building of buildings) { if (rectCollision(ped, building)) { // If colliding, change direction ped.direction = (ped.direction + Math.PI) % (Math.PI * 2); ped.x = Math.max(0, Math.min(MAP_WIDTH - ped.width, ped.x)); ped.y = Math.max(0, Math.min(MAP_HEIGHT - ped.height, ped.y)); break; } } // Check for collisions with player if (rectCollision(player, ped) && !player.inVehicle) { player.health -= 0.5; if (player.health < 0) player.health = 0; healthFill.style.width = `${player.health}%`; } } } // Move and update vehicles function updateVehicles() { for (const vehicle of vehicles) { // Occasionally change direction if (vehicle.directionCooldown <= 0) { vehicle.directionCooldown = Math.floor(Math.random() * 80) + 100; vehicle.direction = Math.random() * Math.PI * 2; } else { vehicle.directionCooldown--; } // Move vehicles faster when not captured const currentSpeed = vehicle.captured ? player.speed : vehicle.speed; // Move vehicle vehicle.x += Math.cos(vehicle.direction) * currentSpeed; vehicle.y += Math.sin(vehicle.direction) * currentSpeed; // Keep within map bounds vehicle.x = Math.max(0, Math.min(MAP_WIDTH - vehicle.width, vehicle.x)); vehicle.y = Math.max(0, Math.min(MAP_HEIGHT - vehicle.height, vehicle.y)); // Check for collisions with buildings for (const building of buildings) { if (rectCollision(vehicle, building)) { vehicle.direction = (vehicle.direction + Math.PI) % (Math.PI * 2); vehicle.x = Math.max(0, Math.min(MAP_WIDTH - vehicle.width, vehicle.x)); vehicle.y = Math.max(0, Math.min(MAP_HEIGHT - vehicle.height, vehicle.y)); break; } } } } // Update pickups function updatePickups() { for (const pickup of pickups) { if (!pickup.collected && rectCollision(player, pickup)) { pickup.collected = true; if (pickup.type === 'coin') { player.coins++; coinsElement.textContent = player.coins; player.score += 20; } else { player.health = Math.min(100, player.health + 30); healthFill.style.width = `${player.health}%`; player.score += 10; } scoreElement.textContent = player.score; } } } // Check for vehicle capture function captureVehicle() { for (const vehicle of vehicles) { if (!vehicle.captured && rectCollision(player, vehicle)) { vehicle.captured = true; player.inVehicle = true; vehicle.color = 'rgb(60, 200, 60)'; player.speed *= 1.5; player.score += 50; scoreElement.textContent = player.score; // Expire after 5 seconds setTimeout(() => { vehicle.captured = false; player.inVehicle = false; player.speed = PLAYER_SPEED; vehicle.color = `hsl(${Math.random() * 360}, 80%, 40%)`; }, 5000); } } } // Render a rotating character sprite function drawPlayer() { ctx.save(); ctx.translate(player.x - player.width/2, player.y - player.height/2); ctx.rotate(player.direction); if (player.inVehicle) { // Draw vehicle ctx.fillStyle = '#2ecc40'; ctx.fillRect(-10, -25, 60, 40); // Draw windshield ctx.fillStyle = '#3498db'; ctx.fillRect(30, -20, 15, 30); // Draw wheels ctx.fillStyle = '#2c3e50'; ctx.fillRect(0, -30, 15, 10); ctx.fillRect(0, 15, 15, 10); ctx.fillRect(35, -30, 15, 10); ctx.fillRect(35, 15, 15, 10); } else { // Draw player body (head and body) ctx.fillStyle = '#e74c3c'; // Draw head ctx.beginPath(); ctx.arc(0, -5, 18, 0, Math.PI * 2); ctx.fill(); // Draw body ctx.fillRect(-15, 5, 30, 30); // Draw legs ctx.fillRect(-15, 35, 10, 20); ctx.fillRect(5, 35, 10, 20); // Draw arms - direction based const armRotation = Math.sin(Date.now() / 100) * 0.3; ctx.fillStyle = '#c0392b'; ctx.save(); ctx.translate(-15, 15); ctx.rotate(armRotation); ctx.fillRect(0, 0, 25, 8); ctx.restore(); ctx.save(); ctx.translate(15, 15); ctx.rotate(-armRotation); ctx.fillRect(-25, 0, 25, 8); ctx.restore(); } ctx.restore(); } // Draw the game world function drawWorld() { // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Fill background ctx.fillStyle = '#6491c9'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Calculate the offset to center on the player const offsetX = canvas.width/2 - player.x; const offsetY = canvas.height/2 - player.y; ctx.save(); ctx.translate(offsetX, offsetY); // Draw roads ctx.fillStyle = '#2c3e50'; for (let x = ROAD_WIDTH; x < MAP_WIDTH; x += ROAD_WIDTH * 3) { ctx.fillRect(x - ROAD_WIDTH/2, 0, ROAD_WIDTH, MAP_HEIGHT); } for (let y = ROAD_WIDTH; y < MAP_HEIGHT; y += ROAD_WIDTH * 3) { ctx.fillRect(0, y - ROAD_WIDTH/2, MAP_WIDTH, ROAD_WIDTH); } // Draw center lines ctx.fillStyle = '#f1c40f'; for (let x = ROAD_WIDTH; x < MAP_WIDTH; x += ROAD_WIDTH * 3) { for (let i = 0; i < MAP_HEIGHT; i += 80) { ctx.fillRect(x - 2.5, i + ((Date.now()/20 + i) % 80), 5, 30); } } for (let y = ROAD_WIDTH; y < MAP_HEIGHT; y += ROAD_WIDTH * 3) { for (let i = 0; i < MAP_WIDTH; i += 80) { ctx.fillRect(i + ((Date.now()/20 + i) % 80), y - 2.5, 30, 5); } } // Draw buildings for (const building of buildings) { ctx.fillStyle = building.color; const shadowIntensity = 50; ctx.shadowColor = `rgba(0, 0, 0, 0.4)`; ctx.shadowBlur = 15; ctx.fillRect(building.x, building.y, building.width, building.height); // Draw windows ctx.shadowBlur = 0; ctx.fillStyle = '#3498db'; for (let i = 5; i < building.width; i += 20) { for (let j = 5; j < building.height; j += 30) { ctx.fillRect(building.x + i, building.y + j, 10, 15); } } } // Draw pickups for (const pickup of pickups) { if (pickup.collected) continue; ctx.fillStyle = pickup.type === 'coin' ? '#f1c40f' : '#e74c3c'; ctx.beginPath(); ctx.arc(pickup.x, pickup.y, 15, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#000'; if (pickup.type === 'coin') { ctx.font = '16px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('$', pickup.x, pickle.y); } else { // Health cross ctx.fillRect(pickup.x - 8, pickup.y - 2, 16, 4); ctx.fillRect(pickup.x - 2, pickup.y - 8, 4, 16); } } // Draw pedestrians for (const ped of pedestrians) { ctx.fillStyle = ped.color; ctx.beginPath(); ctx.arc(ped.x, ped.y, 15, 0, Math.PI*2); ctx.fill(); // Draw head ctx.fillStyle = '#ecf0f1'; ctx.beginPath(); ctx.arc(ped.x, ped.y - 6, 5, 0, Math.PI*2); ctx.fill(); } // Draw vehicles for (const vehicle of vehicles) { const angle = vehicle.direction; ctx.save(); ctx.translate(vehicle.x, vehicle.y); ctx.rotate(angle); ctx.fillStyle = vehicle.color; ctx.fillRect(-vehicle.width/2, -vehicle.height/2, vehicle.width, vehicle.height); // Draw windshield ctx.fillStyle = '#3498db'; ctx.fillRect(vehicle.width * 0.4 - vehicle.width/2, -vehicle.height/2, vehicle.width * 0.4, vehicle.height); // Draw wheels ctx.fillStyle = '#2c3e50'; ctx.fillRect(-vehicle.width/2, -vehicle.height/2 - 5, 10, 5); ctx.fillRect(vehicle.width/2 - 10, -vehicle.height/2 - 5, 10, 5); ctx.fillRect(-vehicle.width/2, vehicle.height/2, 10, 5); ctx.fillRect(vehicle.width/2 - 10, vehicle.height/2, 10, 5); ctx.restore(); } // Draw player drawPlayer(); // Reset translation ctx.restore(); } // Draw the minimap function drawMinimap() { minimapCtx.clearRect(0, 0, minimapCanvas.width, minimapCanvas.height); // Background minimapCtx.fillStyle = '#233'; minimapCtx.fillRect(0, 0, minimapCanvas.width, minimapCanvas.height); const scale = MINIMAP_SCALE * minimapCanvas.width / MAP_WIDTH; const circleSize = 10; // Draw roads minimapCtx.fillStyle = '#2c3e50'; for (let x = ROAD_WIDTH; x < MAP_WIDTH; x += ROAD_WIDTH * 3) { minimapCtx.fillRect(x * scale - (ROAD_WIDTH/2 * scale), 0, ROAD_WIDTH * scale, MAP_HEIGHT * scale); } for (let y = ROAD_WIDTH; y < MAP_HEIGHT; y += ROAD_WIDTH * 3) { minimapCtx.fillRect(0, y * scale - (ROAD_WIDTH/2 * scale), MAP_WIDTH * scale, ROAD_WIDTH * scale); } // Draw buildings as small rectangles minimapCtx.fillStyle = '#34495e'; for (const building of buildings) { minimapCtx.fillRect( building.x * scale, building.y * scale, building.width * scale, building.height * scale ); } // Draw pickups for (const pickup of pickups) { if (pickup.collected) continue; minimapCtx.fillStyle = pickup.type === 'coin' ? '#f1c40f' : '#e74c3c'; minimapCtx.beginPath(); minimapCtx.arc( pickup.x * scale, pickup.y * scale, 2, 0, Math.PI * 2 ); minimapCtx.fill(); } // Draw vehicles on minimap for (const vehicle of vehicles) { minimapCtx.fillStyle = vehicle.color; minimapCtx.beginPath(); minimapCtx.arc( vehicle.x * scale, vehicle.y * scale, 3, 0, Math.PI * 2 ); minimapCtx.fill(); } // Draw player on minimap minimapCtx.fillStyle = player.inVehicle ? '#2ecc40' : '#e74c3c'; minimapCtx.beginPath(); minimapCtx.arc( player.x * scale, player.y * scale, circleSize, 0, Math.PI * 2 ); minimapCtx.fill(); // Draw circle around player minimapCtx.strokeStyle = 'white'; minimapCtx.lineWidth = 2; minimapCtx.beginPath(); minimapCtx.arc( player.x * scale, player.y * scale, circleSize + 4, 0, Math.PI * 2 ); minimapCtx.stroke(); // Draw directional marker minimapCtx.translate(player.x * scale, player.y * scale); minimapCtx.rotate(player.direction); minimapCtx.fillStyle = 'white'; minimapCtx.beginPath(); minimapCtx.moveTo(0, -circleSize - 2); minimapCtx.lineTo(-5, -circleSize - 7); minimapCtx.lineTo(5, -circleSize - 7); minimapCtx.closePath(); minimapCtx.fill(); minimapCtx.resetTransform(); } // Game update loop function update() { movePlayer(); updatePedestrians(); updateVehicles(); updatePickups(); captureVehicle(); // Random events to add pedestrians occasionally if (Math.random() < 0.02 && pedestrians.length < 40) { pedestrians.push({ x: Math.random() * MAP_WIDTH, y: Math.random() * MAP_HEIGHT, width: 25, height: 25, speed: Math.random() * 2 + 1, direction: Math.random() * Math.PI * 2, directionCooldown: 0, color: `hsl(${Math.random() * 360}, 80%, 60%)` }); } // Add a coin occasionally if (pickups.filter(p => p.type === 'coin' && !p.collected).length < 8 && Math.random() < 0.01) { pickups.push({ x: Math.random() * MAP_WIDTH, y: Math.random() * MAP_HEIGHT, width: 30, height: 30, type: 'coin', collected: false }); } // Score increase over time when in vehicle if (player.inVehicle) { player.score += 0.1; scoreElement.textContent = Math.floor(player.score); } } // Game draw loop function draw() { drawWorld(); drawMinimap(); } // Main game loop function gameLoop() { update(); draw(); requestAnimationFrame(gameLoop); } // Initialize key listeners function initInput() { document.addEventListener('keydown', (e) => { if (e.key === 'w' || e.key === 'W' || e.key === 'ArrowUp') keys.up = true; if (e.key === 's' || e.key === 'S' || e.key === 'ArrowDown') keys.down = true; if (e.key === 'a' || e.key === 'A' || e.key === 'ArrowLeft') keys.left = true; if (e.key === 'd' || e.key === 'D' || e.key === 'ArrowRight') keys.right = true; }); document.addEventListener('keyup', (e) => { if (e.key === 'w' || e.key === 'W' || e.key === 'ArrowUp') keys.up = false; if (e.key === 's' || e.key === 'S' || e.key === 'ArrowDown') keys.down = false; if (e.key === 'a' || e.key === 'A' || e.key === 'ArrowLeft') keys.left = false; if (e.key === 'd' || e.key === 'D' || e.key === 'ArrowRight') keys.right = false; }); } // Initialize the game function init() { generateCity(); initInput(); gameLoop(); } // Start the game window.onload = init; </script> </body> </html> ``` ## Game Features - **Character Control**: Use WASD or arrow keys to move your character in all directions - **Sprite Animation**: The player character shows smooth walking animations with directional movement - **Collision Detection**: You can't walk through buildings or other obstacles - **Vehicle Interaction**: Approach vehicles to temporarily drive them (increases speed) - **AI Pedestrians**: Random civilians that roam the city and damage you when touched - **Minimap UI**: Shows an overview of the map with your position in the corner - **Pickups**: Collect coins for points and health packs to restore health - **Health System**: Keep an eye on your health bar at the top-left The city includes roads and buildings arranged in a grid pattern, with pedestrians and vehicles moving around. The minimap displays the player as a red circle (green when in a vehicle). Capture vehicles for a temporary speed boost and higher scoring potential!
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GTA-Style Top-Down Game</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; background: #000; overflow: hidden; } #gameContainer { position: relative; width: 800px; height: 600px; margin: 0 auto; border: 2px solid #333; } #gameCanvas { background: #2a2a2a; display: block; } #ui { position: absolute; top: 10px; left: 10px; color: white; z-index: 100; } #minimap { position: absolute; top: 10px; right: 10px; width: 150px; height: 150px; background: rgba(0, 0, 0, 0.7); border: 2px solid #fff; z-index: 100; } #health { background: rgba(0, 0, 0, 0.7); padding: 10px; border-radius: 5px; } .health-bar { width: 100px; height: 20px; background: #333; border: 1px solid #fff; margin-top: 5px; } .health-fill { height: 100%; background: linear-gradient(90deg, #ff0000, #ffff00, #00ff00); transition: width 0.3s; } #controls { position: absolute; bottom: 10px; left: 10px; color: white; background: rgba(0, 0, 0, 0.7); padding: 10px; border-radius: 5px; font-size: 12px; } </style> </head> <body> <div id="gameContainer"> <canvas id="gameCanvas" width="800" height="600"></canvas> <div id="ui"> <div id="health"> <div>Health: <span id="healthText">100</span></div> <div class="health-bar"> <div class="health-fill" id="healthFill" style="width: 100%;"></div> </div> </div> </div> <canvas id="minimap" width="150" height="150"></canvas> <div id="controls"> Controls:<br> WASD / Arrow Keys: Move<br> E: Enter/Exit Vehicle<br> R: Punch Pedestrians </div> </div> <script> class Game { constructor() { this.canvas = document.getElementById('gameCanvas'); this.ctx = this.canvas.getContext('2d'); this.minimapCanvas = document.getElementById('minimap'); this.minimapCtx = this.minimapCanvas.getContext('2d'); this.width = this.canvas.width; this.height = this.canvas.height; this.camera = { x: 0, y: 0 }; this.worldSize = { width: 1600, height: 1200 }; this.keys = {}; this.entities = []; this.player = new Player(400, 300); this.entities.push(this.player); this.init(); this.gameLoop(); } init() { this.setupControls(); this.generateWorld(); } setupControls() { document.addEventListener('keydown', (e) => { this.keys[e.key.toLowerCase()] = true; }); document.addEventListener('keyup', (e) => { this.keys[e.key.toLowerCase()] = false; }); } generateWorld() { // Generate buildings for (let i = 0; i < 20; i++) { const x = Math.random() * (this.worldSize.width - 100); const y = Math.random() * (this.worldSize.height - 100); this.entities.push(new Building(x, y)); } // Generate vehicles for (let i = 0; i < 8; i++) { const x = Math.random() * this.worldSize.width; const y = Math.random() * this.worldSize.height; this.entities.push(new Vehicle(x, y)); } // Generate pedestrians for (let i = 0; i < 15; i++) { const x = Math.random() * this.worldSize.width; const y = Math.random() * this.worldSize.height; this.entities.push(new Pedestrian(x, y)); } } update() { // Update camera to follow player this.camera.x = this.player.x - this.width / 2; this.camera.y = this.player.y - this.height / 2; // Keep camera within world bounds this.camera.x = Math.max(0, Math.min(this.worldSize.width - this.width, this.camera.x)); this.camera.y = Math.max(0, Math.min(this.worldSize.height - this.height, this.camera.y)); // Update all entities this.entities.forEach(entity => { if (entity.update) { entity.update(this); } }); // Handle collisions this.handleCollisions(); } handleCollisions() { for (let i = 0; i < this.entities.length; i++) { for (let j = i + 1; j < this.entities.length; j++) { const a = this.entities[i]; const b = this.entities[j]; if (this.checkCollision(a, b)) { this.resolveCollision(a, b); } } } } checkCollision(a, b) { return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y; } resolveCollision(a, b) { if (a.type === 'player' || b.type === 'player') { const player = a.type === 'player' ? a : b; const other = a.type === 'player' ? b : a; if (other.type === 'building') { // Push player out of building const overlapX = Math.min(player.x + player.width - other.x, other.x + other.width - player.x); const overlapY = Math.min(player.y + player.height - other.y, other.y + other.height - player.y); if (overlapX < overlapY) { player.x += player.x < other.x ? -overlapX : overlapX; } else { player.y += player.y < other.y ? -overlapY : overlapY; } } } } render() { // Clear canvas this.ctx.fillStyle = '#2a2a2a'; this.ctx.fillRect(0, 0, this.width, this.height); // Draw grid (streets) this.drawGrid(); // Sort entities by y position for proper layering const visibleEntities = this.entities .filter(entity => this.isVisible(entity)) .sort((a, b) => a.y - b.y); // Render all visible entities visibleEntities.forEach(entity => { entity.render(this.ctx, this.camera); }); // Render minimap this.renderMinimap(); // Update UI this.updateUI(); } drawGrid() { this.ctx.strokeStyle = '#444'; this.ctx.lineWidth = 1; const gridSize = 50; const startX = -this.camera.x % gridSize; const startY = -this.camera.y % gridSize; for (let x = startX; x < this.width; x += gridSize) { this.ctx.beginPath(); this.ctx.moveTo(x, 0); this.ctx.lineTo(x, this.height); this.ctx.stroke(); } for (let y = startY; y < this.height; y += gridSize) { this.ctx.beginPath(); this.ctx.moveTo(0, y); this.ctx.lineTo(this.width, y); this.ctx.stroke(); } } isVisible(entity) { return entity.x < this.camera.x + this.width && entity.x + entity.width > this.camera.x && entity.y < this.camera.y + this.height && entity.y + entity.height > this.camera.y; } renderMinimap() { const scale = 0.1; // Clear minimap this.minimapCtx.fillStyle = '#000'; this.minimapCtx.fillRect(0, 0, 150, 150); // Draw entities on minimap this.entities.forEach(entity => { const x = entity.x * scale; const y = entity.y * scale; const width = Math.max(1, entity.width * scale); const height = Math.max(1, entity.height * scale); if (entity.type === 'player') { this.minimapCtx.fillStyle = '#0ff'; } else if (entity.type === 'vehicle') { this.minimapCtx.fillStyle = '#ff0'; } else if (entity.type === 'building') { this.minimapCtx.fillStyle = '#666'; } else if (entity.type === 'pedestrian') { this.minimapCtx.fillStyle = '#0f0'; } this.minimapCtx.fillRect(x, y, width, height); }); // Draw camera view area this.minimapCtx.strokeStyle = '#fff'; this.minimapCtx.lineWidth = 1; this.minimapCtx.strokeRect( this.camera.x * scale, this.camera.y * scale, this.width * scale, this.height * scale ); } updateUI() { document.getElementById('healthText').textContent = this.player.health; document.getElementById('healthFill').style.width = `${this.player.health}%`; } gameLoop() { this.update(); this.render(); requestAnimationFrame(() => this.gameLoop()); } } class Entity { constructor(x, y, width, height, type) { this.x = x; this.y = y; this.width = width; this.height = height; this.type = type; } render(ctx, camera) { const screenX = this.x - camera.x; const screenY = this.y - camera.y; ctx.fillStyle = this.color || '#fff'; ctx.fillRect(screenX, screenY, this.width, this.height); } } class Player extends Entity { constructor(x, y) { super(x, y, 20, 20, 'player'); this.color = '#00ff00'; this.speed = 3; this.health = 100; this.inVehicle = null; this.animFrame = 0; } update(game) { if (this.inVehicle) { this.updateInVehicle(game); } else { this.updateOnFoot(game); } // Handle interactions if (game.keys['e']) { this.handleInteraction(game); } // Handle combat if (game.keys['r']) { this.punch(game); } // Keep player in world bounds this.x = Math.max(0, Math.min(game.worldSize.width - this.width, this.x)); this.y = Math.max(0, Math.min(game.worldSize.height - this.height, this.y)); this.animFrame++; } updateOnFoot(game) { let dx = 0, dy = 0; if (game.keys['w'] || game.keys['arrowup']) dy -= this.speed; if (game.keys['s'] || game.keys['arrowdown']) dy += this.speed; if (game.keys['a'] || game.keys['arrowleft']) dx -= this.speed; if (game.keys['d'] || game.keys['arrowright']) dx += this.speed; this.x += dx; this.y += dy; } updateInVehicle(game) { this.x = this.inVehicle.x; this.y = this.inVehicle.y; let dx = 0, dy = 0; if (game.keys['w'] || game.keys['arrowup']) dy -= this.inVehicle.speed; if (game.keys['s'] || game.keys['arrowdown']) dy += this.inVehicle.speed; if (game.keys['a'] || game.keys['arrowleft']) dx -= this.inVehicle.speed; if (game.keys['d'] || game.keys['arrowright']) dx += this.inVehicle.speed; this.inVehicle.x += dx; this.inVehicle.y += dy; this.inVehicle.x = Math.max(0, Math.min(game.worldSize.width - this.inVehicle.width, this.inVehicle.x)); this.inVehicle.y = Math.max(0, Math.min(game.worldSize.height - this.inVehicle.height, this.inVehicle.y)); } handleInteraction(game) { if (this.inVehicle) { // Exit vehicle this.x = this.inVehicle.x + this.inVehicle.width + 5; this.inVehicle = null; } else { // Try to enter vehicle const nearbyVehicle = game.entities.find(entity => entity.type === 'vehicle' && Math.abs(entity.x - this.x) < 40 && Math.abs(entity.y - this.y) < 40 ); if (nearbyVehicle) { this.inVehicle = nearbyVehicle; } } } punch(game) { const nearbyPeds = game.entities.filter(entity => entity.type === 'pedestrian' && Math.abs(entity.x - this.x) < 30 && Math.abs(entity.y - this.y) < 30 ); nearbyPeds.forEach(ped => { ped.health -= 25; if (ped.health <= 0) { const index = game.entities.indexOf(ped); if (index > -1) { game.entities.splice(index, 1); } } }); } render(ctx, camera) { if (this.inVehicle) return; // Don't render player when in vehicle const screenX = this.x - camera.x; const screenY = this.y - camera.y; // Simple walking animation const bobOffset = Math.sin(this.animFrame * 0.3) * 2; ctx.fillStyle = this.color; ctx.fillRect(screenX, screenY + bobOffset, this.width, this.height); // Draw direction indicator ctx.fillStyle = '#fff'; ctx.fillRect(screenX + 8, screenY + 5, 4, 4); } } class Vehicle extends Entity { constructor(x, y) { super(x, y, 30, 50, 'vehicle'); this.color = `hsl(${Math.random() * 360}, 70%, 50%)`; this.speed = 5; } render(ctx, camera) { const screenX = this.x - camera.x; const screenY = this.y - camera.y; // Car body ctx.fillStyle = this.color; ctx.fillRect(screenX, screenY, this.width, this.height); // Windows ctx.fillStyle = '#333'; ctx.fillRect(screenX + 5, screenY + 5, this.width - 10, 15); ctx.fillRect(screenX + 5, screenY + 30, this.width - 10, 15); // Wheels ctx.fillStyle = '#000'; ctx.fillRect(screenX - 2, screenY + 5, 4, 8); ctx.fillRect(screenX + this.width - 2, screenY + 5, 4, 8); ctx.fillRect(screenX - 2, screenY + this.height - 13, 4, 8); ctx.fillRect(screenX + this.width - 2, screenY + this.height - 13, 4, 8); } } class Pedestrian extends Entity { constructor(x, y) { super(x, y, 15, 15, 'pedestrian'); this.color = `hsl(${Math.random() * 360}, 50%, 60%)`; this.speed = 1; this.health = 100; this.direction = Math.random() * Math.PI * 2; this.walkTimer = 0; this.animFrame = 0; } update(game) { // Simple AI: walk randomly this.walkTimer++; if (this.walkTimer > 120) { // Change direction every 2 seconds this.direction = Math.random() * Math.PI * 2; this.walkTimer = 0; } const dx = Math.cos(this.direction) * this.speed; const dy = Math.sin(this.direction) * this.speed; this.x += dx; this.y += dy; // Keep in world bounds this.x = Math.max(0, Math.min(game.worldSize.width - this.width, this.x)); this.y = Math.max(0, Math.min(game.worldSize.height - this.height, this.y)); // Bounce off world edges if (this.x <= 0 || this.x >= game.worldSize.width - this.width) { this.direction = Math.PI - this.direction; } if (this.y <= 0 || this.y >= game.worldSize.height - this.height) { this.direction = -this.direction; } this.animFrame++; } render(ctx, camera) { const screenX = this.x - camera.x; const screenY = this.y - camera.y; // Simple walking animation const bobOffset = Math.sin(this.animFrame * 0.2) * 1; ctx.fillStyle = this.color; ctx.fillRect(screenX, screenY + bobOffset, this.width, this.height); // Health bar if damaged if (this.health < 100) { ctx.fillStyle = '#f00'; ctx.fillRect(screenX, screenY - 8, this.width, 3); ctx.fillStyle = '#0f0'; ctx.fillRect(screenX, screenY - 8, this.width * (this.health / 100), 3); } } } class Building extends Entity { constructor(x, y) { const width = 60 + Math.random() * 80; const height = 60 + Math.random() * 100; super(x, y, width, height, 'building'); this.color = `hsl(${Math.random() * 60 + 200}, 20%, ${20 + Math.random() * 30}%)`; } render(ctx, camera) { const screenX = this.x - camera.x; const screenY = this.y - camera.y; // Building body ctx.fillStyle = this.color; ctx.fillRect(screenX, screenY, this.width, this.height); // Windows ctx.fillStyle = Math.random() > 0.7 ? '#ff0' : '#333'; for (let i = 10; i < this.width - 10; i += 15) { for (let j = 10; j < this.height - 10; j += 20) { ctx.fillRect(screenX + i, screenY + j, 8, 12); } } // Building outline ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.strokeRect(screenX, screenY, this.width, this.height); } } // Start the game window.addEventListener('load', () => { new Game(); }); </script> </body> </html> ``` This GTA-style top-down game includes: **Core Features:** - **Player Movement**: Control character with WASD or arrow keys - **Vehicle System**: Enter/exit vehicles with 'E' key - **Combat**: Punch pedestrians with 'R' key - **Collision Detection**: Prevents walking through buildings - **World Generation**: Random buildings, vehicles, and NPCs **Visual Elements:** - **Sprite Animations**: Walking bob animations for player and pedestrians - **City Environment**: Buildings with windows, cars with details - **Grid Streets**: Visual street grid overlay - **Health System**: Health bar and damage visualization **UI Components:** - **Minimap**: Shows entire world with entity positions - **Health Display**: Player health with visual bar - **Controls Guide**: On-screen control instructions - **Camera System**: Follows player smoothly **Gameplay Mechanics:** - **Vehicle Driving**: Different speed when in vehicles - **NPC Behavior**: Pedestrians walk randomly around the city - **World Bounds**: Keeps all entities within the game world - **Interactive Elements**: Vehicles can be entered/exited The game creates a mini open-world experience with basic GTA-style mechanics in a compact, playable format!
Drag to resize