📂 Pembuat Folder & File PHP
2
Total Folder
4
Total File
✏️ Edit File: game/index.php
<?php session_start(); ?> <!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover"> <meta name="theme-color" content="#0a0a12"> <title>Open World - Real 3D Characters & PS2 Controller</title> <style> *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;} html,body{ width:100%;height:100%;overflow:hidden;background:#05050a; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; user-select:none;-webkit-user-select:none;touch-action:none; -webkit-font-smoothing:antialiased;overscroll-behavior:none; } canvas{display:block;touch-action:none;width:100vw;height:100vh;} #ui{position:fixed;inset:0;pointer-events:none;z-index:10;} /* HUD Klasik Proporsional */ .hud{ position:absolute; background:linear-gradient(180deg,rgba(15,15,25,0.9),rgba(5,5,15,0.75)); border:1px solid rgba(255,255,255,0.15); border-radius:6px;color:#fff;padding:8px 12px; font-family:'Courier New',monospace; backdrop-filter:blur(8px);box-shadow:0 4px 20px rgba(0,0,0,0.8); } .money{ top:12px;right:12px;color:#2ecc71;font-size:22px;font-weight:900; text-shadow:0 0 10px rgba(46,204,113,0.7);text-align:right;min-width:150px; } .money-label{font-size:8px;letter-spacing:2px;opacity:0.6;margin-bottom:2px;} .wanted{ top:12px;left:12px;display:flex;gap:4px;align-items:center;padding:5px 10px; } .star{font-size:18px;color:rgba(255,255,255,0.2);line-height:1;} .star.on{color:#f1c40f;text-shadow:0 0 10px #f1c40f,0 0 18px #e67e22;} .mission{ top:12px;left:50%;transform:translateX(-50%);text-align:center; max-width:50%;min-width:200px;padding:6px 12px; } .mission-label{font-size:8px;letter-spacing:2px;color:#f39c12;font-weight:bold;margin-bottom:2px;} .mission-text{color:#fff;font-size:10px;font-weight:600;} .status-hud{bottom:15px;left:15px;width:160px;padding:8px 10px;} .status-label{font-size:8px;letter-spacing:2px;opacity:0.6;margin-bottom:4px;} .bar-bg{height:7px;background:rgba(255,255,255,0.15);border-radius:4px;overflow:hidden;} .hp-fill{height:100%;background:linear-gradient(90deg,#e74c3c,#ff7675);width:100%;transition:width 0.2s;} .armor-fill{height:4px;background:linear-gradient(90deg,#0984e3,#74b9ff);width:50%;margin-top:3px;border-radius:3px;transition:width 0.2s;} .weapon-hud{bottom:15px;left:185px;padding:8px 12px;min-width:105px;} .weapon-label{font-size:8px;letter-spacing:2px;opacity:0.6;} .weapon-name{color:#00cec9;font-size:12px;font-weight:900;margin:2px 0;} .weapon-ammo{color:#fff;font-size:9px;} .minimap-wrap{ position:absolute;bottom:15px;right:15px;width:115px;height:115px; border-radius:50%;border:3px solid rgba(255,255,255,0.4);overflow:hidden; background:rgba(5,5,10,0.85);box-shadow:0 0 20px rgba(0,0,0,0.9); } #minimap{width:100%;height:100%;display:block;} .speedo{position:absolute;bottom:15px;right:140px;text-align:right;padding:8px 10px;} .speed-val{font-size:24px;font-weight:900;color:#74b9ff;line-height:1;} .speed-unit{font-size:8px;opacity:0.6;letter-spacing:2px;margin-top:2px;} /* --- KONTROLER STIK PS2 DI LAYAR --- */ .pad{ position:fixed;bottom:15px;left:15px;width:130px;height:130px; pointer-events:auto;z-index:20;touch-action:none; } .joystick-base{ position:absolute;inset:0;border-radius:50%; background:radial-gradient(circle,rgba(255,255,255,0.15),rgba(255,255,255,0.03)); border:2px solid rgba(255,255,255,0.3); } .joystick-knob{ position:absolute;width:50px;height:50px;border-radius:50%; background:radial-gradient(circle,rgba(90,90,100,0.95),rgba(35,35,40,0.95)); border:2px solid rgba(180,180,190,0.9);left:calc(50% - 25px);top:calc(50% - 25px); box-shadow:0 0 15px rgba(0,0,0,0.8); } .ps2-buttons{ position:fixed;bottom:15px;right:135px;width:130px;height:130px; pointer-events:auto;z-index:20; } .ps2-btn{ position:absolute;width:42px;height:42px;border-radius:50%; color:#fff;font-weight:bold;font-size:15px;cursor:pointer; display:flex;align-items:center;justify-content:center; box-shadow:0 4px 15px rgba(0,0,0,0.7);border:2px solid rgba(255,255,255,0.4); } .ps2-btn:active{transform:scale(0.9);filter:brightness(1.2);} .btn-triangle{top:0;left:44px;background:#9b59b6;border-color:#8e44ad;} /* Tukar Senjata */ .btn-square{top:44px;left:0;background:#e67e22;border-color:#d35400;} /* Tembak */ .btn-circle{top:44px;right:0;background:#0984e3;border-color:#0066cc;} /* Lari */ .btn-cross{bottom:0;left:44px;background:#2ecc71;border-color:#27ae60;} /* Naik / Masuk Kendaraan */ .overlay{ position:fixed;inset:0;background:rgba(5,5,12,0.96); display:flex;flex-direction:column;align-items:center;justify-content:center; gap:16px;padding:20px;z-index:100;color:#fff;text-align:center; } .overlay.hidden{display:none;} .title{ font-size:36px;font-weight:900;letter-spacing:3px;color:#f39c12; text-shadow:3px 3px 0 #78281f,0 0 30px rgba(243,156,18,0.7); font-family:'Courier New',monospace;line-height:1.1; } .btn-main{ padding:12px 38px;font-size:16px;font-weight:bold;letter-spacing:2px;color:#000; background:linear-gradient(135deg,#f39c12,#f1c40f);border:none;border-radius:6px;cursor:pointer; box-shadow:0 0 20px rgba(243,156,18,0.5); } .audio-toggle{ position:fixed;top:12px;right:175px;width:38px;height:38px;border-radius:50%; background:rgba(15,15,25,0.85);border:2px solid rgba(255,255,255,0.25); color:#fff;font-size:16px;cursor:pointer;pointer-events:auto;z-index:15; display:flex;align-items:center;justify-content:center;backdrop-filter:blur(6px); } </style> </head> <body> <canvas id="canvas"></canvas> <div id="ui"> <div class="hud wanted"> <span class="star" id="s1">★</span><span class="star" id="s2">★</span> <span class="star" id="s3">★</span><span class="star" id="s4">★</span><span class="star" id="s5">★</span> </div> <div class="hud mission"> <div class="mission-label" id="missionLabel">MISI UTAMA</div> <div class="mission-text" id="missionText">Eksplorasi Kota & Karakter 3D Realistis</div> </div> <div class="hud money"> <div class="money-label">CASH</div> <div id="money">$00007500</div> </div> <div class="hud status-hud"> <div class="status-label">HEALTH & ARMOR</div> <div class="bar-bg"><div class="hp-fill" id="hpFill"></div></div> <div class="armor-fill" id="armorFill"></div> </div> <div class="hud weapon-hud"> <div class="weapon-label">SENJATA</div> <div class="weapon-name" id="weaponName">PISTOL</div> <div class="weapon-ammo" id="weaponAmmo">∞ / ∞</div> </div> <div class="speedo hud"> <div class="speed-val" id="speedVal">0</div> <div class="speed-unit">KM/H</div> </div> <div class="minimap-wrap"> <canvas id="minimap" width="115" height="115"></canvas> </div> </div> <button class="audio-toggle" id="audioToggle">🔊</button> <div class="pad" id="pad"> <div class="joystick-base"></div> <div class="joystick-knob" id="knob"></div> </div> <!-- Layout Stik PS2 Asli --> <div class="ps2-buttons" id="ps2Buttons"> <button class="ps2-btn btn-triangle" id="switchBtn">△</button> <button class="ps2-btn btn-square" id="fireBtn">□</button> <button class="ps2-btn btn-circle" id="runBtn">○</button> <button class="ps2-btn btn-cross" id="enterBtn">✕</button> </div> <div class="overlay" id="startScreen"> <div class="title">OPEN WORLD CITY<br>REAL 3D PEOPLE EDITION</div> <p style="color:#b2bec3;font-size:12px;max-width:340px;line-height:1.5;">Karakter Orang 3D Realistis • Mobil & Motor AI • SPBU Meledak[span_2](start_span)[span_2](end_span)[span_3](start_span)[span_3](end_span) • Stik PS2</p> <button class="btn-main" id="startBtn">MULAI BERMAIN</button> </div> <div class="overlay hidden" id="endScreen"> <div class="title" style="color:#e74c3c;" id="endTitle">TERTANGKAP</div> <p style="color:#fff;font-size:13px;" id="endMsg">Kamu telah dilumpuhkan pihak berwajib!</p> <button class="btn-main" id="restartBtn">COBA LAGI</button> </div> <script> (function(){ 'use strict'; const Sound = { ctx: null, master: null, enabled: true, init(){ if(this.ctx) return; try{ const AC = window.AudioContext || window.webkitAudioContext; this.ctx = new AC(); this.master = this.ctx.createGain(); this.master.gain.value = 0.35; this.master.connect(this.ctx.destination); }catch(e){this.enabled=false;} }, resume(){if(this.ctx && this.ctx.state==='suspended') this.ctx.resume();}, tone(freq, dur, type, vol, slide){ if(!this.enabled || !this.ctx) return; try{ const t = this.ctx.currentTime; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = type || 'sine'; osc.frequency.setValueAtTime(freq, t); if(slide) osc.frequency.exponentialRampToValueAtTime(Math.max(20, slide), t + dur); gain.gain.setValueAtTime(0, t); gain.gain.linearRampToValueAtTime(vol || 0.3, t + 0.01); gain.gain.exponentialRampToValueAtTime(0.001, t + dur); osc.connect(gain); gain.connect(this.master); osc.start(t); osc.stop(t + dur + 0.02); }catch(e){} }, noise(dur, vol){ if(!this.enabled || !this.ctx) return; try{ const t = this.ctx.currentTime; const bufSize = this.ctx.sampleRate * dur; const buffer = this.ctx.createBuffer(1, bufSize, this.ctx.sampleRate); const data = buffer.getChannelData(0); for(let i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1); const src = this.ctx.createBufferSource(); src.buffer = buffer; const gain = this.ctx.createGain(); gain.gain.setValueAtTime(vol || 0.2, t); gain.gain.exponentialRampToValueAtTime(0.001, t + dur); src.connect(gain); gain.connect(this.master); src.start(t); }catch(e){} }, shoot(type){ if(type === 'AK-47'){ this.tone(550, 0.06, 'sawtooth', 0.35, 140); this.noise(0.05, 0.25); } else if(type === 'SHOTGUN'){ this.tone(250, 0.15, 'square', 0.45, 40); this.noise(0.18, 0.4); } else if(type === 'RPG'){ this.tone(130, 0.4, 'sawtooth', 0.55, 30); this.noise(0.3, 0.5); } else { this.tone(850, 0.08, 'square', 0.3, 180); this.noise(0.05, 0.2); } }, explosion(){ this.noise(0.6, 0.8); this.tone(60, 0.4, 'sawtooth', 0.6, 20); }, siren(){ this.tone(750, 0.22, 'sine', 0.15, 450); }, carStart(){ this.tone(65, 0.3, 'sawtooth', 0.35, 110); this.noise(0.15, 0.12); }, bikeEngine(){ this.tone(180, 0.1, 'sawtooth', 0.15, 220); } }; const canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'); const minimap = document.getElementById('minimap'), mmCtx = minimap.getContext('2d'); let W, H, DPR, running = false, lastTime = 0; let camX = 0, camY = 0; const WORLD = 5500, BLOCK = 600, ROAD = 120; let player, vehicles, npcs, buildings, gasStations, bullets; let wanted = 0, money = 7500, health = 100, armor = 50; let gameTime = 0; const WEAPONS = [ { name: 'PISTOL', ammo: Infinity, maxAmmo: Infinity, fireRate: 14, damage: 25, speed: 18, type: 'PISTOL' }, { name: 'SHOTGUN', ammo: 30, maxAmmo: 60, fireRate: 35, damage: 22, pellets: 5, speed: 16, type: 'SHOTGUN' }, { name: 'AK-47', ammo: 120, maxAmmo: 240, fireRate: 6, damage: 20, speed: 20, type: 'AK-47' }, { name: 'RPG-7', ammo: 5, maxAmmo: 15, fireRate: 50, damage: 160, speed: 12, type: 'RPG' } ]; let currentWeaponIdx = 0; function resize(){ DPR = Math.min(window.devicePixelRatio || 1, 2); W = window.innerWidth; H = window.innerHeight; canvas.width = W * DPR; canvas.height = H * DPR; canvas.style.width = W + 'px'; canvas.style.height = H + 'px'; ctx.setTransform(DPR, 0, 0, DPR, 0, 0); } function rand(a, b){ return a + Math.random() * (b - a); } function dist(x1, y1, x2, y2){ return Math.hypot(x2 - x1, y2 - y1); } function generateCity(){ buildings = []; gasStations = []; for(let bx = 0; bx < WORLD; bx += BLOCK){ for(let by = 0; by < WORLD; by += BLOCK){ const rx = bx + ROAD / 2 + 25; const ry = by + ROAD / 2 + 25; const rw = BLOCK - ROAD - 50; const rh = BLOCK - ROAD - 50; if(rw <= 0 || rh <= 0) continue; if(bx === BLOCK && by === BLOCK){ gasStations.push({ x: rx + 25, y: ry + 25, w: 110, h: 75, active: true }); } const count = 3 + Math.floor(Math.random() * 3); for(let i = 0; i < count; i++){ const w = 90 + Math.random() * (rw / 2 - 25); const h = 90 + Math.random() * (rh / 2 - 25); const x = rx + Math.random() * (rw - w); const y = ry + Math.random() * (rh - h); const colors = ['#2c3e50','#34495e','#1e272e','#2f3542','#1a1a24','#222f3e']; buildings.push({ x, y, w, h, color: colors[Math.floor(Math.random() * colors.length)] }); } } } } function resetGame(){ generateCity(); gameTime = 0; wanted = 0; money = 7500; health = 100; armor = 50; currentWeaponIdx = 0; WEAPONS[0].ammo = Infinity; WEAPONS[1].ammo = 30; WEAPONS[2].ammo = 120; WEAPONS[3].ammo = 5; player = { x: ROAD / 2 + 80, y: ROAD / 2 + 80, angle: 0, speed: 0, inCar: false, vehicle: null, walkSpeed: 3.4, runSpeed: 6.2, radius: 12, walkPhase: 0, fireCooldown: 0, invuln: 0, shirtColor: '#2ecc71', pantsColor: '#2c3e50' }; vehicles = []; const colors = ['#e74c3c','#0984e3','#f1c40f','#2ecc71','#8e44ad','#e67e22','#2d3436']; for(let i = 0; i < 25; i++){ vehicles.push({ x: rand(ROAD, WORLD-ROAD), y: rand(ROAD, WORLD-ROAD), angle: Math.random() * Math.PI * 2, speed: rand(1.5, 3.5), color: colors[i % colors.length], w: 58, h: 26, occupied: false, isBike: false, isAi: true, hp: 100 }); } for(let i = 0; i < 10; i++){ vehicles.push({ x: rand(ROAD, WORLD-ROAD), y: rand(ROAD, WORLD-ROAD), angle: Math.random() * Math.PI * 2, speed: rand(2, 4), color: '#f39c12', w: 36, h: 16, occupied: false, isBike: true, isAi: true, hp: 80 }); } // NPC Manusia 3D Lengkap (Kepala, Badan, Tangan, Kaki) npcs = []; for(let i = 0; i < 60; i++){ npcs.push({ x: rand(ROAD, WORLD-ROAD), y: rand(ROAD, WORLD-ROAD), angle: Math.random() * Math.PI * 2, speed: 0.8, walkPhase: Math.random()*10, shirtColor: colors[Math.floor(Math.random()*colors.length)], pantsColor: '#1e272e', skinColor: '#f0c090' }); } bullets = []; updateHUD(); } function updateHUD(){ document.getElementById('money').textContent = '$' + String(money).padStart(8, '0'); document.getElementById('hpFill').style.width = health + '%'; document.getElementById('armorFill').style.width = armor + '%'; const w = WEAPONS[currentWeaponIdx]; document.getElementById('weaponName').textContent = w.name; document.getElementById('weaponAmmo').textContent = w.maxAmmo === Infinity ? '∞' : w.ammo + ' / ' + w.maxAmmo; for(let i=1; i<=5; i++){ document.getElementById('s'+i).className = i <= wanted ? 'star on' : 'star'; } } function collideBuilding(nx, ny, r){ for(const b of buildings){ if(nx > b.x - r && nx < b.x + b.w + r && ny > b.y - r && ny < b.y + b.h + r) return true; } for(const g of gasStations){ if(g.active && nx > g.x - r && nx < g.x + g.w + r && ny > g.y - r && ny < g.y + g.h + r) return true; } return false; } // --- RENDER ORANG 3D REALISTIS DENGAN KAKI/TANGAN ANIMASI --- function draw3DHuman(x, y, angle, walkPhase, shirtCol, pantsCol, skinCol, isMoving){ ctx.save(); ctx.translate(x, y); ctx.rotate(angle + Math.PI / 2); // Efek Bayangan Kaki ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.beginPath(); ctx.ellipse(0, 2, 8, 12, 0, 0, Math.PI * 2); ctx.fill(); // Animasi Kaki Bergerak Saat Jalan const legOffset = isMoving ? Math.sin(walkPhase) * 5 : 0; ctx.fillStyle = pantsCol; ctx.fillRect(-5, -2 + legOffset, 3, 12); // Kaki Kiri ctx.fillRect(2, -2 - legOffset, 3, 12); // Kaki Kanan // Badan / Torso (Baju 3D) ctx.fillStyle = shirtCol; ctx.fillRect(-7, -10, 14, 12); // Tangan Kiri & Kanan ctx.fillStyle = skinCol; ctx.fillRect(-10, -8, 3, 9); ctx.fillRect(7, -8, 3, 9); // Kepala 3D Realistis (Lingkaran dengan efek gradasi/bayangan) ctx.fillStyle = skinCol; ctx.beginPath(); ctx.arc(0, -14, 6, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#d35400'; ctx.lineWidth = 1; ctx.stroke(); ctx.restore(); } // --- KONTROLER STIK PS2 (ANALOG & TOMBOL) --- let joyX = 0, joyY = 0, joyActive = false, joyId = null; const pad = document.getElementById('pad'), knob = document.getElementById('knob'); const padRect = () => pad.getBoundingClientRect(); pad.addEventListener('touchstart', e => { e.preventDefault(); const t = e.changedTouches[0]; joyId = t.identifier; joyActive = true; handleJoy(t); }, {passive:false}); pad.addEventListener('touchmove', e => { if(!joyActive) return; e.preventDefault(); for(const t of e.changedTouches){ if(t.identifier === joyId) handleJoy(t); } }, {passive:false}); pad.addEventListener('touchend', () => { joyActive = false; joyX = 0; joyY = 0; knob.style.transform = 'translate(0,0)'; }); function handleJoy(t){ const r = padRect(); const cx = r.left + r.width/2; const cy = r.top + r.height/2; let dx = t.clientX - cx; let dy = t.clientY - cy; const max = 40; const d = Math.hypot(dx, dy); if(d > max){ dx = dx/d*max; dy = dy/d*max; } knob.style.transform = `translate(${dx}px, ${dy}px)`; joyX = dx / max; joyY = dy / max; } // Pemetaan Tombol Stik PS2 Asli document.getElementById('enterBtn').addEventListener('click', () => { // Tombol ✕ (Naik / Turun Kendaraan) Sound.resume(); if(player.inCar){ player.inCar = false; player.vehicle.occupied = false; player.x = player.vehicle.x + 35; player.y = player.vehicle.y; player.vehicle = null; } else { let nearest = null, nd = 75; for(const v of vehicles){ if(!v.occupied && dist(v.x, v.y, player.x, player.y) < nd){ nd = dist(v.x, v.y, player.x, player.y); nearest = v; } } if(nearest){ player.inCar = true; player.vehicle = nearest; nearest.occupied = true; nearest.isAi = false; if(nearest.isBike) Sound.bikeEngine(); else Sound.carStart(); } } }); document.getElementById('fireBtn').addEventListener('click', () => { // Tombol □ (Tembak) Sound.resume(); if(player.inCar || player.fireCooldown > 0) return; const w = WEAPONS[currentWeaponIdx]; if(w.ammo <= 0) return; if(w.maxAmmo !== Infinity) w.ammo--; player.fireCooldown = w.fireRate; updateHUD(); Sound.shoot(w.type); const bx = player.x + Math.cos(player.angle)*20; const by = player.y + Math.sin(player.angle)*20; bullets.push({x: bx, y: by, vx: Math.cos(player.angle)*w.speed, vy: Math.sin(player.angle)*w.speed, life: 50, damage: w.damage}); }); document.getElementById('switchBtn').addEventListener('click', () => { // Tombol △ (Ganti Senjata) currentWeaponIdx = (currentWeaponIdx + 1) % WEAPONS.length; updateHUD(); }); document.getElementById('audioToggle').addEventListener('click', () => { Sound.init(); Sound.resume(); Sound.enabled = !Sound.enabled; document.getElementById('audioToggle').textContent = Sound.enabled ? '🔊' : '🔇'; }); document.getElementById('startBtn').addEventListener('click', () => { Sound.init(); Sound.resume(); document.getElementById('startScreen').classList.add('hidden'); resize(); resetGame(); running = true; lastTime = performance.now(); requestAnimationFrame(loop); }); document.getElementById('restartBtn').addEventListener('click', () => { document.getElementById('endScreen').classList.add('hidden'); resetGame(); running = true; lastTime = performance.now(); requestAnimationFrame(loop); }); function update(dt){ const step = dt / 16.67; gameTime += dt; if(player.fireCooldown > 0) player.fireCooldown -= step; let isMoving = false; if(!player.inCar){ let ix = joyX, iy = joyY; const mag = Math.hypot(ix, iy); if(mag > 0.1){ isMoving = true; const isRunning = document.getElementById('runBtn').matches(':active'); // Tombol ○ untuk Lari const spd = isRunning ? player.runSpeed : player.walkSpeed; const nx = player.x + ix * spd * step; const ny = player.y + iy * spd * step; if(!collideBuilding(nx, player.y, player.radius)) player.x = nx; if(!collideBuilding(player.x, ny, player.radius)) player.y = ny; player.angle = Math.atan2(iy, ix); player.walkPhase += 0.4 * step; } player.x = Math.max(20, Math.min(WORLD-20, player.x)); player.y = Math.max(20, Math.min(WORLD-20, player.y)); document.getElementById('speedVal').textContent = '0'; } else { const v = player.vehicle; v.speed += (-joyY) * 0.45 * step; v.speed *= 0.97; v.angle += joyX * 0.06 * (Math.abs(v.speed)/11) * step * Math.sign(v.speed||1); const nx = v.x + Math.cos(v.angle)*v.speed*step; const ny = v.y + Math.sin(v.angle)*v.speed*step; if(!collideBuilding(nx, ny, 18)){ v.x = nx; v.y = ny; } else { v.speed *= -0.3; } player.x = v.x; player.y = v.y; player.angle = v.angle; document.getElementById('speedVal').textContent = Math.round(Math.abs(v.speed)*20); } // Kendaraan AI di Jalan for(const v of vehicles){ if(v.isAi && v.occupied === false){ v.x += Math.cos(v.angle) * v.speed * step; v.y += Math.sin(v.angle) * v.speed * step; if(collideBuilding(v.x, v.y, 20)){ v.angle += Math.PI / 2; } } } // Pejalan Kaki NPC 3D Berjalan Dinamis for(const n of npcs){ n.x += Math.cos(n.angle) * n.speed * step; n.y += Math.sin(n.angle) * n.speed * step; if(collideBuilding(n.x, n.y, 10)){ n.angle += Math.PI; } n.walkPhase += 0.15 * step; } for(let i = bullets.length - 1; i >= 0; i--){ const b = bullets[i]; b.x += b.vx * step; b.y += b.vy * step; b.life -= step; for(const g of gasStations){ if(g.active && dist(b.x, b.y, g.x+g.w/2, g.y+g.h/2) < 65){ g.active = false; Sound.explosion(); bullets.splice(i, 1); continue; } } if(b.life <= 0 || collideBuilding(b.x, b.y, 4)){ bullets.splice(i, 1); } } // Minimap Render mmCtx.clearRect(0,0,115,115); mmCtx.fillStyle = '#111'; mmCtx.fillRect(0,0,115,115); mmCtx.fillStyle = '#00cec9'; mmCtx.beginPath(); mmCtx.arc(57.5,57.5,4,0,Math.PI*2); mmCtx.fill(); camX += (player.x - W/2 - camX) * 0.12 * step; camY += (player.y - H/2 - camY) * 0.12 * step; } function draw(){ ctx.fillStyle = '#151520'; ctx.fillRect(0,0,W,H); ctx.save(); ctx.translate(-camX, -camY); // Jalan Raya Grid ctx.fillStyle = '#222230'; for(let bx=0; bx<WORLD; bx+=BLOCK){ ctx.fillRect(bx, 0, ROAD, WORLD); ctx.fillRect(0, bx, WORLD, ROAD); } // Bangunan Perkotaan Lengkap for(const b of buildings){ ctx.fillStyle = b.color; ctx.fillRect(b.x, b.y, b.w, b.h); ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.strokeRect(b.x, b.y, b.w, b.h); } // SPBU (Bisa Meledak)[span_4](start_span)[span_4](end_span)[span_5](start_span)[span_5](end_span) for(const g of gasStations){ if(g.active){ ctx.fillStyle = '#e74c3c'; ctx.fillRect(g.x, g.y, g.w, g.h); ctx.fillStyle = '#fff'; ctx.font = 'bold 11px monospace'; ctx.fillText('SPBU', g.x+12, g.y+22); } } // Kendaraan Mobil & Motor for(const v of vehicles){ ctx.save(); ctx.translate(v.x, v.y); ctx.rotate(v.angle); ctx.fillStyle = v.color; ctx.fillRect(-v.w/2, -v.h/2, v.w, v.h); ctx.restore(); } // NPC Orang 3D Realistis Bergerak for(const n of npcs){ draw3DHuman(n.x, n.y, n.angle, n.walkPhase, n.shirtColor, n.pantsColor, n.skinColor, true); } // Karakter Pemain Orang 3D if(!player.inCar){ const isMoving = joyX !== 0 || joyY !== 0; draw3DHuman(player.x, player.y, player.angle, player.walkPhase, player.shirtColor, player.pantsColor, '#f0c090', isMoving); } // Tembakan Peluru for(const b of bullets){ ctx.fillStyle = '#f1c40f'; ctx.beginPath(); ctx.arc(b.x, b.y, 3, 0, Math.PI*2); ctx.fill(); } ctx.restore(); } function loop(t){ if(!running) return; const dt = Math.min(t - lastTime, 50); lastTime = t; update(dt); draw(); requestAnimationFrame(loop); } window.addEventListener('resize', resize); resize(); })(); </script> </body> </html>
💾 Simpan Perubahan
Batal
Nama Folder
Nama File PHP
Isi File (opsional)
🚀 Buat Folder & File
📋 Daftar Folder
game
🔗 Open
🗑 Hapus
index.php
👁 Lihat
✏️ Edit
🗑
✏️ Rename
+ Tambah File
movie
🔗 Open
🗑 Hapus
index.php
👁 Lihat
✏️ Edit
🗑
✏️ Rename
+ Tambah File