Skip to content
Deasy Corporation
Logos
Theos
Work
Deasy Corporation
2026 AD
Games
Machine Gun POC
Back to Gaming Concepts
<html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>POC — Machine Gun (Customizable)</title> <style> :root { color-scheme: dark; } html, body { margin:0; height:100%; overflow:hidden; background:#000; font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; } #app { width:1500px; height:1500px; margin:0 auto; position:relative; background:#07080a; } canvas { display:block; width:100%; height:100%; cursor:crosshair; touch-action:none; } #hud, #hint { position:absolute; left:12px; color:#e9f8ff; background:rgba(0,0,0,.70); border:1px solid rgba(255,255,255,.18); border-radius:12px; padding:10px 12px; user-select:none; pointer-events:none; box-shadow:0 10px 30px rgba(0,0,0,.45); } #hud { top:12px; font-size:13px; line-height:1.35; width:410px; } #hint { bottom:12px; font-size:12px; line-height:1.35; width:520px; } .k{opacity:.75} .v{font-variant-numeric:tabular-nums} </style> </head> <body> <div id="app"> <canvas id="c" width="1500" height="1500"></canvas> <div id="hud"> <div><span class="k">Fire:</span> <span class="v" id="fire">OFF</span></div> <div><span class="k">RPM:</span> <span class="v" id="rpm">900</span> <span class="k">ms/shot:</span> <span class="v" id="mss">—</span></div> <div><span class="k">Spread:</span> <span class="v" id="spr">ON</span> <span class="k">deg:</span> <span class="v" id="sprd">—</span></div> <div><span class="k">Recoil:</span> <span class="v" id="rec">—</span> <span class="k">Bullet speed:</span> <span class="v" id="spd">—</span></div> <div><span class="k">Mag:</span> <span class="v" id="mag">—</span> <span class="k">Reload:</span> <span class="v" id="rl">—</span></div> <div><span class="k">Bullets live:</span> <span class="v" id="bul">0</span> <span class="k">Hits:</span> <span class="v" id="hit">0</span></div> </div> <div id="hint"> <div><b>Controls</b></div> <div><span class="k">Aim:</span> Mouse</div> <div><span class="k">Fire:</span> Hold LMB or Space</div> <div><span class="k">Reload:</span> R</div> <div><span class="k">RPM:</span> [ / ] (step) • Shift+[ / ] (big step)</div> <div><span class="k">Spread:</span> S toggle • - / = change cone • Shift(-/=) big change</div> <div><span class="k">Recoil:</span> K toggle • , / . adjust • Shift(, / .) big</div> <div><span class="k">Bullet speed:</span> V toggle slow-mo • 1 / 2 adjust speed</div> <div><span class="k">Mag / Reload:</span> M / N mag size • T / Y reload ms</div> <div><span class="k">Targets:</span> C reset targets</div> </div> </div> <script> (() => { const canvas = document.getElementById("c"); const ctx = canvas.getContext("2d", { alpha:false }); const elFire = document.getElementById("fire"); const elRpm = document.getElementById("rpm"); const elMss = document.getElementById("mss"); const elSpr = document.getElementById("spr"); const elSprd = document.getElementById("sprd"); const elRec = document.getElementById("rec"); const elSpd = document.getElementById("spd"); const elMag = document.getElementById("mag"); const elRl = document.getElementById("rl"); const elBul = document.getElementById("bul"); const elHit = document.getElementById("hit"); const W = canvas.width, H = canvas.height; // Gun in center const gun = { x: W/2, y: H/2, ang: 0 }; // Targets const targets = []; function spawnTargets() { targets.length = 0; const n = 12; for (let i=0;i<n;i++){ const x = 140 + Math.random()*(W-280); const y = 140 + Math.random()*(H-280); if (Math.hypot(x-gun.x,y-gun.y) < 260) { i--; continue; } targets.push({ x, y, r: 18, hp: 3, alive:true, flash:0 }); } } // FX const bullets = []; const impacts = []; // Controls let firing = false; // RPM (WIDER RANGE) let rpm = 900; // 60..3000 supported let msPerShot = 60000 / rpm; let lastShotAt = 0; // Spread (FIXED: make it obviously visible) let spreadOn = true; let spreadDeg = 6.0; // degrees full-cone, 0..35 let spreadRad = (spreadDeg * Math.PI/180) / 2; // half-angle radians // Recoil let recoilOn = true; let recoilKickDeg = 1.2; // degrees of visual kick per shot, 0..6 let recoilKick = recoilKickDeg * Math.PI/180; // Bullet tuning let bulletSpeed = 1400; // px/s, 400..3600 let bulletLife = 0.95; // seconds, 0.2..2.0 let slowMo = false; // V toggles // Ammo let magSize = 60; // 5..250 let ammo = magSize; let reloading = false; let reloadMs = 900; // 100..3000 let reloadUntil = 0; let hits = 0; function clamp(v, lo, hi){ return Math.max(lo, Math.min(hi, v)); } function recomputeRates(){ rpm = clamp(rpm, 60, 3000); msPerShot = 60000 / rpm; spreadDeg = clamp(spreadDeg, 0, 35); spreadRad = (spreadDeg * Math.PI/180) / 2; recoilKickDeg = clamp(recoilKickDeg, 0, 6); recoilKick = recoilKickDeg * Math.PI/180; bulletSpeed = clamp(bulletSpeed, 400, 3600); bulletLife = clamp(bulletLife, 0.2, 2.0); magSize = clamp(magSize, 5, 250); ammo = clamp(ammo, 0, magSize); reloadMs = clamp(reloadMs, 100, 3000); } recomputeRates(); function setAimFromMouse(e){ const r = canvas.getBoundingClientRect(); const mx = (e.clientX - r.left) * (canvas.width / r.width); const my = (e.clientY - r.top) * (canvas.height / r.height); gun.ang = Math.atan2(my - gun.y, mx - gun.x); } canvas.addEventListener("mousemove", setAimFromMouse); canvas.addEventListener("pointerdown", (e) => { if (e.button === 0) firing = true; canvas.setPointerCapture(e.pointerId); }); canvas.addEventListener("pointerup", (e) => { if (e.button === 0) firing = false; }); canvas.addEventListener("pointercancel", () => firing = false); canvas.addEventListener("contextmenu", (e) => e.preventDefault()); window.addEventListener("keydown", (e) => { const k = e.key; const kl = k.toLowerCase(); const big = e.shiftKey; if (k === " ") firing = true; if (kl === "r") startReload(); if (kl === "s") spreadOn = !spreadOn; if (kl === "k") recoilOn = !recoilOn; if (kl === "v") slowMo = !slowMo; // RPM if (k === "[") { rpm -= big ? 250 : 50; recomputeRates(); } if (k === "]") { rpm += big ? 250 : 50; recomputeRates(); } // Spread cone (use - and = keys) if (k === "-") { spreadDeg -= big ? 3 : 0.5; recomputeRates(); } if (k === "=") { spreadDeg += big ? 3 : 0.5; recomputeRates(); } // Recoil adjust if (k === ",") { recoilKickDeg -= big ? 0.6 : 0.1; recomputeRates(); } if (k === ".") { recoilKickDeg += big ? 0.6 : 0.1; recomputeRates(); } // Bullet speed adjust if (k === "1") { bulletSpeed -= big ? 300 : 100; recomputeRates(); } if (k === "2") { bulletSpeed += big ? 300 : 100; recomputeRates(); } // Mag size if (kl === "m") { magSize = clamp(magSize - (big ? 25 : 5), 5, 250); ammo = Math.min(ammo, magSize); } if (kl === "n") { magSize = clamp(magSize + (big ? 25 : 5), 5, 250); ammo = Math.min(ammo, magSize); } // Reload ms if (kl === "t") { reloadMs = clamp(reloadMs - (big ? 250 : 50), 100, 3000); } if (kl === "y") { reloadMs = clamp(reloadMs + (big ? 250 : 50), 100, 3000); } if (kl === "c") resetAll(); }); window.addEventListener("keyup", (e) => { if (e.key === " ") firing = false; }); function startReload(){ if (reloading) return; if (ammo === magSize) return; reloading = true; reloadUntil = performance.now() + reloadMs; } function resetAll(){ bullets.length = 0; impacts.length = 0; hits = 0; reloading = false; reloadUntil = 0; ammo = magSize; spawnTargets(); } function shoot(now){ if (reloading) return; if (ammo <= 0) { startReload(); return; } if (now - lastShotAt < msPerShot) return; lastShotAt = now; // Spread is now VERY visible: random within a cone (half-angle spreadRad) let ang = gun.ang; if (spreadOn && spreadRad > 0){ // uniform-ish in angle: jitter in [-spreadRad, +spreadRad] const jitter = (Math.random()*2 - 1) * spreadRad; ang += jitter; } const vx = Math.cos(ang) * bulletSpeed; const vy = Math.sin(ang) * bulletSpeed; // muzzle offset const muzzle = 30; const bx = gun.x + Math.cos(gun.ang) * muzzle; const by = gun.y + Math.sin(gun.ang) * muzzle; bullets.push({ x: bx, y: by, vx, vy, life: bulletLife, r: 2.2 }); // recoil (visual, small but adjustable) if (recoilOn && recoilKick > 0){ gun.ang += (Math.random()*2 - 1) * recoilKick; } ammo -= 1; } function update(dt, now){ const timeScale = slowMo ? 0.35 : 1.0; dt *= timeScale; // reload if (reloading){ const left = reloadUntil - now; if (left <= 0){ reloading = false; ammo = magSize; } } if (firing) shoot(now); // bullets for (let i = bullets.length - 1; i >= 0; i--){ const b = bullets[i]; b.x += b.vx * dt; b.y += b.vy * dt; b.life -= dt; if (b.x < -30 || b.x > W+30 || b.y < -30 || b.y > H+30 || b.life <= 0){ bullets.splice(i,1); continue; } for (let t=0; t<targets.length; t++){ const tg = targets[t]; if (!tg.alive) continue; const d = Math.hypot(b.x - tg.x, b.y - tg.y); if (d <= tg.r + b.r){ tg.hp -= 1; tg.flash = 0.10; hits += 1; impacts.push({ x: b.x, y: b.y, life: 0.14 }); bullets.splice(i,1); if (tg.hp <= 0) tg.alive = false; break; } } } for (const tg of targets){ if (tg.flash > 0) tg.flash = Math.max(0, tg.flash - dt); } for (let i = impacts.length - 1; i >= 0; i--){ impacts[i].life -= dt; if (impacts[i].life <= 0) impacts.splice(i,1); } } function draw(){ ctx.fillStyle = "#07080a"; ctx.fillRect(0,0,W,H); // subtle diagonal texture const g = ctx.createLinearGradient(0,0,W,H); g.addColorStop(0,"rgba(255,255,255,0.04)"); g.addColorStop(1,"rgba(0,0,0,0)"); ctx.fillStyle = g; ctx.fillRect(0,0,W,H); // aim ray ctx.strokeStyle = "rgba(255,255,255,0.14)"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(gun.x, gun.y); ctx.lineTo(gun.x + Math.cos(gun.ang)*2000, gun.y + Math.sin(gun.ang)*2000); ctx.stroke(); // spread cone visualization (only when firing or spreadOn) if (spreadOn && spreadRad > 0){ const r = 240; ctx.fillStyle = "rgba(255,255,255,0.03)"; ctx.strokeStyle = "rgba(255,255,255,0.12)"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(gun.x, gun.y); ctx.arc(gun.x, gun.y, r, gun.ang - spreadRad, gun.ang + spreadRad); ctx.closePath(); ctx.fill(); ctx.stroke(); } // targets for (const tg of targets){ if (!tg.alive) continue; ctx.fillStyle = tg.flash > 0 ? "rgba(255,255,255,0.26)" : "rgba(255,255,255,0.12)"; ctx.strokeStyle = "rgba(255,255,255,0.65)"; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(tg.x, tg.y, tg.r, 0, Math.PI*2); ctx.fill(); ctx.stroke(); ctx.fillStyle = "rgba(255,255,255,0.85)"; ctx.font = "12px system-ui"; ctx.fillText(String(tg.hp), tg.x - 4, tg.y + 4); } // impacts for (const p of impacts){ const a = clamp(p.life / 0.14, 0, 1); ctx.strokeStyle = `rgba(255,255,255,${0.65*a})`; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(p.x, p.y, 12*(1-a)+2, 0, Math.PI*2); ctx.stroke(); } // bullets ctx.fillStyle = "rgba(255,255,255,0.95)"; for (const b of bullets){ ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI*2); ctx.fill(); } // gun ctx.save(); ctx.translate(gun.x, gun.y); ctx.rotate(gun.ang); ctx.fillStyle = "rgba(255,255,255,0.14)"; ctx.strokeStyle = "rgba(255,255,255,0.75)"; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(0,0,18,0,Math.PI*2); ctx.fill(); ctx.stroke(); ctx.fillRect(8,-5,34,10); ctx.strokeRect(8,-5,34,10); // muzzle dot ctx.fillStyle = "rgba(255,255,255,0.9)"; ctx.beginPath(); ctx.arc(42,0,3.5,0,Math.PI*2); ctx.fill(); ctx.restore(); } let last = performance.now(); function loop(now){ const dt = Math.min(0.033, (now - last)/1000); last = now; update(dt, now); draw(); // HUD elFire.textContent = firing ? "ON" : "OFF"; elRpm.textContent = String(rpm|0); elMss.textContent = `${msPerShot.toFixed(1)}`; elSpr.textContent = spreadOn ? "ON" : "OFF"; elSprd.textContent = `${spreadDeg.toFixed(1)}`; elRec.textContent = recoilOn ? `${recoilKickDeg.toFixed(1)}°` : "OFF"; elSpd.textContent = `${bulletSpeed|0}` + (slowMo ? " (SLOW)" : ""); elMag.textContent = `${ammo}/${magSize}`; if (reloading){ const left = Math.max(0, reloadUntil - now); elRl.textContent = `${Math.ceil(left)}ms`; } else { elRl.textContent = `${reloadMs}ms`; } elBul.textContent = String(bullets.length); elHit.textContent = String(hits); requestAnimationFrame(loop); } spawnTargets(); requestAnimationFrame(loop); })(); </script> </body> </html>' ></iframe> </div>