A complete demo with input and simple collision. Use W/S and arrow keys to move the paddles.
You are viewing: Browser (WASM) · Switch
canvas with id vulfram-canvas.W / S and the Up / Down arrows.
Click the canvas to focus.1. Create paddles, ball, and the backdrop.
2. Read the keyboard with isKeyPressed.
3. Update positions and do simple collision each frame.
import {
initEngine,
createWorld,
createWindow,
createEntity,
createCamera,
createLight,
createGeometry,
createMaterial,
createModel,
createTexture,
updateTransform,
isKeyPressed,
tick,
} from '@vulfram/engine';
import { initWasmTransport, transportWasm } from '@vulfram/transport-wasm';
const WINDOW_ID = 1;
const KEY = {
KeyW: 41,
KeyS: 37,
ArrowUp: 74,
ArrowDown: 71,
} as const;
function createColorMaterial(color: [number, number, number, number]): number {
const texId = createTexture(WINDOW_ID, { source: { type: 'color', color }, srgb: true });
return createMaterial(WINDOW_ID, {
kind: 'standard',
options: {
type: 'standard',
content: {
baseColor: [1, 1, 1, 1],
surfaceType: 'opaque',
baseTexId: texId,
baseSampler: 'linear-clamp',
flags: 0,
},
},
});
}
async function boot() {
await initWasmTransport();
initEngine({ transport: transportWasm });
createWorld(WINDOW_ID);
createWindow(WINDOW_ID, {
title: 'Pong',
size: [1100, 700],
position: [0, 0],
canvasId: 'vulfram-canvas',
});
const camera = createEntity(WINDOW_ID);
updateTransform(WINDOW_ID, camera, { position: [0, 0, 12], rotation: [0, 0, 0, 1], scale: [1, 1, 1] });
createCamera(WINDOW_ID, camera, { kind: 'perspective', near: 0.1, far: 100.0 });
const light = createEntity(WINDOW_ID);
updateTransform(WINDOW_ID, light, { position: [2, 4, 6], rotation: [0, 0, 0, 1], scale: [1, 1, 1] });
createLight(WINDOW_ID, light, { kind: 'point', intensity: 16, range: 30 });
const paddleGeom = createGeometry(WINDOW_ID, { type: 'primitive', shape: 'cube' });
const ballGeom = createGeometry(WINDOW_ID, { type: 'primitive', shape: 'sphere', options: { radius: 0.4, sectors: 24, stacks: 16 } });
const backdropGeom = createGeometry(WINDOW_ID, { type: 'primitive', shape: 'plane', options: { size: [18, 10, 1], subdivisions: 1 } });
const redMat = createColorMaterial([0.95, 0.25, 0.25, 1]);
const blueMat = createColorMaterial([0.25, 0.4, 0.95, 1]);
const yellowMat = createColorMaterial([1, 0.9, 0.2, 1]);
const backdropMat = createColorMaterial([0.12, 0.12, 0.15, 1]);
const backdrop = createEntity(WINDOW_ID);
updateTransform(WINDOW_ID, backdrop, { position: [0, 0, -2], rotation: [0, 0, 0, 1], scale: [1, 1, 1] });
createModel(WINDOW_ID, backdrop, { geometryId: backdropGeom, materialId: backdropMat });
const left = createEntity(WINDOW_ID);
const right = createEntity(WINDOW_ID);
const ball = createEntity(WINDOW_ID);
const fieldW = 10;
const fieldH = 7;
const paddleH = 1.7;
const paddleW = 0.3;
const ballSize = 0.45;
let leftY = 0;
let rightY = 0;
let ballX = 0;
let ballY = 0;
let ballVelX = 4.2;
let ballVelY = 2.6;
updateTransform(WINDOW_ID, left, { position: [-fieldW / 2 + 0.6, 0, 0], rotation: [0, 0, 0, 1], scale: [paddleW, paddleH, 0.3] });
updateTransform(WINDOW_ID, right, { position: [fieldW / 2 - 0.6, 0, 0], rotation: [0, 0, 0, 1], scale: [paddleW, paddleH, 0.3] });
updateTransform(WINDOW_ID, ball, { position: [0, 0, 0], rotation: [0, 0, 0, 1], scale: [ballSize, ballSize, ballSize] });
createModel(WINDOW_ID, left, { geometryId: paddleGeom, materialId: redMat });
createModel(WINDOW_ID, right, { geometryId: paddleGeom, materialId: blueMat });
createModel(WINDOW_ID, ball, { geometryId: ballGeom, materialId: yellowMat });
let last = performance.now();
function frame(now: number) {
const deltaMs = now - last;
const delta = deltaMs / 1000;
last = now;
const speed = 6.0;
if (isKeyPressed(WINDOW_ID, KEY.KeyW)) leftY += speed * delta;
if (isKeyPressed(WINDOW_ID, KEY.KeyS)) leftY -= speed * delta;
if (isKeyPressed(WINDOW_ID, KEY.ArrowUp)) rightY += speed * delta;
if (isKeyPressed(WINDOW_ID, KEY.ArrowDown)) rightY -= speed * delta;
leftY = Math.max(-fieldH / 2 + paddleH / 2, Math.min(fieldH / 2 - paddleH / 2, leftY));
rightY = Math.max(-fieldH / 2 + paddleH / 2, Math.min(fieldH / 2 - paddleH / 2, rightY));
ballX += ballVelX * delta;
ballY += ballVelY * delta;
if (ballY > fieldH / 2 - ballSize || ballY < -fieldH / 2 + ballSize) {
ballVelY *= -1;
ballY = Math.max(-fieldH / 2 + ballSize, Math.min(fieldH / 2 - ballSize, ballY));
}
const leftX = -fieldW / 2 + 0.6;
const rightX = fieldW / 2 - 0.6;
const hitLeft = ballX - ballSize < leftX + paddleW && ballX > leftX && Math.abs(ballY - leftY) < paddleH / 2 + ballSize * 0.3;
const hitRight = ballX + ballSize > rightX - paddleW && ballX < rightX && Math.abs(ballY - rightY) < paddleH / 2 + ballSize * 0.3;
if (hitLeft) {
ballVelX = Math.abs(ballVelX);
} else if (hitRight) {
ballVelX = -Math.abs(ballVelX);
}
if (ballX > fieldW / 2 + 2 || ballX < -fieldW / 2 - 2) {
ballX = 0;
ballY = 0;
ballVelX = Math.sign(ballVelX) * 4.2;
}
updateTransform(WINDOW_ID, left, { position: [leftX, leftY, 0] });
updateTransform(WINDOW_ID, right, { position: [rightX, rightY, 0] });
updateTransform(WINDOW_ID, ball, { position: [ballX, ballY, 0] });
tick(now, deltaMs);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
boot().catch(console.error);