Croquet Microverse

repository·main·Indexed 41 days ago

https://github.com/croquet/microverse

A framework for building multiplayer, immersive 3D virtual worlds on the web, built on Croquet OS, the Worldcore framework, and Three.js. It features a uniform object model of Cards and Behaviors, supporting multiuser live programming and zero-installation deployment. Version 0.8.4 includes tools for asset management, avatar control, and world template configuration.

Tokens
37.1K
Snippets
105
Records
150
Agent score
52%

What's inside Croquet Microverse

  1. Overview of Croquet Microverse architecture

    main

    Croquet Microverse is a framework for building multiplayer immersive 3D virtual worlds on the web. It is composed of several layers:

    • Croquet OS: Provides the substrate for multiuser applications.
    • Worldcore Framework: Provides an abstraction for building 3D applications on top of Croquet OS.
    • Cards and Behaviors: A uniform object model providing pluggable behavior descriptions for objects. This model supports multiuser live programming via text editor integration.
    • Three.js: The 3D rendering backend.
    • Web Technologies: Full access to browser-based media and real-world data.

    Key benefit: Deployed web apps require zero-installation for end-users.

  2. What is a Croquet Microverse Card?

    main

    A card is a declarative specification defined by a JSON-serializable set of properties. These properties allow you to define where a card is spatially situated, how it interacts with user pointer input, how it looks, what user-defined behaviors it possesses, and any data specific to those behaviors.

    Because cards are declarative, they can be transmitted, stored, or used to rebuild existing cards without losing identity. This enables interactive world-building and the ability to quickly explore different world alternatives.

    // Example of a card specification for a drivable 3D car
    {
        translation: [10, -1.672, -11],
        rotation: [0, 0, 0, 1],
        layers: ["pointer"],
        type: "3d",
        behaviorModules: ["Drive"],
        name: "porsche",
        dataLocation: "...",
        dataRotation: [-0.7071067811865475, 0, 0, 0.7071067811865476]
    }
  3. Understand the Croquet Microverse technology stack

    main

    Croquet Microverse is a composite system built using several specialized frameworks:

    • Croquet OS: An extremely low-latency multi-user replicated computation platform (the kernel).
    • Croquet Worldcore: A framework that defines the actor/pawn architecture used in Croquet Metaverse cards.
    • Three.js: The underlying 3D rendering framework.
    • Rapier Physics Engine: A high-performance, WebAssembly-based physics engine used for multiuser, interactive simulations.
  4. Data Persistence in Microverse

    main

    The system automatically saves "persistent data" approximately every 60 seconds, provided there is activity in the world.

    • What is saved: Essential data required to recreate the cards.
    • What is NOT saved: Transient view values or avatar states.
    • Usage: Persistence allows a session to be migrated to a new version of the Microverse or Croquet OS core library. When the Croquet OS detects a library version change for an existing sessionId and appId, it starts a fresh session but reloads the contents from the persistent data.

    Warning: The world template file (e.g., worlds/default.js) is only used once to initialize a session. Changes to the file will not affect an existing session identified by the ?q= URL parameter.

  5. Implement the Pawn-side of the Elected mechanism

    main

    The Pawn-side implementation handles the actual election state changes via events.

    Workflow

    1. Request Status: In the setup() method, the client pawn must send the electionStatusRequested event. This is a view-to-view event and is not replicated.
    2. Receive Election Event: The ElectedPawn responds with either a handleElected or handleUnelected event.

    Handling handleElected

    The handleElected event behaves differently depending on the context:

    • Initial Request: If the event is a direct response to electionStatusRequested, it contains no arguments (undefined).
    • Leadership Change: If a previous leader drops out and a new leader is elected, the event is sent with an object containing from and to fields.

    Action Trigger: A peer should start its elected action if the event argument is undefined OR if the to field matches the peer's viewId.

  6. Understand Card Property Categories

    main

    Card properties are grouped into four functional categories:

    1. Spatial: Defines the card's position, scale, rotation, and hierarchy.
    2. Pointer: Defines how the card interacts with user input (e.g., walking or pointing).
    3. Visual: Defines how the card looks.
    4. Code/Card Data: Defines user-defined behaviors and specific data required by those behaviors.
  7. Understand the Avatar model in Croquet Microverse

    main

    In Croquet Microverse, an avatar handles user interaction (mouse/keyboard) and manages the 3D scene camera. It is implemented as a special type of Card, meaning it inherits all properties and methods from Card classes.

    There are two primary components to an avatar:

    1. AvatarActor: The base actor class representing the logical state of the avatar in the shared world.
    2. AvatarPawn: The base pawn class representing the local/visual representation.

    Key distinctions:

    • Local Avatar: Uses AvatarActor and AvatarPawn to manage your own presence and camera.
    • Remote Avatars: Other participants in the session are represented by a different kind of pawn called RemoteAvatarPawn.
    • Visuals: Since avatars are Card objects, their visual appearance is defined using the same mechanism as 3D model-type cards.
  8. Implement the Actor-side of the Elected mechanism

    main

    The Actor-side implementation manages the list of active views by listening to view-join and view-exit messages.

    Key details:

    • The ElectedActor maintains a views property (a Set) containing all active views.
    • The leader is determined by picking the first entry in the views Set.
    • The Actor-side does not require specific code for the election logic itself; it simply receives messages from the elected peer, updates its local variables, and requests view updates as necessary.
  9. Access the AssetManager service

    main

    The AssetManager is a service used to load asset files (images, vector graphics, and 3D models) and manage caching. Because it deals with external resources, it is only available from the view (pawn) side code.

    In Microverse, you typically access the core features of the AssetManager via the following expression:

    this.service("AssetManager").assetManager

  10. Implement a Pawn Behavior

    main

    A Pawn behavior is used to construct the visual representation of an object.

    • setup(): Called when the behavior is created or changed. Use this to initialize the object. If setup() is called multiple times (e.g., during development hot-reloading), ensure you clean up previous geometry to avoid duplication.
    • Accessing Three.js: The THREE library is available via the global Microverse object (e.g., Microverse.THREE).
    • this.shape: Use this.shape to add Three.js meshes to the object's visual hierarchy.

    Export the behavior as a module containing pawnBehaviors.

    class JoeTheBoxPawn {
        setup() {
            const THREE = Microverse.THREE;
            // ... construct geometry/material ...
            let box = new THREE.Mesh(
                new THREE.BoxGeometry( 1, 1, 1, 1, 1, 1 ),
                new THREE.MeshStandardMaterial({ color: 0xcccccc }));
            
            box.receiveShadow = true;
            box.castShadow = true;
            this.shape.add(box);
        }
    }
    
    export default {
        modules: [
            {
                name: "JoeTheBox",
                pawnBehaviors: [JoeTheBoxPawn],
            }
        ]
    }
  11. How Croquet synchronization works

    main

    Croquet Microverse maintains perfect synchronization across all clients using a reflector (a central server).

    1. Events: User-initiated events (like pointerDown) and regular tick updates are sent to the reflector.
    2. Timestamps: The reflector distributes these events with timestamps.
    3. Deterministic Simulation: Every client uses these timestamps to compute pending simulations. Because the code in an Actor behavior runs identically on every machine, the simulation results remain perfectly in sync.
    4. Tick Rate: The default tick update rate is 30 Hz. This is independent of the client's local render rate (FPS).
  12. How WalkManager handles avatar movement

    main

    The WalkManager provides a pluggable mechanism to customize how an avatar moves (walks or flies) in a 3D scene. It operates as a pipeline of behavior methods (called "walkers" or "kernels") that are invoked in series.

    The Pipeline Model

    1. A proposed new pose (represented as vq, containing a vector3 for position and a quaternion for rotation) is passed into the pipeline.
    2. Each kernel in the pipeline receives the avatar, the current vq, the wall clock time, and the delta (time since last update).
    3. Each kernel proposes a new vq.
    4. If a kernel returns a flag isFinal: true, the pipeline terminates immediately, and that kernel's vq is used as the final pose.
    5. If isFinal is false, the next kernel in the pipeline receives the updated vq from the previous kernel.
    6. The final vq returned by the WalkManager is used by the avatar to update its actual position and rotation.
    // Conceptual representation of the walk pipeline logic
    walk(avatar, vq, time, delta) {
        for (let i = 0; i < this.walkers.length; i++) {
            let walker = this.walkers[i];
            let behavior = avatar.actor.behaviorManager.lookup(walker[0], walker[1]);
            let [newVq, isFinal] = behavior.invoke(avatar, walker[2], vq, time, delta);
            if (isFinal) {
                return newVq;
            }
            vq = newVq;
        }
        return vq;
    }