@driftengine/core

中文文档
4.14.3发布于 2026/11/04Apache-2.0TypeScriptES Module近 30 天下载 3316

The runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics

原始使用文档
文档目录5 节
  1. Install
  2. Quickstart
  3. What is in it
  4. The other packages
  5. Licence

@driftengine/core

The runtime of DriftEngine, a 3D game engine for the web and for installed applications. It draws through WebGPU where the browser has a device and through WebGL2 everywhere else, behind one API, and carries the fixed-step loop, the renderer, cameras, input, the scene graph, geometry, text and the whole of @driftengine/physics, which it re-exports. Everything else is a separate package a game adds when it reaches for it.

Cost: 966.4 KB gzipped, importing createRenderer. Measured by scripts/size-gate.test.mjs, which fails the build if the figure drifts by more than 3%.

The manual is at driftengine.dev/docs, with an example running beside every chapter, and every export is in the reference.

Install

npm install @driftengine/core

It resolves to compiled JavaScript, so any ES module bundler takes it as it is; Vite is what the examples use. A browser needs WebGL2 at least, which every current one has. Installation covers the page, TypeScript and the backends.

Quickstart

A lit cube turning on a fixed step, on a page with <canvas id="stage">:

import {
  Camera,
  MeshBuilder,
  SceneNode,
  createEnvironment,
  createRenderer,
  srgbColor,
  startLoop,
} from '@driftengine/core';
import { bindModule } from '@driftengine/script';
import { loadModule, patchModule } from 'driftscript';
import * as spinScript from './spin.drs';

const canvas = document.querySelector<HTMLCanvasElement>('#stage');
if (canvas === null) throw new Error('the page must carry <canvas id="stage">');

/*
 * Asynchronous because it has to be: asking for a WebGPU adapter returns a promise, and there
 * is no synchronous way to learn whether a usable device exists. The fall back to WebGL2 is
 * automatic and silent, so `reason` is the only thing that says why. Print it: the first
 * question asked about any picture is what drew it.
 */
const { renderer, backend, reason } = await createRenderer(canvas, {
  maxDevicePixelRatio: 1.75,
});

const readout = document.querySelector('#backend');
if (readout !== null) readout.textContent = `${backend}: ${reason}`;

renderer.resize();
addEventListener('resize', () => renderer.resize());

/**
 * Light, air and ground bounce. Chosen once, because these are allocation-sized decisions.
 *
 * Every colour here and below was picked by eye, so it goes through `srgbColor`: the renderer
 * lights in linear values and encodes them for the screen at the end, and a colour typed as it
 * looks would come out too light. The clear colour in the loop is one of them.
 */
const CLEAR = srgbColor(0.05, 0.06, 0.09);

const ENV = createEnvironment({
  directionalDir: [0.4, 0.7, 0.35],
  directionalColor: srgbColor(1, 0.96, 0.88),
  ambient: srgbColor(0.2, 0.22, 0.28),
  ambientGround: srgbColor(0.08, 0.08, 0.1),
  emissiveGain: 0,
  nightFactor: 0,
  fogColor: srgbColor(0.16, 0.18, 0.24),
  fogDensity: 0.004,
  fogHeightFalloff: 0.03,
  fogBaseY: 0,
});

/*
 * Colour is vertex data here, which is what lets a whole world be flat-shaded draw calls.
 * `addBox` takes a centre and *half* extents, so this ground is 24 units across and the cube
 * is two units on a side.
 */
const shapes = new MeshBuilder();
shapes.addBox([0, 1, 0], [1, 1, 1], srgbColor(0.85, 0.45, 0.25));
const cube = renderer.createMesh(shapes.build());

const slab = new MeshBuilder();
slab.addBox([0, -0.25, 0], [12, 0.25, 12], srgbColor(0.3, 0.32, 0.36));
const ground = renderer.createMesh(slab.build());

const spinner = new SceneNode();
spinner.setPosition(0, 1, 0);
const stillness = new SceneNode();
stillness.updateWorld();

const camera = new Camera();
camera.position[0] = 6;
camera.position[1] = 4.5;
camera.position[2] = 8;
camera.lookAt(0, 1, 0);

/*
 * How fast the cube turns is a rule, and rules live in DriftScript: `spin.drs`. The bundler compiles
 * the file, `loadModule` makes a module of it, and `bindModule` gives it the engine capabilities it
 * imports. Saving the file while the page is open replaces its function in place.
 */
const spinModule = loadModule(spinScript as Record<string, unknown>);
const bound = bindModule(spinModule, {});
if (!bound.bound) throw new Error(bound.reason);
const rules = spinModule.exports as unknown as { spinRate(time: number): number };

if (import.meta.hot) {
  import.meta.hot.accept('./spin.drs', (next) => {
    if (next !== undefined) patchModule(spinModule, next as Record<string, unknown>);
  });
}

/*
 * Two angles, because that is what `alpha` is for. The simulation advances in fixed steps and
 * the display does not, so a frame almost never lands on a step boundary: drawing `spin`
 * directly judders at any refresh rate that is not a multiple of the step. Interpolating
 * between the last two states is the whole reason the loop hands `alpha` over.
 */
let spin = 0;
let previousSpin = 0;
let time = 0;

