在 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 直接导入。例如,下面的脚本让实体旋转:
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)。