NXNEXISLE明日新岛
NEXISLE Voxel Script API

神岛脚本 API 参考

这是把神岛(dao3.fun)的脚本 API 迁移到 NEXISLE 的兼容层参考。创作者可以在地图编辑器的 「脚本」标签里直接用 JavaScript 编写体素世界逻辑——下方列出的命名空间(Vec3、voxels、world、 entity、ui、input、http 等)都会由引擎自动注入到你的脚本作用域中,无需 import。

新手指南/脚本 API

数学类 math

向量、包围盒、四元数、颜色与 UI 缩放工具,构造体素空间里的坐标、方向与色彩计算。

Vec3 — 三维向量
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)

由球坐标(模长、俯仰、方位)生成一个向量,适合做环形排布。

Vec2 — 二维向量
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 两个分量。

Bounds3 — 三维轴对齐包围盒
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 纳入盒体范围并就地扩展。

Quaternion — 四元数(旋转)
new Quaternion(x = 0, y = 0, z = 0, w = 1)

构造一个旋转四元数。

q.multiply(q2)

四元数乘法,叠加两次旋转,返回新四元数。

q.multiplyVec3(v)

把向量 v 按该四元数旋转后返回新的 Vec3。

q.normalize() / q.inverse() / q.angle()

归一化、求逆旋转、取旋转角(弧度)。

RGB / RGBA — 颜色
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)。

Coord2 / UiScale — UI 辅助
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 — 玩家实体
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 — 玩家专属 API
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 仅为示意,请以你地图编辑器的方块面板为准。