startLoop({
  simulate(dt) {
    previousSpin = spin;
    time += dt;
    spin += dt * rules.spinRate(time);
  },
  render(alpha) {
    spinner.setRotationAxisAngle(0, 1, 0, previousSpin + (spin - previousSpin) * alpha);
    spinner.updateWorld();

    camera.updateMatrices(canvas.height > 0 ? canvas.width / canvas.height : 1);

    renderer.beginFrame(CLEAR);
    renderer.bindMeshPass(camera, ENV);
    renderer.drawMesh(ground, stillness.worldMatrix);
    renderer.drawMesh(cube, spinner.worldMatrix);
    renderer.endFrame();
  },
});

How fast the cube turns is a rule, so it lives in DriftScript, in spin.drs beside the page:

// How the cube turns. The page owns the cube; this file only says how fast it goes.
//
// Save it while the page is open and the cube takes the new rate without the page reloading. Try a
// steady 2, a cube that stops every few seconds, or one that turns the other way.

import { sin } from "std/math"

// Radians a second: a steady turn that quickens and eases every few seconds.
fn spinRate(time: f32) -> f32 {
    return 0.8 + 0.4 * math.sin(time * 0.5)
}

The bundler compiles it with DriftScript's Vite plugin, set up as in the starter's vite.config.ts; Installation has the two packages and the config.

This is the engine's examples/starter, and a test keeps the two the same. Hello world walks through it, and Your first game builds a complete one.

What is in it

The frameA fixed-step loop with interpolation, two backends behind one API, construction-time quality profiles, and a frame whose order you can read.
DrawingMeshes, materials, instancing, lights and shadows, the sky, fog and weather, water, reflections, post-processing, particles, text and passes of your own.
Light at scaleDriftLight for thousands of fixed lights, DriftRay for bounced light traced every frame, DriftTR for temporal reconstruction, and the GPU-driven pipeline.
Many thingsInstanced batches that cull, crowds animated from bone textures in the vertex stage (createBoneAnimation), and static draws recorded once and replayed every frame (createStaticDraws).
TexturesImages and compressed blocks through one call: BC for desktops, ETC2, EAC and ASTC for phones, each uploaded as it is where compressedFormats says the device samples it. updateSurfaceTexture can replace an image with blocks behind the handle a draw already holds.
WorldsWorlds bigger than a float, streamed in cells, with hierarchical detail and worlds built from a kit.
PhysicsDeterministic rigid bodies, joints, queries, characters, vehicles, and ragdolls and cloth.
PlayInput from every device through one action map, cameras that cut, picking and the scene graph.
Around the gameSaves and preferences through a store the game supplies, a host seam for windows, displays and files, and the visual-gate scripts — @driftengine/core/scripts/browser.mjs and its siblings — that photograph a page on the hardware GPU.

The other packages

Each is added when a game reaches for it, and a game that never imports one carries none of it.

PackageWhat it adds
@driftengine/animationSkeletons, clips, blend trees and IK. Guide
@driftengine/navNavigation meshes and routes. Guide
@driftengine/entitiesEntities, components, systems and prefabs, with rewind. Guide
@driftengine/chemistryHeat, burning, phase changes and smoke. Guide
@driftengine/aiBehaviour trees and agents. Guide
@driftengine/audioA mix of buses, placed sounds, rooms and rhythm. Guide
@driftengine/assets and @driftengine/drftModel importers, KTX2, and the streaming .drft container. Guide
@driftengine/terrainTerrain drawn as a clipmap. Guide
@driftengine/splatsGaussian splat captures among meshes. Guide
@driftengine/textureMaterials as programs decoded on the device. Guide
@driftengine/captureA video clip turned into a surface, a collider and entities. Guide
@driftengine/ui2dSprites, tilemaps and interfaces. Guide
@driftengine/scriptDriftScript, the engine's scripting language, with hot reload. Reference
@driftengine/networkLockstep and rollback netcode. Reference
@driftengine/xrWebXR sessions. Reference
@driftengine/mediaRecording and exporting clips. Reference
@driftengine/editor and @driftengine/toolsThe editor and in-game tools. Reference
@driftengine/package and @driftengine/native-hostInstallable apps built from the same game. Reference

Licence

Apache-2.0. Distributing it means reproducing its NOTICE, which every bundle of it already carries. Part of DriftEngine; see docs/ARCHITECTURE.md for how the packages divide.

版本与发布记录

@driftengine/core 共发布 52 个版本,首次发布于 2026/09/13;过去 12 个月发布了 52 个版本。

版本发布日期距今
4.14.3latest2026/11/049 天前
4.14.22026/11/0310 天前
4.14.12026/11/0310 天前
4.14.02026/10/1826 天前
4.13.62026/10/1529 天前
4.13.52026/10/121 个月前

关于 @driftengine/core 的常见问题

@driftengine/core 是什么?

The runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics

如何安装 @driftengine/core?

在项目目录运行 npm install @driftengine/core,或使用 pnpm add / yarn add / bun add。

@driftengine/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@driftengine/core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@driftengine/core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@driftengine/core 可以商用吗?

@driftengine/core 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@driftengine/core 的下载量有多少?

根据 npm 官方下载统计,@driftengine/core 在 2026/10/13 – 2026/11/11 这 30 天内被下载 3,316 次,日均约 111 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@driftengine/core 还在维护吗?

最新版本 4.14.3 发布于 2026/11/04(9 天前);至今共发布 52 个版本,其中过去 12 个月发布了 52 个;首次发布于 2026/09/13。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。