Player moves freely inside a center “dead zone”; the camera only moves when the player exits that zone


Code for Above

<div style="max-width:1500px;margin:0 auto;">
<iframe
  title="POC — Camera Dead-Zone Follow"
  scrolling="no"
  style="width:1500px;height:1500px;border:0;display:block;background:#111;border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,.5);"
  sandbox="allow-scripts allow-same-origin"
  srcdoc='
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>POC — Camera Dead-Zone Follow</title>
<style>
  html, body { margin:0; width:100%; height:100%; overflow:hidden; background:#111; }
  canvas { display:block; }
  .hint { position:absolute; left:12px; bottom:12px; color:#aaa; font:14px system-ui; }
</style>
</head>
<body>
<canvas id="c" width="1500" height="1500"></canvas>
<div class="hint">Arrows: move • Camera moves only when you leave the center box</div>

<script>
const c = document.getElementById("c");
const x = c.getContext("2d");

const VW = 1500, VH = 1500;
const WW = 4200, WH = 4200;

const p = { x: WW/2, y: WH/2, vx: 0, vy: 0 };
const cam = { x: p.x - VW/2, y: p.y - VH/2 };

// dead-zone size (screen-space)
const DZ_W = 520;
const DZ_H = 520;

const ACCEL = 0.45;
const DRAG  = 0.92;
const MAX   = 12;

const k = {};
addEventListener("keydown", e => k[e.key] = true);
addEventListener("keyup",   e => k[e.key] = false);

function clamp(v,a,b){ return Math.max(a, Math.min(b, v)); }

function update(){
  if (k["ArrowUp"])    p.vy -= ACCEL;
  if (k["ArrowDown"])  p.vy += ACCEL;
  if (k["ArrowLeft"])  p.vx -= ACCEL;
  if (k["ArrowRight"]) p.vx += ACCEL;

  p.vx *= DRAG; p.vy *= DRAG;

  const sp = Math.hypot(p.vx, p.vy);
  if (sp > MAX) { p.vx = (p.vx/sp)*MAX; p.vy = (p.vy/sp)*MAX; }

  p.x = clamp(p.x + p.vx, 0, WW);
  p.y = clamp(p.y + p.vy, 0, WH);

  // player in screen coords
  let sx = p.x - cam.x;
  let sy = p.y - cam.y;

  // dead-zone bounds (screen coords)
  const left   = (VW - DZ_W)/2;
  const right  = left + DZ_W;
  const top    = (VH - DZ_H)/2;
  const bottom = top + DZ_H;

  // move camera only if player exits dead-zone
  if (sx < left)   cam.x -= (left - sx);
  if (sx > right)  cam.x += (sx - right);
  if (sy < top)    cam.y -= (top - sy);
  if (sy > bottom) cam.y += (sy - bottom);

  // clamp camera
  cam.x = clamp(cam.x, 0, WW - VW);
  cam.y = clamp(cam.y, 0, WH - VH);
}

function drawGrid(){
  x.strokeStyle = "rgba(255,255,255,0.07)";
  x.lineWidth = 1;
  const step = 150;

  const startX = Math.floor(cam.x / step) * step;
  const startY = Math.floor(cam.y / step) * step;

  for (let gx = startX; gx <= cam.x + VW; gx += step){
    const px = gx - cam.x;
    x.beginPath(); x.moveTo(px, 0); x.lineTo(px, VH); x.stroke();
  }
  for (let gy = startY; gy <= cam.y + VH; gy += step){
    const py = gy - cam.y;
    x.beginPath(); x.moveTo(0, py); x.lineTo(VW, py); x.stroke();
  }

  x.strokeStyle = "rgba(255,255,255,0.18)";
  x.strokeRect(-cam.x, -cam.y, WW, WH);
}

function draw(){
  x.clearRect(0,0,VW,VH);

  drawGrid();

  // dead-zone overlay
  const dzx = (VW - DZ_W)/2, dzy = (VH - DZ_H)/2;
  x.strokeStyle = "rgba(255,255,255,0.22)";
  x.lineWidth = 2;
  x.strokeRect(dzx, dzy, DZ_W, DZ_H);

  // player
  const sx = p.x - cam.x;
  const sy = p.y - cam.y;
  x.fillStyle = "#fff";
  x.beginPath();
  x.arc(sx, sy, 10, 0, Math.PI*2);
  x.fill();

  x.fillStyle = "rgba(255,255,255,0.5)";
  x.font = "14px system-ui";
  x.fillText(`player: (${p.x|0}, ${p.y|0})  cam: (${cam.x|0}, ${cam.y|0})  dead-zone: ${DZ_W}x${DZ_H}`, 12, 24);
}

(function loop(){
  update();
  draw();
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>
'></iframe>
</div>