Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungThe Homelab Is the New Resume(21.09.2026 um 00:29 Uhr)
Sichere ProgrammierungPerl 🐪 Weekly #791 - The Dark Side is here!(21.09.2026 um 00:44 Uhr)
Sichere Programmierungbro.js v3.0.0 – What’s new(21.09.2026 um 00:44 Uhr)
Sichere ProgrammierungFour bugs my test suite couldn't catch(21.09.2026 um 00:49 Uhr)
Sichere ProgrammierungAutomating Deployment with Github Actions(21.09.2026 um 00:49 Uhr)
Linux Tipps & HardeningKernel prepatch 7.3-rc4(21.09.2026 um 00:52 Uhr)
IT NachrichtenHow to use Xbox mode on your Windows PC(21.09.2026 um 00:30 Uhr)
Sichere ProgrammierungThe Homelab Is the New Resume(21.09.2026 um 00:29 Uhr)
Sichere ProgrammierungPerl 🐪 Weekly #791 - The Dark Side is here!(21.09.2026 um 00:44 Uhr)
Sichere Programmierungbro.js v3.0.0 – What’s new(21.09.2026 um 00:44 Uhr)
Sichere ProgrammierungFour bugs my test suite couldn't catch(21.09.2026 um 00:49 Uhr)
Sichere ProgrammierungAutomating Deployment with Github Actions(21.09.2026 um 00:49 Uhr)
Linux Tipps & HardeningKernel prepatch 7.3-rc4(21.09.2026 um 00:52 Uhr)
IT NachrichtenHow to use Xbox mode on your Windows PC(21.09.2026 um 00:30 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

The Hidden Complexity of Two-Player Browser Games — A Practical Guide to Keyboard Input

Reagiere als Erste:r — dein Feedback zählt!

I run 2playerfun.com, a site dedicated entirely to two-player browser games where both players share one keyboard. It's a niche that turned out to have more technical depth than I expected.

When I started, I assumed local multiplayer was the easy case. No netcode. No latency. No matchmaking. Just two players, one keyboard, browser-side state. What could go wrong?

What could go wrong, it turns out, is that two people pressing keys on a single keyboard at the same time is a surprisingly hostile environment.

This post is about what I've learned. If you're building a local-multiplayer browser game, or you've just always wondered why every two-player web game uses WASD and arrows specifically, read on.

Keyboard hardware actually matters

The thing nobody tells you when you start: keyboards have a property called N-key rollover (NKRO), and most keyboards don't have very good NKRO.

In practice this means: a cheap membrane keyboard might only register two or three simultaneous key presses correctly. If Player 1 is holding W and A to move diagonally, and Player 2 presses an arrow key at the same time, the keyboard might just not send the third event. Or worse, it might send a "ghost" event for a key that wasn't actually pressed.

This is hardware. There's nothing you can do about it from the browser. But there are things you can do that make the problem less common.

Why every 2P game uses WASD + Arrows

If you've played any local multiplayer browser game, you've seen this layout:

  • Player 1: WASD (movement) + nearby action keys (Q, E, F, Space)
  • Player 2: Arrow keys (movement) + nearby action keys (Enter, comma, period, M, /)

This isn't an accident. WASD and arrow keys live on opposite sides of the keyboard, on different rows of the underlying key matrix. On most keyboards, this means they're far less likely to ghost into each other than, say, WASD and TFGH would be.

You can almost always press WASD + arrows + 2–3 nearby action keys simultaneously without issues, even on a cheap keyboard. That's why the convention exists. It's a hardware-aware layout choice that the genre converged on through trial and error.

Lesson: if you're inventing your own scheme, don't put both players' keys in the same physical region of the keyboard. Use the WASD/arrow split unless you have a strong reason not to.

Browser event handling — the things that bit me

Once you've got your key layout, you have to actually capture the inputs in the browser. Here's where I learned a few things the hard way.

1. Use keydown and keyup, not keypress

The keypress event is deprecated and doesn't fire for non-character keys like arrows or modifiers. Use keydown for "key went down" and keyup for "key was released":

window.addEventListener('keydown', (e) => {
  // Handle press
});

window.addEventListener('keyup', (e) => {
  // Handle release
});

2. Use event.code, not event.key or event.keyCode

  • event.keyCode is deprecated.
  • event.key returns the character ("a", "A", "ArrowLeft"), which changes with keyboard layout (AZERTY vs QWERTY) and shift state.
  • event.code returns the physical key location ("KeyA", "ArrowLeft"), which is consistent across layouts.

For game input, you almost always want the physical key, not the character. Use event.code:

window.addEventListener('keydown', (e) => {
  switch (e.code) {
    case 'KeyW':    player1.up = true; break;
    case 'ArrowUp': player2.up = true; break;
  }
});

3. Handle key repeat correctly

When a key is held, the browser fires keydown repeatedly. For movement keys this is fine — your game loop reads the state every frame. For action keys (jump, shoot), you want to fire the action only on initial press, not on every repeat.

Two common patterns:

// Pattern A: skip repeats with event.repeat
window.addEventListener('keydown', (e) => {
  if (e.repeat) return;
  if (e.code === 'Space') player1.jump();
});

// Pattern B: track held keys, fire action on transition
const heldKeys = new Set();

window.addEventListener('keydown', (e) => {
  if (heldKeys.has(e.code)) return;
  heldKeys.add(e.code);
  if (e.code === 'Space') player1.jump();
});

window.addEventListener('keyup', (e) => {
  heldKeys.delete(e.code);
});

I prefer Pattern B because it gives you a clean "what's currently held" state for movement, plus reliable transition detection for actions, from the same data structure.

4. preventDefault aggressively (but not indiscriminately)

Browsers have default behavior for many keys:

  • Space scrolls the page.
  • Arrow keys scroll the page.
  • Tab moves focus.
  • / may open browser search.
  • F1–F12 do various OS/browser things.

If your game uses any of these for input, call event.preventDefault() or the browser hijacks the key:

const gameKeys = new Set([
  'Space',
  'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',
  'KeyW', 'KeyA', 'KeyS', 'KeyD',
  'Enter',
]);

window.addEventListener('keydown', (e) => {
  if (gameKeys.has(e.code)) {
    e.preventDefault();
  }
  // ... handle game input
});

Whitelist your game keys. Don't blanket-preventDefault everything — users still need Ctrl+R, Ctrl+Tab, and so on for browser functions. I've seen games that swallow Tab and break focus navigation site-wide. Don't be that game.

5. Focus matters

Your game needs focus to receive keyboard events. If you're rendering into a <canvas>, the canvas itself doesn't receive keyboard events by default. Two options:

  • Listen on window — simplest, but globally captures keys.
  • Make the canvas focusable with tabindex="0" and listen on the canvas — better for embeddable games.

For a dedicated 2P game page, window is fine. For games embedded inside a larger page, scope to the canvas.

The input architecture I actually use

Here's the pattern I use across 2playerfun games:

const players = {
  p1: { up: false, down: false, left: false, right: false, action: false },
  p2: { up: false, down: false, left: false, right: false, action: false },
};

const keyMap = {
  KeyW:       ['p1', 'up'],
  KeyS:       ['p1', 'down'],
  KeyA:       ['p1', 'left'],
  KeyD:       ['p1', 'right'],
  Space:      ['p1', 'action'],

  ArrowUp:    ['p2', 'up'],
  ArrowDown:  ['p2', 'down'],
  ArrowLeft:  ['p2', 'left'],
  ArrowRight: ['p2', 'right'],
  Enter:      ['p2', 'action'],
};

window.addEventListener('keydown', (e) => {
  const mapping = keyMap[e.code];
  if (!mapping) return;
  e.preventDefault();
  const [who, what] = mapping;
  players[who][what] = true;
});

window.addEventListener('keyup', (e) => {
  const mapping = keyMap[e.code];
  if (!mapping) return;
  const [who, what] = mapping;
  players[who][what] = false;
});

// In game loop:
function update(dt) {
  if (players.p1.up)    p1Sprite.y -= speed * dt;
  if (players.p1.down)  p1Sprite.y += speed * dt;
  if (players.p1.left)  p1Sprite.x -= speed * dt;
  if (players.p1.right) p1Sprite.x += speed * dt;
  // ... same for p2
}

The key idea: keep input state in a structured object that the game loop reads, instead of mutating game state directly in event handlers. This decouples input from game logic and makes a few things much easier:

  • Customizable key bindings — just rebuild keyMap.
  • Replay systems — serialize the players state per frame.
  • AI opponents — let the AI write to the state object the same way the keyboard does.
  • Networked multiplayer later — the state object is what you'd send over the wire.

This pattern looks trivial in isolation, but the savings compound over a dozen games.

The mobile/touch reality

Honest answer: local multiplayer on mobile is mostly broken.

In theory you can put two touch zones on screen — one player on the left, one on the right. In practice this works for very simple games (tap-to-jump, swipe-to-move) but breaks down for anything needing simultaneous directional + action input. The device is also held by one person, which adds awkward physical dynamics.

For 2playerfun, my pragmatic answer is: most games are desktop-only, and mobile users see a polite "this game works best on a desktop with two players sharing a keyboard" message. Trying to force mobile two-player support has been more trouble than it's worth, and the audience that wants couch co-op is mostly on laptops anyway.

What I wish I'd known earlier

Test on a cheap keyboard. Your nice mechanical board with full NKRO will tell you nothing about what 90% of your users experience. Buy a $15 USB membrane keyboard and test 2P games on it. You will find ghosting issues you didn't know existed.

Use event.code from day one. Switching from event.key or event.keyCode later means rewriting input across every game. Save yourself the rework.

Don't blanket-preventDefault. Be specific about which keys your game uses, and let the rest behave normally.

Build the input layer once, reuse it. The keyMap → state object → game-loop-reads-state pattern works for almost every local 2P game I've built. Once it's working, copy it forward.

Wrapping up

Local multiplayer browser games feel like a niche from 2008, but the genre is having a quiet renaissance. People want to play games together in person again. Sharing a couch and a laptop is a perfectly valid form of multiplayer, and the browser is an extremely accessible platform for it — no installs, no accounts, no friend codes, no matchmaking.

If you're building one, I hope some of this saves you a few hours.

If you're just looking to play one, 2playerfun.com has a couple hundred of them, all using roughly the input architecture above.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten The Hidden Complexity of Two-Player Browser Games — A Practical Guide to Keyboard Input

Thematisch verwandte Begriffe: Hidden, Complexity, TwoPlayer, Browser · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94084 | Suricata before 8.0.7 has an Http2ThreadMultiBuf use-after-free when a t…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick