Develop

Flap Frenzy sample overview

Updated: May 10, 2026

Overview

This sample demonstrates how to build a VR game with natural arm-based input, entity-component-system architecture, and optimized 3D assets for WebXR. You learn physics-based flapping mechanics, ECS game structure with elics, WebXR controller input patterns, and Draco/KTX2 asset compression workflows.

What you will learn

  • Detect and respond to arm-flapping gestures using WebXR controller position tracking
  • Structure a WebXR game using the entity-component-system (ECS) pattern with elics
  • Use controller position to modulate game physics, such as wing angle modulating gravity
  • Optimize 3D assets using Draco mesh compression and KTX2 texture compression
  • Implement a game state machine that transitions between lobby and gameplay states

Requirements

  • Meta Quest headset (Quest 2, Quest 3, or Quest 3S)
  • Development environment: Node.js and npm
For detailed build prerequisites, see the sample README.

Get started

Clone or download the webxr-showcases repository. Navigate to the flap-frenzy directory and install dependencies with npm install. Run the development server with npm run serve, then open the local URL on your Quest browser. For detailed build instructions and deployment options, see the sample’s README.

Explore the sample

FileWhat it demonstratesKey concepts
ECS initialization and main loop
Creating a World, registering components and systems, animation loop with delta time
Centralized configuration
Shared state via GlobalComponent, game constants (gravity, flap multiplier, ring scaling)
WebXR input setup
Controller connection events, gamepad wrapping, head tracking via XRViewerPose matrix decomposition
Arm-flapping physics
Y-position delta detection, wing angle calculation, gravity modulation based on arm extension
Game state machine
Lobby/ingame states, flap-counting entry mechanic, ring spawning, collision detection, score persistence
Three.js WebXR setup
Renderer configuration, compressed asset loading (Draco, KTX2), environment mapping
VR session management
VRButton integration, feature requests (local-floor, layers), non-VR browser fallback
3D asset optimization
gltf-transform pipeline with Draco mesh and KTX2 texture compression

Runtime behavior

When you run this sample, you see a landing page with an “Enter VR” button. After entering VR, you appear in a 3D environment with a scoreboard floating in front of you. The game starts in lobby state, prompting you to flap your arms three times. Once you flap three times, rings begin appearing ahead of you in a circular flight path. You fly by flapping your arms to ascend, extending your arms horizontally to glide, or tucking your arms to dive. Each ring displays a number, and passing through it increments your score and shrinks the ring by 2%, increasing difficulty. Missing a ring ends the game, which saves your high score to local storage and returns you to the lobby.

Key concepts

ECS architecture with elics

The sample uses elics to separate data from logic. Components like PlayerComponent and GlobalComponent extend the Component base class and contain only state (no methods). Systems like FlapSystem and GameSystem contain all update logic and use static queries objects to filter entities by component type. The World manages entity creation and dispatches update(delta, time) to each system. See src/index.js for how systems are registered and the update loop is structured.

WebXR controller input

The sample accesses controllers through renderer.xr.getController(i) and getControllerGrip(i), listening for connected events to detect handedness and wrap gamepads with GamepadWrapper. Head tracking uses frame.getViewerPose(referenceSpace) with matrix decomposition to extract position. This pattern provides both controller and head position data for physics calculations. See src/player.js for the complete controller setup.

Flap physics with gravity modulation

The flap detection compares each controller’s Y-position between frames. Downward motion beyond a threshold registers as a flap and adds vertical speed. The wing angle (calculated from the controller-to-wing vector using atan(|dy|/|dx|)) modulates gravity: flat arms (angle less than 0.2 radians) halve gravity for gliding, while steeper angles apply full gravity. This creates three distinct movement modes (flap, glide, dive) from a single angle calculation. See src/flap.js for the physics implementation.

3D asset optimization pipeline

The compress.mjs script shows a production-ready asset pipeline using gltf-transform. It applies deduplication, Draco mesh compression, and KTX2 (ETC1S) texture compression in sequence. At runtime, scene.js configures matching decompression loaders (DRACOLoader, KTX2Loader) on GLTFLoader. This combination reduces asset size for faster loading on Quest devices while maintaining visual quality. See content/compress.mjs and src/scene.js.

Game state machine

The sample uses a simple two-state machine (lobby, ingame) stored on GlobalComponent.gameState. In lobby, the system counts valid flaps per hand, and three flaps trigger game start. In game, the system spawns rings, checks collision by comparing player Y to ring Y within ring radius, and increments score on success. The flap-counting entry mechanic teaches the primary input before gameplay begins. See src/game.js.

VR session management with RATK

The sample uses VRButton.convertToVRButton() from the Reality Accelerator Toolkit (RATK) to convert an HTML button into a WebXR session launcher. It requests optional features (local-floor, bounded-floor, layers) for enhanced tracking. For non-VR browsers, an onUnsupported handler displays a deep link to open the experience in the Meta Quest browser. See src/landing.js.

Extend the sample

  • Add hand tracking support: Replace or augment controller input with the WebXR Hand Input API to enable natural hand-based flapping without controllers.
  • Implement multiplayer leaderboards: Replace localforage with a server endpoint to share scores across players and display a global leaderboard.
  • Vary ring patterns: Add vertical movement, random positions, or obstacle rings to create different difficulty modes and game progression.