Skip to content

Input API

Pointer lock for first-person or mouse-based games.

Overview

Pointer lock (also called "mouse capture") hides the cursor and provides unlimited mouse movement. Essential for first-person games, mouse-look controls, or any game needing raw mouse input.

Methods

requestPointerLock()

Request pointer lock. Usually called after a user interaction (click).

javascript
await gemshell.input.requestPointerLock();

Returns: Promise<void>

User Interaction Required

Browsers require a user gesture (click) before pointer lock can be activated. Call this in response to a click event.

exitPointerLock()

Release pointer lock and show the cursor.

javascript
await gemshell.input.exitPointerLock();

Returns: Promise<void>

isPointerLocked()

Check if pointer lock is currently active.

javascript
const locked = await gemshell.input.isPointerLocked();

Returns: Promise<boolean>

Examples

First-Person Controls

javascript
// Start game on click
document.addEventListener('click', async () => {
  if (!await gemshell.input.isPointerLocked()) {
    await gemshell.input.requestPointerLock();
  }
});

// Handle mouse movement
document.addEventListener('mousemove', (e) => {
  if (isPointerLocked) {
    // Use movementX/Y for camera rotation
    camera.rotation.y -= e.movementX * sensitivity;
    camera.rotation.x -= e.movementY * sensitivity;
    camera.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, camera.rotation.x));
  }
});

// ESC releases pointer lock (browser default)
document.addEventListener('keydown', async (e) => {
  if (e.key === 'Escape') {
    await gemshell.input.exitPointerLock();
    showPauseMenu();
  }
});

Click to Play

javascript
let gameStarted = false;

async function startGame() {
  if (!gameStarted) {
    await gemshell.input.requestPointerLock();
    gameStarted = true;
    hideStartScreen();
    gameLoop();
  }
}

document.getElementById('startButton').addEventListener('click', startGame);

Pause Menu Integration

javascript
async function togglePause() {
  isPaused = !isPaused;
  
  if (isPaused) {
    await gemshell.input.exitPointerLock();
    showPauseMenu();
  } else {
    hidePauseMenu();
    await gemshell.input.requestPointerLock();
  }
}

// Check lock state changes
document.addEventListener('pointerlockchange', async () => {
  const locked = await gemshell.input.isPointerLocked();
  if (!locked && !isPaused) {
    // User pressed ESC or clicked outside - pause game
    isPaused = true;
    showPauseMenu();
  }
});

Mouse Look Toggle

javascript
let mouseLook = false;

async function toggleMouseLook() {
  mouseLook = !mouseLook;
  
  if (mouseLook) {
    await gemshell.input.requestPointerLock();
  } else {
    await gemshell.input.exitPointerLock();
  }
}

document.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    e.preventDefault();
    toggleMouseLook();
  }
});

RTS Camera

javascript
// Right-click drag for camera rotation
let isDragging = false;

document.addEventListener('mousedown', async (e) => {
  if (e.button === 2) { // Right click
    isDragging = true;
    await gemshell.input.requestPointerLock();
  }
});

document.addEventListener('mouseup', async (e) => {
  if (e.button === 2) {
    isDragging = false;
    await gemshell.input.exitPointerLock();
  }
});

document.addEventListener('mousemove', (e) => {
  if (isDragging) {
    camera.rotation.y -= e.movementX * 0.003;
    camera.pitch -= e.movementY * 0.003;
  }
});

Notes

  • Pointer lock is automatically released when the user presses Escape (browser behavior)
  • The cursor is hidden while locked
  • Use movementX and movementY from mouse events for relative movement
  • Some browsers show a notification when pointer lock is requested