Web Engine Docs
Preparing documentation
Use the search bar to quickly find any topic
Preparing documentation
Use the search bar to quickly find any topic
Complete, production-ready script examples for common game mechanics. Copy and adapt these patterns for your project.
This page provides complete, tested script examples for common game mechanics. Each example demonstrates best practices and performance patterns used in Web Engine.
WASD movement, mouse look, jumping
Third-person and orbit cameras
Bullet spawning with cooldowns
Damage, health, and death
Trigger-based item collection
NavMesh-based enemy patrol
A complete first-person or third-person character controller with WASD movement, mouse look, sprinting, and jumping. Requires CharacterController and RigidBody components.
export default (api) => {// Configurationconst speed = 5.0;const sprintMultiplier = 2.0;const jumpForce = 10.0;const sensitivity = 0.002;// Statelet yaw = 0;let pitch = 0;const maxPitch = Math.PI / 3; // 60 degrees// Pooled vectors (reuse to avoid GC)const moveVec = { x: 0, y: 0, z: 0 };return async (dt, time) => {const input = api.input;// Mouse Lookif (input.look.x !== 0 || input.look.y !== 0) {yaw -= input.look.x * sensitivity;pitch -= input.look.y * sensitivity;pitch = Math.max(-maxPitch, Math.min(maxPitch, pitch));// Update rotation (only yaw for capsule)const rot = api.rotation;rot.y = yaw;api.rotation = rot;}// Movement (relative to camera)const moveLen = Math.sqrt(input.move.x ** 2 + input.move.y ** 2);if (moveLen > 0.01) {const currentSpeed = input.sprint ? speed * sprintMultiplier : speed;const sin = Math.sin(yaw);const cos = Math.cos(yaw);// Forward/backward (input.move.y)const fwdX = -sin * input.move.y;const fwdZ = -cos * input.move.y;// Left/right (input.move.x)const rightX = cos * input.move.x;const rightZ = -sin * input.move.x;moveVec.x = (fwdX + rightX) * currentSpeed;moveVec.y = 0;moveVec.z = (fwdZ + rightZ) * currentSpeed;api.controller.move(moveVec);}// Jumpingif (input.jumpDown && api.controller.isGrounded) {api.applyImpulse({ x: 0, y: jumpForce, z: 0 });}};};
Performance Notes
Reusing the moveVec object avoids creating new Vector3 instances every frame, reducing garbage collection pressure.
A smooth third-person camera that follows a target entity. Requires a Target component pointing to the player.
export default (api) => {// Camera offset from target (local space)const offset = { x: 0, y: 2, z: -5 };const smoothness = 8.0;// Pooled vectorsconst desiredPos = { x: 0, y: 0, z: 0 };return (dt, time) => {if (!api.target.exists) return;const target = api.target;// Calculate desired position in world space// Rotate offset by target's rotationconst rot = target.rotation;const sin = Math.sin(rot.y);const cos = Math.cos(rot.y);// Rotate offset vectorconst offsetX = offset.x * cos - offset.z * sin;const offsetZ = offset.x * sin + offset.z * cos;desiredPos.x = target.position.x + offsetX;desiredPos.y = target.position.y + offset.y;desiredPos.z = target.position.z + offsetZ;// Smooth lerp toward desired positionconst t = Math.min(dt * smoothness, 1.0);const pos = api.position;pos.x += (desiredPos.x - pos.x) * t;pos.y += (desiredPos.y - pos.y) * t;pos.z += (desiredPos.z - pos.z) * t;api.position = pos;// Look at targetapi.lookAt(target.position);};};
Spawn bullets on input with a fire rate cooldown. Demonstrates prefab spawning and timer management.
export default (api) => {const fireRate = 0.15; // 6.67 shots per secondconst bulletSpeed = 50.0;const muzzleOffset = { x: 0, y: 0.5, z: -0.5 };let cooldown = 0;// Pooled vectorsconst spawnPos = { x: 0, y: 0, z: 0 };const spawnRot = { x: 0, y: 0, z: 0 };return async (dt, time) => {cooldown -= dt;if (api.input.fireDown && cooldown <= 0) {cooldown = fireRate;// Calculate spawn position (muzzle)const pos = api.position;const rot = api.rotation;const sin = Math.sin(rot.y);const cos = Math.cos(rot.y);spawnPos.x = pos.x + muzzleOffset.x * cos - muzzleOffset.z * sin;spawnPos.y = pos.y + muzzleOffset.y;spawnPos.z = pos.z + muzzleOffset.x * sin + muzzleOffset.z * cos;spawnRot.x = rot.x;spawnRot.y = rot.y;spawnRot.z = rot.z;// Spawn bullet prefabconst bulletEid = await api.spawn("Bullet", spawnPos, spawnRot);// Play sound effectapi.audio.play();api.log("Fired bullet: " + bulletEid);}};};
Prefab Setup
This script requires a Bullet prefab with a RigidBody component set to Dynamic type. The bullet should have its own script to handle velocity and lifetime.
A damage-able entity with health, taking damage on collision. Demonstrates collision events and entity destruction.
export default (api) => {let health = 100;const maxHealth = 100;const damagePerHit = 10;// Invincibility frames to prevent multiple hits in one collisionlet invincible = false;let invincibleTimer = 0;const invincibleDuration = 0.5; // 500msreturn {onUpdate: (dt, time) => {if (invincible) {invincibleTimer -= dt;if (invincibleTimer <= 0) {invincible = false;}}},onCollisionEnter: (otherEid) => {// Only take damage if not invincibleif (invincible) return;// Check if collided with a damage source (e.g., bullet)// You could add tags or check component types herehealth -= damagePerHit;invincible = true;invincibleTimer = invincibleDuration;api.log(`Took damage! Health: ${health}/${maxHealth}`);// Visual feedback (optional)// api.audio.play(); // Hit sound// Check for deathif (health <= 0) {api.log("Destroyed!");// Spawn death effect// await api.spawn("DeathEffect", api.position);// The entity will be removed by the engine}},onDestroy: (time) => {api.log("Health component destroyed");}};};
A collectible item that triggers when the player enters its area. Requires a Sensor component for trigger detection.
export default (api) => {const rotationSpeed = 2.0; // Rotate for visibilityconst bobSpeed = 2.0;const bobAmount = 0.2;let collected = false;const baseY = api.position.y;return {onUpdate: (dt, time) => {if (collected) return;// Rotateconst rot = api.rotation;rot.y += rotationSpeed * dt;api.rotation = rot;// Bob up and downconst pos = api.position;pos.y = baseY + Math.sin(time * bobSpeed) * bobAmount;api.position = pos;},onTriggerEnter: (otherEid) => {if (collected) return;// Check if it's the player (you could add a tag system)// For now, collect on any triggercollected = true;api.log(`Collected by entity ${otherEid}`);// Play collect soundapi.audio.play();// Grant item to player (implement via event system)// events.emit("item_collected", { type: "coin", value: 10 });// Destroy this entity// The engine will handle cleanup after this frame}};};
An AI agent that patrols between waypoints using the NavMesh system. Requires NavMeshAgent and Steering components.
export default (api) => {// Waypoint positionsconst waypoints = [{ x: 0, y: 0, z: 0 },{ x: 10, y: 0, z: 0 },{ x: 10, y: 0, z: 10 },{ x: 0, y: 0, z: 10 }];let currentWaypoint = 0;let waitTimer = 0;const waitDuration = 2.0; // Wait 2s at each waypointconst reachedThreshold = 1.0; // Distance to consider "reached"return {onStart: (time) => {// Start moving to first waypointapi.navigation.setDestination(waypoints[currentWaypoint]);api.navigation.setSpeed(3.0);api.log("Starting patrol");},onUpdate: (dt, time) => {// If waiting at waypointif (waitTimer > 0) {waitTimer -= dt;if (waitTimer <= 0) {// Move to next waypointcurrentWaypoint = (currentWaypoint + 1) % waypoints.length;api.navigation.setDestination(waypoints[currentWaypoint]);api.log(`Moving to waypoint ${currentWaypoint}`);}return;}// Check if reached current waypointif (!api.navigation.isMoving && !api.navigation.isPathPending) {const pos = api.position;const target = waypoints[currentWaypoint];const dx = target.x - pos.x;const dz = target.z - pos.z;const dist = Math.sqrt(dx * dx + dz * dz);if (dist < reachedThreshold) {api.log(`Reached waypoint ${currentWaypoint}`);waitTimer = waitDuration;}}}};};
Dynamic Waypoints
For dynamic waypoints, you could store waypoint entities in the scene and reference them via Target components, or use an event system to update waypoints at runtime.
A companion script for the projectile spawning example. Handles bullet velocity and automatic destruction after a timeout.
export default (api) => {const speed = 50.0;const lifetime = 5.0; // Destroy after 5 secondslet aliveTime = 0;return {onStart: (time) => {// Apply initial velocity in forward directionconst rot = api.rotation;const sin = Math.sin(rot.y);const cos = Math.cos(rot.y);api.velocity = {x: -sin * speed,y: 0,z: -cos * speed};},onUpdate: (dt, time) => {aliveTime += dt;// Destroy after lifetimeif (aliveTime >= lifetime) {api.log("Bullet expired");// Entity will be removed by engine}},onCollisionEnter: (otherEid) => {api.log("Bullet hit entity " + otherEid);// Spawn impact effect// await api.spawn("ImpactEffect", api.position);// Destroy bullet// Entity will be removed by engine}};};