Those working in 3D GIS are likely no strangers to CesiumJS. In projects such as digital twins, smart cities, and reality-based 3D, it is almost the standard on the browser side. However, Cesium is essentially a 3D globe engine, not a game engine. Once a project wants to achieve a “first-person/third-person walking through the scene” experience, it becomes quite troublesome. Game engines like Unreal and Unity come with a full set of character controllers, but on the browser side with Cesium, this capability is largely absent, requiring you to piece together physics, collision, camera, and animation from scratch.

Recently, we received a submission about an open-source library called cesium-player-controller, which is ready to use out of the box. If you have such needs, take a look.

Project Overview

cesium-player-controller is a player controller built on CesiumJS, open-sourced under the MIT license and published on npm. Its underlying physics is based on Rapier3d. In short, it brings the mature “character controller” concept from game engines into Cesium’s 3D Tiles scenes, allowing you to control a character in a globe-scale 3D scene just like in a game.

GitHub: https://github.com/hh-hang/cesium-player-controller

Online demo: https://hh-hang.github.io/cesium-player-controller/index.html

Key Features

To achieve a first-person feel in Cesium, the difficulties lie in piecing together collision, camera, and animation piece by piece. What’s interesting about this project is that the author has ported the relatively mature gameplay patterns from game engines to the web. It works with terrain, glTF, and even dynamic objects; you can switch between first-person and third-person freely, and the camera automatically avoids walls, eliminating the need to write a bunch of obstacle avoidance logic. Actions like walking, running, jumping, and flying are supported — in large scenes, press F to take off and get a bird’s-eye view. On mobile, it supports virtual joysticks, key bindings can be customized, and callbacks for position and landing are provided via APIs. We think it may not excel in every single aspect, but it packages the common capabilities needed along this path into a directly usable bundle. For developing park tours, oblique photography inspections, and similar applications, it saves significant time and effort.

Installation and Getting Started

Installation is simple, just install the library together with Rapier:

npm install cesium-player-controller @dimforge/rapier3d-compat

Below is a complete example:

import {
    Cartesian3,
    Cesium3DTileset,
    Viewer,
    Math as CMath,
    Cartographic,
} from "cesium";
import "cesium/Build/Cesium/Widgets/widgets.css";
import { playerController } from "cesium-player-controller";

// Set up CesiumJS environment
const viewer = new Viewer("cesiumContainer", {
    timeline: false,
    animation: false,
});

// Load 3D Tiles scene
const tileset = await Cesium3DTileset.fromUrl("./tileset.json");
viewer.scene.primitives.add(tileset);
await viewer.flyTo(tileset);

// Set the character spawn point
const center = tileset.boundingSphere.center;
const carto = Cartographic.fromCartesian(center);
const initPos = Cartesian3.fromDegrees(
    CMath.toDegrees(carto.longitude),
    CMath.toDegrees(carto.latitude),
    carto.height + 20,
);

// Core usage of playerController
const player = new playerController();

// Initialize character control
await player.init({
    viewer,   // Cesium Viewer instance
    initPos,  // Character initial position, ECEF
    playerModelConfig: {
        url: "./glb/person.glb",   // Model path (GLB/GLTF)
        scale: 0.01,               // Model scale
        idleAnim: "idle",          // Idle animation name
        walkAnim: "walk",          // Walk animation name
        runAnim: "run",            // Run animation name
        jumpAnim: "jump",          // Jump animation name; or pass ["takeoff", "loop", "land"] to play in three segments
    },
    // Static collision source
    staticCollider: {
        type: "gltf",
        url: "./glb/agi-hq.glb",
        position: center,
    },
});

// Called every frame
let last = performance.now();
viewer.scene.preUpdate.addEventListener(() => {
    const now = performance.now();
    const delta = (now - last) / 1000;
    last = now;
    player.update(delta);
});

More detailed parameters (physics, camera, key bindings, mobile buttons, etc.) have default values and can be adjusted field by field as needed. Refer to the project documentation for adjustments.

Companion Tool

One detail worth mentioning: since CesiumJS cannot directly access the vertex coordinates of 3D Tiles via the CPU, creating collision bodies for 3D Tiles scenes such as oblique photography and reality-based 3D is not convenient. To address this, the author has also provided a companion tool called collider-forge, specifically designed for creating collision bodies, which complements the controller and completes the workflow.

Conclusion

Implementing a complete character controller in CesiumJS has always been a laborious task — physics, collision, camera obstacle avoidance, animation state machine — none of these are easy to build individually, and assembling them together is a considerable engineering effort. cesium-player-controller packages these capabilities into an out-of-the-box library, covering common requirements in a one-stop manner. For teams working on digital twins, smart city roaming, 3D scene interaction, or reality-based 3D presentations, it can save a significant amount of time otherwise spent reinventing the wheel. The project is currently in an early 0.1.x stage with active iterations. If you need this, check it out.