跳到主要内容

在 Node.js 中运行引擎

Node.js 是跨平台的开源 JavaScript 运行时,可运行于 Windows、Linux、macOS 等系统。它基于 V8 JavaScript 引擎,在浏览器之外执行 JavaScript 代码,使开发者可以用 JavaScript 编写命令行工具和服务端脚本。

PlayCanvas Engine 完整支持在 Node.js 中运行,适用于:

  • 创建多人游戏服务器。
  • 创建处理资源数据的工具。
  • 为应用编写单元测试。
备注

PlayCanvas Engine 自身也使用 Node.js 运行单元测试。

安装​

开始前,请确认已安装 Node.js 18 或更高版本,然后通过 npm 安装 PlayCanvas Engine。

npm install playcanvas

创建无界面应用​

在 Node.js 中运行 PlayCanvas 应用时,通常不需要渲染。这时可以创建不执行任何渲染的 NullGraphicsDevice。由于没有内容需要显示,可以用普通对象代替 canvas。

import { AppBase, AppOptions, NullGraphicsDevice, ScriptComponentSystem } from 'playcanvas';

// Nothing is rendered, so a plain object stands in for the canvas
const canvas = { width: 1, height: 1 };

const options = new AppOptions();
options.graphicsDevice = new NullGraphicsDevice(canvas);
options.componentSystems = [ScriptComponentSystem];

const app = new AppBase(canvas);
app.init(options);
app.start();

AppBase 仅运行已注册的组件系统。此应用注册了脚本组件系统,供后面的添加脚本使用。添加其他类型的组件时,也需要注册其系统,具体参见配置应用。

更新应用​

在浏览器中,app.start() 会启动由 requestAnimationFrame 驱动的主循环。Node.js 没有 requestAnimationFrame,因此不会运行主循环。你需要按所需频率调用 app.update(dt)。它会更新已注册的组件系统,例如脚本、动画或物理,而完全跳过渲染。

const TICK_RATE = 20; // updates per second

setInterval(() => {
app.update(1 / TICK_RATE);
}, 1000 / TICK_RATE);

传入固定的 dt,可以使每次更新推进相同长度的模拟时间,不受定时器漂移影响。

添加脚本​

ESM 脚本是标准 JavaScript 模块,可以由 Node.js 直接导入。例如,下面的脚本让实体旋转:

rotate.mjs
import { Script } from 'playcanvas';

export class Rotate extends Script {
static scriptName = 'rotate';

update(dt) {
this.entity.rotate(0, 90 * dt, 0);
}
}

导入脚本类,并将其传给脚本组件:

import { Entity } from 'playcanvas';
import { Rotate } from './rotate.mjs';

const entity = new Entity('Box');
entity.addComponent('script');
entity.script.create(Rotate);
app.root.addChild(entity);

现在,每次调用 app.update(dt) 时,脚本的 update 方法都会执行。

使用 jsdom​

引擎的某些部分依赖 Node.js 不提供的 DOM API:

  • 通过 <script> 元素加载的经典脚本。
  • 通过资源注册表加载资源时,使用 XMLHttpRequest 请求文件。

如果需要上述任一功能,可以使用 jsdom 包模拟 DOM 环境。

npm install jsdom

下面的函数使用 jsdom 配置 DOM 环境,使 PlayCanvas Engine 能够正常运行。

import { JSDOM } from 'jsdom';
import * as pc from 'playcanvas';

let jsdom;

export function jsdomSetup() {
const html = '<!DOCTYPE html><html><head></head><body></body></html>';

jsdom = new JSDOM(html, {
resources: 'usable', // Allow the engine to load assets
runScripts: 'dangerously', // Allow the engine to run scripts
url: 'http://localhost:3000' // Set the URL of the document
});

// Copy the window and document to global scope
global.window = jsdom.window;
global.document = jsdom.window.document;

// Copy the DOM APIs used by the engine to global scope
global.ArrayBuffer = jsdom.window.ArrayBuffer;
global.Audio = jsdom.window.Audio;
global.DataView = jsdom.window.DataView;
global.Image = jsdom.window.Image;
global.KeyboardEvent = jsdom.window.KeyboardEvent;
global.MouseEvent = jsdom.window.MouseEvent;
global.XMLHttpRequest = jsdom.window.XMLHttpRequest;

// Copy the PlayCanvas API to global scope (only required for 'classic' scripts)
jsdom.window.pc = pc;
}

请在创建应用前调用 jsdomSetup()。加载资源时,还需在 AppOptions 中为每种资源类型注册对应的处理器,例如经典脚本需要 ScriptHandler。jsdom 同样不提供主循环,因此仍需按上文方式持续调用 app.update(dt)。