elics, WebXR controller input patterns, and Draco/KTX2 asset compression workflows.elicsflap-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.| File | What it demonstrates | Key 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 |
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.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.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.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.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.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.