Meta Description" name="description" />
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>BR 49 Bots Mobile</title>
<style>
html,body {margin:0; background:#000; overflow:hidden; touch-action:none}
canvas {display:block}
#fps {position:absolute; top:10px; left:10px; color:#0f0; font:14px monospace}
.joy {position:absolute; bottom:20px; width:120px; height:120px; border-radius:50%; background:#3338; border:2px solid #555}
#joystick {left:20px}
#shoot {right:20px; width:90px; height:90px; background:#f008}
.knob {position:absolute; width:50px; height:50px; border-radius:50%; background:#aaa; top:35px; left:35px}
</style>
</head>
<body>
<div id="fps">FPS: 0</div>
<canvas id="c"></canvas>
<div class="joy" id="joystick"><div class="knob" id="knob"></div></div>
<div class="joy" id="shoot"></div>
<script>
const c = http://document.getElementById('c');
const ctx = http://c.getContext('2d');
http://c.width = innerWidth; http://c.height = innerHeight;
const BOT_COUNT = 49;
const CELL = 100;
let last = http://performance.now();
let fps = 0;
// Game state
const bots = http://Array.from({length: BOT_COUNT}, () => ({
x: http://Math.random()_c.width, y: http://Math.random()_c.height,
vx:0, vy:0, r:12, alive:true
}));
const player = {x:c.width/2, y:c.height/2, r:14, alive:true};
let zoneR = http://Math.min(c.width,c.height)/2;
let zoneC = {x:c.width/2, y:c.height/2};
let bullets = [];
let moveVec = {x:0, y:0};
// Joystick control
const joy = http://document.getElementById('joystick');
const knob = http://document.getElementById('knob');
let joyActive = false;
http://joy.addEventListener('touchstart', e => joyActive = true);
http://joy.addEventListener('touchend', e => {
joyActive = false; moveVec = {x:0, y:0}; http://knob.style.transform = 'translate(0,0)';
});
http://joy.addEventListener('touchmove', e => {
if(!joyActive) return;
let t = http://e.touches;
let rect = http://joy.getBoundingClientRect();
let dx = http://t.clientX - http://rect.left - 60;
let dy = http://t.clientY - http://rect.top - 60;
let dist = http://Math.hypot(dx,dy);
let max = 45;
if(dist > max) { dx = dx/dist_max; dy = dy/dist_max; }
http://knob.style.transform = `translate(${dx}px, ${dy}px)`;
moveVec = {x: dx/max, y: dy/max};
});[0]
// Shoot button
http://document.getElementById('shoot').addEventListener('touchstart', e => {
if(!player.alive) return;
let angle = http://Math.atan2(moveVec.y, moveVec.x) || 0;
http://bullets.push({x:player.x, y:player.y, vx:Math.cos(angle)_12, vy:Math.sin(angle)_12, life:60});
});
function getGrid(x,y) {
return `${Math.floor(x/CELL)}_${Math.floor(y/CELL)}`;
}
function buildGrid() {
const grid = {};
for(let b of bots) if(b.alive) {
let key = getGrid(b.x,b.y);
(grid??= []).push(b);
}
return grid;
}[key]
function update() {
const now = http://performance.now();
fps = 0.9_fps + 0.1_(1000/(now-last));
last = now;
http://document.getElementById('fps').textContent = 'FPS: ' + http://fps.toFixed(0);
http://ctx.fillStyle = '#000';
http://ctx.fillRect(0,0,c.width,c.height);
zoneR = http://Math.max(80, zoneR - 0.4);
http://ctx.strokeStyle = '#fff';
http://ctx.beginPath();
http://ctx.arc(zoneC.x, zoneC.y, zoneR, 0, 7);
http://ctx.stroke();
// Player move dari joystick
if(player.alive) {
player.x += moveVec.x _ 6;
player.y += moveVec.y _ 6;
if(Math.hypot(player.x-zoneC.x, player.y-zoneC.y) > zoneR) http://player.alive = false;
}
const grid = buildGrid();
const playerCell = getGrid(player.x, player.y);
for(let b of bots) {
if(!b.alive) continue;
let = http://playerCell.split('_').map(Number);
let near = false;
for(let dx=-1; dx<=1; dx++) for(let dy=-1; dy<=1; dy++) {
if(grid[`${cx+dx}_${cy+dy}`]?.includes(b)) near = true;
}
if(near && http://player.alive) {
let dx = player.x - b.x, dy = player.y - b.y;
let d = http://Math.hypot(dx,dy) || 1;
http://b.vx = dx/d_3; http://b.vy = dy/d_3;
}
b.x += http://b.vx; b.y += http://b.vy;
if(Math.hypot(b.x-zoneC.x, b.y-zoneC.y) > zoneR) http://b.alive = false;[cx][cy]
http://ctx.fillStyle = '#ff3333';
http://ctx.beginPath();
http://ctx.arc(b.x, b.y, b.r, 0, 7);
http://ctx.fill();
if(player.alive && http://Math.hypot(b.x-player.x, b.y-player.y) < b.r+player.r) {
http://b.alive = false; http://player.alive = false;
}
}
bullets = http://bullets.filter(bl => {
bl.x += http://bl.vx; bl.y += http://bl.vy; http://bl.life--;
http://ctx.fillStyle = 'yellow';
http://ctx.fillRect(bl.x, bl.y, 3, 3);
let g = grid[getGrid(bl.x, bl.y)] || [];
for(let b of g) if(b.alive && http://Math.hypot(bl.x-b.x, bl.y-b.y) < b.r) {
http://b.alive = false; return false;
}
return http://bl.life > 0 && bl.x>0 && bl.x<c.width && bl.y>0 && bl.y<c.height;
});
if(player.alive) {
http://ctx.fillStyle = '#00aaff';
http://ctx.beginPath();
http://ctx.arc(player.x, player.y, player.r, 0, 7);
http://ctx.fill();
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
</script>
</body>
</html>1
1
6KB
6KB
79.0ms
172.0ms
80.0ms