W Library Utilities
The Lab13 SDK provides several utilities specifically designed to work with the W WebGL framework. These utilities handle common tasks like canvas resizing, input handling, and animation timing.
Note: This tutorial covers the W utilities in the Lab13 SDK. For a complete W library tutorial, see the official W documentation.
Overview
The Lab13 SDK includes these W-specific utilities:
useResizer
- Automatic canvas resizing and WebGL viewport updatesuseSpeedThrottledRaf
- Speed-throttled animation frames for consistent movementusePointerLock
- Mouse pointer lock for first-person camera controluseKeyboard
- Keyboard input handling
useResizer
Automatically handles canvas resizing and WebGL viewport updates when the window is resized.
import { useResizer } from 'lab13-sdk'
// Initialize W
W.reset(c)
W.light({ y: -1 })
W.ambient(0.2)
// Automatically resizes canvas to fill window
// Updates WebGL viewport and projection matrix
useResizer()
What it does:
- Resizes the canvas to
window.innerWidth
×window.innerHeight
- Updates the WebGL viewport with the new dimensions
- Recalculates the projection matrix with the new aspect ratio
- Listens for window resize events
Example with custom FOV:
import { useResizer } from 'lab13-sdk'
W.reset(c)
W.camera({ fov: 45 }) // Custom field of view
useResizer() // Will maintain your 45° FOV when resizing
useSpeedThrottledRaf
Provides speed-throttled animation frames for consistent movement regardless of frame rate.
import { useSpeedThrottledRaf } from 'lab13-sdk'
const MOVE_SPEED = 20 // units per second
useSpeedThrottledRaf(MOVE_SPEED, (speed) => {
// This function is called with consistent speed values
// regardless of frame rate (60fps, 30fps, etc.)
if (isKeyPressed('w')) {
W.move({
n: 'player',
z: player.z - speed,
a: 16,
})
}
})
How it works:
- Calculates delta time between frames
- Multiplies your move speed by delta time
- Ensures consistent movement speed regardless of frame rate
- Uses
requestAnimationFrame
internally
Example with multiple movements:
import { useSpeedThrottledRaf } from 'lab13-sdk'
const MOVE_SPEED = 15
const ROTATION_SPEED = 90 // degrees per second
useSpeedThrottledRaf(MOVE_SPEED, (speed) => {
const rotationSpeed = (ROTATION_SPEED * speed) / MOVE_SPEED
// Handle movement
if (isKeyPressed('w')) {
W.move({ n: 'player', z: player.z - speed, a: 16 })
}
// Handle rotation
if (isKeyPressed('q')) {
W.move({ n: 'player', ry: player.ry - rotationSpeed, a: 16 })
}
if (isKeyPressed('e')) {
W.move({ n: 'player', ry: player.ry + rotationSpeed, a: 16 })
}
})
usePointerLock
Handles mouse pointer lock for first-person camera control.
import { usePointerLock } from 'lab13-sdk'
let cameraY = 0
let cameraX = 0
usePointerLock({
onMove: (e) => {
cameraY -= e.movementX * 0.1
cameraX -= e.movementY * 0.1
cameraX = Math.max(-90, Math.min(90, cameraX)) // Clamp vertical rotation
W.move({
n: 'camera',
rx: cameraX,
ry: cameraY,
a: 16,
})
},
element: c, // canvas element
})
Options:
interface PointerLockOptions {
onMove: (e: MouseEvent) => void
element: HTMLElement // defaults to document.body
}
Example with sensitivity control:
import { usePointerLock } from 'lab13-sdk'
const MOUSE_SENSITIVITY = 0.05
usePointerLock({
onMove: (e) => {
const deltaX = e.movementX * MOUSE_SENSITIVITY
const deltaY = e.movementY * MOUSE_SENSITIVITY
updateMyState({
ry: me().ry - deltaX,
rx: Math.max(-90, Math.min(90, me().rx - deltaY)),
})
},
element: c,
})
useKeyboard
Provides keyboard input handling with a simple API.
import { useKeyboard } from 'lab13-sdk'
const { isKeyPressed, getKeys } = useKeyboard()
// Check if a key is currently pressed
if (isKeyPressed('w')) {
// Move forward
}
// Get all pressed keys
const keys = getKeys()
console.log(
'Pressed keys:',
Object.keys(keys).filter((k) => keys[k])
)
API:
isKeyPressed(key: string)
- Returnstrue
if the key is currently pressedgetKeys()
- Returns an object with all key states
Example with WASD movement:
import { useKeyboard } from 'lab13-sdk'
const { isKeyPressed } = useKeyboard()
const updatePlayerMovement = (speed: number) => {
const angle = (me().ry * Math.PI) / 180
const forwardX = Math.sin(angle) * speed
const forwardZ = Math.cos(angle) * speed
if (isKeyPressed('w')) {
updateMyState({ x: me().x - forwardX, z: me().z - forwardZ })
}
if (isKeyPressed('s')) {
updateMyState({ x: me().x + forwardX, z: me().z + forwardZ })
}
if (isKeyPressed('a')) {
updateMyState({ x: me().x - forwardZ, z: me().z + forwardX })
}
if (isKeyPressed('d')) {
updateMyState({ x: me().x + forwardZ, z: me().z - forwardX })
}
}
Complete Example
Here's a complete example using all the W utilities with a multiplayer game:
import { useOnline, useEasyState, useResizer, useKeyboard, usePointerLock, useSpeedThrottledRaf } from 'lab13-sdk'
// Initialize multiplayer
useOnline('mygame/room')
// Initialize W
W.reset(c)
W.light({ y: -1 })
W.ambient(0.2)
W.clearColor('#87CEEB')
// Create world
W.plane({
n: 'ground',
y: -1,
w: 20,
h: 1,
d: 20,
b: '#228B22',
})
// Setup utilities
useResizer()
const { isKeyPressed } = useKeyboard()
usePointerLock({
onMove: (e) => {
updateMyState({ ry: me().ry - e.movementX * 0.1 })
},
element: c,
})
// Player state management
const { updateMyState, getPlayerStates } = useEasyState({
onPlayerStateAvailable: (id, state) => {
// Create player when they join
W.group({ n: id })
W.cube({
g: id,
n: `${id}-body`,
y: 0.5,
w: 1,
h: 1,
d: 0.5,
b: state.b,
})
},
})
// Camera follows player
W.camera({ g: 'me', x: 0, y: 1.5, z: 2.5 })
// Game loop with speed-throttled movement
useSpeedThrottledRaf(20, (speed) => {
// Handle input
const angle = (me().ry * Math.PI) / 180
const forwardX = Math.sin(angle) * speed
const forwardZ = Math.cos(angle) * speed
if (isKeyPressed('w')) {
updateMyState({ x: me().x - forwardX, z: me().z - forwardZ })
}
if (isKeyPressed('s')) {
updateMyState({ x: me().x + forwardX, z: me().z + forwardZ })
}
if (isKeyPressed('a')) {
updateMyState({ x: me().x - forwardZ, z: me().z + forwardX })
}
if (isKeyPressed('d')) {
updateMyState({ x: me().x + forwardZ, z: me().z - forwardX })
}
// Update all players
const players = getPlayerStates()
for (const [id, state] of Object.entries(players)) {
W.move({
n: id,
x: state.x,
z: state.z,
ry: state.ry,
a: 16,
})
}
})
Integration Tips
Performance Optimization
- Use
useSpeedThrottledRaf
instead of rawrequestAnimationFrame
for consistent movement - Keep animation durations short (16ms) for real-time movement
- Use groups to move complex objects efficiently
Multiplayer Best Practices
- Update player positions in the speed-throttled loop
- Use
onPlayerStateAvailable
to create player objects when they join - Keep state updates minimal to reduce network traffic
Input Handling
- Use
usePointerLock
for first-person games - Use
useKeyboard
for consistent key detection - Combine input handling with speed-throttled updates
Learn More
- Official W Documentation - Complete W library tutorial and API reference
- W Examples - Interactive examples and demos
- Lab13 SDK Documentation - Other SDK utilities and features