数学类 math
向量、包围盒、四元数、颜色与 UI 缩放工具,构造体素空间里的坐标、方向与色彩计算。
new Vec3(x = 0, y = 0, z = 0)构造一个三维向量,三个轴分别对应世界坐标的宽(X)/高(Y)/深(Z)。
v.add(v2) / v.sub(v2)向量加减,返回一个新的 Vec3,不修改自身。
const c = a.add(b);v.mul(v2) / v.div(v2)逐分量相乘 / 相除,返回新向量。
v.scale(n)标量缩放,等价于每个分量乘以 n。
const d = dir.normalize().scale(3);v.dot(v2)点积,常用于判断方向夹角或投影。
v.cross(v2)叉积,得到一个垂直于两个向量的新向量。
v.mag()向量长度(模)。
if (v.mag() > 10) { /* 超出范围 */ }v.normalize()返回长度为 1 的同方向单位向量,常用于移动方向。
v.distance(v2)两向量之间的欧几里得距离。
v.lerp(v2, n)线性插值,n 为 0~1 的混合比例。
v.clone() / v.copy(v2) / v.set(x, y, z)复制、赋值与就地设置分量。
Vec3.fromPolar(mag, phi, theta)由球坐标(模长、俯仰、方位)生成一个向量,适合做环形排布。
new Vec2(x = 0, y = 0)二维向量,常见于屏幕 / 平面坐标。
v.add / v.sub / v.mul / v.div / v.scale / v.dot / v.mag / v.normalize / v.distance / v.lerp与 Vec3 同名方法一致,仅操作 x、y 两个分量。
new Bounds3(min = new Vec3(), max = new Vec3())用最小角与最大角定义一个包围盒。
b.center()返回盒体中心点 Vec3。
b.contains(p)判断点 p 是否落在盒内(含边界)。
if (zone.bounds.contains(entity.position)) { ... }b.intersects(o)判断与另一个 Bounds3 是否相交。
b.expand(p)把点 p 纳入盒体范围并就地扩展。
new Quaternion(x = 0, y = 0, z = 0, w = 1)构造一个旋转四元数。
q.multiply(q2)四元数乘法,叠加两次旋转,返回新四元数。
q.multiplyVec3(v)把向量 v 按该四元数旋转后返回新的 Vec3。
q.normalize() / q.inverse() / q.angle()归一化、求逆旋转、取旋转角(弧度)。
new RGB(r = 0, g = 0, b = 0)构造一个 RGB 颜色(分量 0~255)。
c.toHex()导出为 '#rrggbb' 十六进制字符串,便于写入 setColor。
voxels.setColor(x, y, z, new RGB(255,80,80).toHex());new RGBA(r, g, b, a = 1)RGB 的子类,额外带 alpha 透明度(0~1)。
new Coord2(x = 0, y = 0) → .toVec2()客户端二维屏幕坐标,可转为 Vec2。
new UiScale(value = 1) → .apply(n)UI 缩放辅助,把逻辑像素乘以缩放系数。
voxels 体素读写
逐方块读取与写入体素世界。坐标均为整数(会自动取整)。类型既可用数字 id,也可用方块名字符串 (如 'grass'、'stone'),通过 voxels.id() / voxels.name() 互转。
voxels.setVoxelId(x, y, z, id, rot = 0)在坐标处放置指定 id 的方块,rot 为旋转(当前实现忽略)。
voxels.setVoxelId(0, 20, 0, 41); // 金块voxels.getVoxelId(x, y, z)读取该坐标的方块 id(空气为 0)。
if (voxels.getVoxelId(x, y, z) === 0) { /* 是空气 */ }voxels.setVoxel(x, y, z, type, rot = 0)用方块名或 id 放置方块(字符串名会自动查表)。
voxels.setVoxel(2, 20, 2, 'grass');voxels.getVoxel(x, y, z)同 getVoxelId,读取方块 id。
voxels.setBlock(x, y, z, type) / voxels.getBlock(x, y, z)神岛别名,等价于 setVoxel / getVoxel。
voxels.isEmpty(x, y, z)判断该坐标是否为空气(id === 0)。
voxels.setColor(x, y, z, color) / voxels.getColor(x, y, z)逐方块染色(内存表语义),color 为数字或 '#rrggbb';读取不到返回 null。
voxels.setColor(0, 20, 0, '#e8b54d');voxels.fill(x1,y1,z1, x2,y2,z2, type)实心填充一个包围盒,自动归一化坐标顺序,返回被写入的方块数量。
voxels.fill(-4,0,-4, 4,0,4, 'grass'); // 9×9 地面voxels.box(x1,y1,z1, x2,y2,z2, type)只画包围盒的外壳(空心立方体),返回写入数量。
voxels.raycast(origin, direction, maxDistance = 64)从 origin 沿 direction 发射体素射线,返回首个非空气命中 { voxelId, position, normal, distance, point },未命中返回 null。
const hit = voxels.raycast(camPos, camDir, 32);voxels.search(x1,y1,z1, x2,y2,z2, predicate)在包围盒内找出第一个满足条件的方块;predicate 可为函数(id,x,y,z)或数字 id,缺省找首个非空气,返回 { x, y, z, voxelId }。
voxels.id(name) / voxels.name(id)方块名与数字 id 互转。
const stone = voxels.id('stone');voxels.VoxelTypes(只读)当前世界可用的全部方块类型名数组。
world 世界
世界级操作与事件:方块修改、实体生成、天气 / 光照状态、区域与每帧事件回调。 注意:天气与光照通过属性直接赋值控制(如 rainDensity、lightMode),本实现未提供 world.time 这类方法。
world.setBlock(x, y, z, id) / world.getBlock(x, y, z)放置 / 读取方块(与 voxels 同义)。
world.removeBlock(x, y, z)移除方块,等价于放置空气。
world.removeBlock(0, 20, 0);world.fill(x1,y1,z1, x2,y2,z2, id)实心填充(世界级别名)。
world.count()返回当前世界方块总数。
world.say(message)向世界发送一条系统聊天消息(会触发 onChat)。
world.say('欢迎来到我的地图!');world.gravity(可读写)重力加速度,默认 26。
world.lightMode(可读写)光照模式,如 'day' / 'night'。
world.lightMode = 'night';world.rainDensity / world.snowDensity / world.rainColor / world.snowColor(可读写)控制雨雪天气强度与颜色。
world.rainDensity = 1; world.snowDensity = 0;world.sunDirection / world.fogColor / world.fogStartDistance(可读写)太阳方向、雾颜色与雾起始距离等渲染态。
world.createEntity(opts = {})创建一个带 3D 网格的客户端实体,返回可移动 / 销毁的对象。opts 支持 name, x, y, z, hp, maxHp, color, size。
const e = world.createEntity({ name:'宝箱', x:0, y:20, z:0, color:'#5b8cff' });world.createNPC(opts = {})等价于 createEntity,默认 name 为 'NPC'。
world.removeEntity(e)销毁一个实体(会触发其 destroy 事件)。
world.spawnParticles(pos, cfg = {})在 pos 处生成一团短命粒子(cfg 支持 count/color/size/duration),返回 Points 或 null。
world.spawnParticles(new Vec3(0,21,0), { color:'#ffd166', count:20 });world.addZone(cfg) / world.removeZone(z) / world.zones添加 / 移除一个区域(含 bounds、onEnter、onLeave),zones 为当前区域列表。
world.onVoxelContact(cb) / world.onEntityContact(cb) / world.onEntitySeparate(cb)方块 / 实体接触与分离事件,cb 收到接触信息。
world.onTick(cb) / world.onPlayerJoin(cb) / world.onPlayerLeave(cb)每帧 tick、玩家加入 / 离开事件。
world.onChat(cb) / world.onInteract(cb) / world.onClick(cb) / world.onPress(cb) / world.onRelease(cb)聊天、交互、点击、按下、松开事件。
world.onTakeDamage(cb) / world.onDie(cb) / world.onRespawn(cb)受伤、死亡、重生事件。
world.onFluidEnter(cb) / world.onFluidLeave(cb) / world.onEnter(cb) / world.onLeave(cb)进入 / 离开流体与区域事件。
entity / player 实体
entity 是玩家自身的实体封装,entity.player 是更细的玩家 API(相机、出生点、移动参数等)。 两者都继承了事件基类 Emitter,可用 on / once / off 监听事件。
entity.position / entity.velocity / entity.rotation(可读写)玩家位置 / 速度 / 朝向,均为 Vec3。
entity.position = new Vec3(0, 30, 0);entity.getPosition() / setPosition(x, y, z)获取 / 设置位置(兼容旧式写法)。
entity.getVelocity() / setVelocity(x, y, z)获取 / 设置速度。
entity.addVelocity(x, y, z)在现有速度上叠加一份加速度,常用于跳跃 / 击退。
if (input.isKeyPressed('Space')) entity.addVelocity(0, 0.6, 0);entity.teleport(x, y, z)瞬移到坐标并清零速度,参数也可直接传 Vec3。
entity.lookAt(v)使玩家朝向向量 v 所在的点。
entity.hp / entity.maxHp / entity.hurt(n) / entity.heal(n) / entity.die()生命值及受伤 / 治疗 / 死亡。
entity.hurt(5); // 扣 5 点血entity.distanceTo(v)玩家到向量 v 的直线距离。
entity.addTag(t) / hasTag(t) / removeTag(t) / entity.tags实体的标签集合,用于分类与查询。
entity.say(text) / entity.destroy() / entity.onGround说话、销毁实体、是否站在地面(只读)。
entity.on(name, cb) / once / off监听实体事件(如 'takeDamage'、'die'、'destroy')。
entity.on('die', () => world.say('玩家阵亡'));entity.player.spawnPoint(可读写)玩家重生点 Vec3。
entity.player.spawnPoint = new Vec3(0, 30, 0);entity.player.dead / forceRespawn()是否死亡(只读) / 强制在原出生点复活并回满血。
entity.player.setCameraPitch(v) / setCameraYaw(v)设置相机俯仰 / 偏航角。
entity.player.canFly / walkSpeed / flySpeed / scale / color / name / showName(可读写)飞行开关、行走 / 飞行速度、缩放、颜色、昵称等玩家属性。
ui / UiNode 界面
在屏幕上叠加 DOM 浮层 UI。ui 是根节点,用 create* 工厂创建子节点。每个节点都是 UiNode, 继承 Emitter,可用 on 监听 focus / blur / input 等事件。
ui.createBox(opts) / createImage(opts) / createText(opts) / createInput(opts) / createScrollBox(opts) / createScreen(opts)创建不同类型的 UI 节点并返回它,可继续用 .create* 嵌套子节点。
const panel = ui.createBox({ size:{x:220,y:64}, backgroundColor:'#1a1f2b', position:{x:24,y:24} });node.create(type, opts) / node.addChild(child)在任意节点下再创建 / 添加子节点。
node.textContent / node.textColor / node.textFontSize文本节点的文字内容、颜色与字号。
panel.createText({ textContent:'点击开始', textColor:'#e8b54d', textFontSize:16 });node.backgroundColor / node.backgroundOpacity / node.size / node.position / node.anchor / node.rotation背景、透明度、尺寸(像素)、位置、锚点与旋转。
node.image / node.imageOpacity / node.imageDisplayMode图片节点的资源地址、透明度与填充方式(如 'stretch')。
node.placeholder / node.value (input)输入框占位文字(用 textContent 充当)与当前输入值(on 'input' 时更新)。
const inp = ui.createInput({ textContent:'输入昵称' }); inp.on('input', () => world.say(inp.value));node.findChildByName(n) / node.visible / node.zIndex按名字查找子节点、可见性与层级。
node.focus() / node.blur()让输入框获得 / 失去焦点。
input 输入
键盘与鼠标输入。键名同时接受 KeyboardEvent.code(如 'KeyW')与 .key(如 'w')两种写法。
input.isKeyDown(key)当前是否按住该键(持续为真)。
if (input.isKeyDown('KeyW')) entity.addVelocity(0, 0, 0.1);input.isKeyPressed(key)本帧是否刚按下(每帧末清空,适合一次性触发)。
if (input.isKeyPressed('Space')) entity.addVelocity(0, 0.6, 0);input.isKeyReleased(key)本帧是否刚松开(每帧末清空)。
input.keys(只读)当前按住的所有键集合。
input.mouse(只读)鼠标状态 { x, y, buttons, left, right, middle }。
if (input.mouse.left) { /* 左键按下 */ }input.focus(node) / input.blur(node)聚焦 / 失焦某个 UI 输入节点。
input.lockPointer() / input.unlockPointer()隐藏 / 恢复鼠标指针(沉浸视角用)。
http 网络请求
封装 fetch 的便捷方法,常用于拉取排行榜、存档进度等。返回对象带 .json() / .text() / .status / .ok。
http.get(url, opts) / http.post(url, body, opts)发起 GET / POST 请求,opts 可带 headers、query、body。
const r = await http.get('https://api.example.com/score'); const data = await r.json();http.request(method, url, opts)通用请求入口,method 为 'GET'/'POST'/... 等。
http.put(url, body, opts) / http.delete(url, opts)PUT / DELETE 请求。
http.fetch(url, init)直接透传浏览器 fetch,返回原始 Response 风格的包装对象。
remoteChannel 联机事件
跨端通讯通道:联机时经 WebSocket 真实跨端,单机时本地回环互通,用于服务端 / 客户端互发事件。
remoteChannel.sendServerEvent(name, data)客户端 → 服务端 发送事件(联机时上行,未联机时本地回环)。
remoteChannel.sendServerEvent('pickup', { id: 7 });remoteChannel.onServerEvent(name, cb)服务端侧监听来自客户端的事件。
remoteChannel.sendClientEvent(name, data)服务端 → 客户端 发送事件(本地回环)。
remoteChannel.onClientEvent(name, cb)客户端侧监听来自服务端的事件。
remoteChannel.onClientEvent('openDoor', () => world.say('门开了'));media / Audio 媒体
音效、背景音乐播放与麦克风录音。Audio 是事件基类 Emitter 的子类,可监听 'ended' / 'error' 等。
media.playSound(src, opts = {})播放一次性音效,opts.gain 控制音量(0~1)。
media.playSound('https://cdn/click.mp3', { gain: 0.6 });media.playMusic(src, opts = {})播放背景音乐(单例循环,重复调用会先停旧的),opts.loop 默认 true。
media.playMusic('https://cdn/bgm.mp3', { loop:true, gain:0.5 });media.stopMusic() / media.setVolume(v)停止背景音乐 / 调节背景音乐音量。
media.startRecording() / media.stopRecording()请求麦克风并开始 / 停止录音,stop 返回音频 Blob。
media.playAudio(spec) / media.stopPlayAudio()播放录音 Blob(spec.blob)或最近一次录音。
new Audio(src)构造一个客户端音频对象,自动加载 src。
const s = new Audio('https://cdn/ding.mp3'); s.volume = 0.5; s.play();audio.play() / audio.pause() / audio.load() / audio.volume播放 / 暂停 / 重新加载,以及音量属性。
s.on('ended', () => world.say('播放完毕'));storage 存储
基于 localStorage 的键值存储封装,自动 JSON 序列化,单地图隔离。方法均为异步(返回 Promise)。
storage.set(k, v) / storage.get(k)写入 / 读取一个键值(任何可 JSON 化的数据)。
await storage.set('score', 10); const s = await storage.get('score');storage.update(k, v)更新键值:v 可为对象(浅合并)或函数 (cur) => next。
storage.increment(k, n = 1)数值自增,返回新值,非数字按 0 计。
const n = await storage.increment('kills');storage.remove(k) / storage.list(opts = {})删除键 / 列出全部键值(opts 支持 offset、limit)。
storage.getDataStorage(name) / storage.getGroupStorage(name)获取指定名称的私有 / 团队存储空间(同样有 set/get/update 等)。
screen 屏幕
屏幕尺寸与轻量提示,用于自适应布局与即时反馈。
screen.width / screen.height / screen.getSize()(只读)视口宽高与 { width, height }。
const cx = screen.width / 2;screen.showToast(message, opts = {})在屏幕顶部弹出一条短暂提示,opts 支持 top、duration(毫秒)。
screen.showToast('保存成功', { duration: 1500 });screen.setCursorVisible(v)显示 / 隐藏鼠标指针。
onStart / onUpdate 脚本入口
创作者在自己的脚本里定义下列顶层函数,引擎会在相应时机调用它们;上述所有命名空间 (Vec3、voxels、world、entity、ui、input、http、storage、media、navigator、screen、remoteChannel、Audio…) 都已由引擎通过 new Function(...names, code) 注入,无需 import 即可直接使用。
function onStart() { ... }脚本加载 / 地图开始时被调用一次,适合做初始化(铺设地形、注册事件)。
function onStart() { voxels.fill(-4,0,-4, 4,0,4, 'grass'); }function onUpdate(dt) { ... }每帧调用,dt 为距上一帧的秒数,适合做动画与实时逻辑。
function onUpdate(dt) { const t = Date.now()/1000; world.lightMode = (Math.sin(t) > 0) ? 'day' : 'night'; }entity.on(...) / world.on*(...)除回调外,也可在 onStart 里用事件 API 监听交互、伤害、玩家加入等。
world.onInteract(() => screen.showToast('你点击了世界'));Emitter: on(name, cb) / once / off / emit所有可监听对象(entity、ui、media、remoteChannel、world)都基于该事件基类。
快速上手 · 最小可运行脚本
// 在地图编辑器「脚本」标签里粘贴即可运行
function onStart() {
world.say('脚本已启动 ✦');
// 用方块名铺设一块 9×9 的草地
voxels.fill(-4, 0, -4, 4, 0, 4, 'grass');
}
// 玩家脚下持续生成石头路(方块 id 35)
function onUpdate(dt) {
const p = entity.position;
voxels.setVoxelId(
Math.round(p.x),
Math.round(p.y - 1),
Math.round(p.z),
35
);
}
// 进阶:用 onUpdate 让一圈金块绕中心旋转
function onSpin(dt) {
const t = (Date.now() / 1000) % (Math.PI * 2);
for (let i = 0; i < 12; i++) {
const a = t + i * (Math.PI * 2 / 12);
voxels.setVoxelId(
Math.round(8 + Math.cos(a) * 5),
16,
Math.round(8 + Math.sin(a) * 5),
41 // 41 = 金块
);
}
}提示:setVoxelId 的最后一个参数是方块数字 id(如 35=石头、41=金块、0=空气),也可用 voxels.id('grass') 取 id。 示例中的 id 仅为示意,请以你地图编辑器的方块面板为准。