跳转至

world & Box3World / GameWorld 世界模块

这是一个服务端API

该API仅在服务端脚本使用

查阅官方文档
查阅官方文档(Arena)
查阅社区文档(Arena)

Box3World / GameWorld无法(很难)被实例化,但在全局存在一个单例对象world
可以通过world对象控制环境天气、物理重力、画面滤镜等全局场景属性,还可以在世界中创建、搜索实体,或监听世界中实体和玩家的碰撞、伤害、互动等事件。

常用


属性

基础

projectName : string
项目的名称,对应 项目-编辑资料-地图名称。无法在代码中修改。
url : URL
当前运行的世界的公共 URL
currentTick : number
当前世界的时刻

物理

gravity : number
重力
airFriction : number
空气阻力
useOBB : boolean

是否启用OBB碰撞
关于OBB碰撞,可以查看此链接获取更多信息

OBB 碰撞

OBB(Oriented Bounding Box)译为方向包围盒,与原本的AABB(Axis Aligned Bounding Box)轴对齐包围盒相比,物理模型新增了旋转属性,碰撞效果更加的精准。

Arena 独有

该属性仅在Arena编辑器中使用

该功能仍在实验中

实验版仅支持400个100面以下的实体在静态场景下以60+FPS展示

性能警告

启用OBB碰撞会造成更大的性能消耗,轻则掉帧、卡顿,重则卡死、崩溃,且也会影响到客户端,不建议在复杂的场景下使用
需要根据地图需要和玩家设备性能来考虑是否开启

天气 & 光照

光照

lightMode : 'natural' | 'manual'

世界光照模式,'natural'为自然模式,昼夜循环按照一定的规律进行。'manual'为手动模式,需要手动设置各个方向上的光。 光照模式不同,决定光照的属性也不同,你可以通过切换下面的选项卡来查看不同光照模式下的光照属性。

sunPhase : number
太阳运行阶段,按照timeOfDay = (sunPhase + sunFrequency * tick) % 1公式计算
sunFrequency : number
太阳在天空中移动的频率。 数值越高=太阳运动越快
lunarPhase : number
月亮的相位。 必须在 0 和 1 之间
sunDirection : Box3Vector3 /
太阳的方向
sunLight : Box3RGBColor /
太阳的光照颜色
skyLeftLight : Box3RGBColor /
skyRightLight : Box3RGBColor /
skyBottomLight : Box3RGBColor /
skyTopLight : Box3RGBColor /
skyFrontLight : Box3RGBColor /
skyBackLight : Box3RGBColor /
天空光照颜色依次在 -x +x -y +y -z +z 方向上的值

fogColor : Box3RGBColor /

雾颜色

Bug

若将颜色设为白色(new Box3RGBColor(1, 1, 1) / new GameRGBColor(1, 1, 1) / #FFFFFF),实际颜色只有#A6B1B9(看地面)#A9B5BE(看天空)
此问题曾向官方反馈,但未得到解决

fogStartDistance : number
雾起始距离
fogHeightOffset : number
雾起始高度
fogHeightFalloff : number
雾高度衰减率
fogUniformDensity : number
均匀雾量(如果>0,就不能看到天幕)
maxFog : number
最大雾量

snowDensity : number
雪密度。密度越大,雪花越多
snowSizeLo : number
雪最小尺寸
snowSizeHi : number
雪最大尺寸
snowFallSpeed : number
雪下落速度
snowSpinSpeed : number
雪自旋速度
snowColor : Box3RGBAColor /
雪颜色。
snowTexture : string
雪纹理,格式为'snow/*.part'

rainDensity : number
雨密度。密度越大,雨滴越多
rainDirection : Box3Vector3 /
雨方向
rainSpeed : number
雨速度
rainSizeLo : number
雨点最小尺寸
rainSizeHi : number
雨点最大尺寸
rainInterference : number
雨扰流幅度
rainColor : Box3RGBAColor /
雨颜色

声音

breakVoxelSound : Box3SoundEffect /
方块破坏声音
placeVoxelSound : Box3SoundEffect /
方块填充声音
playerJoinSound : Box3SoundEffect /
玩家进入世界声音
playerLeaveSound : Box3SoundEffect /
玩家离开世界声音
ambientSound : Box3SoundEffect /
环境声音

方法

实体创建 & 销毁

entityQuota (): number
返回世界当前允许创建的实体的剩余数量
createEntity (config: Partial< / >): / |
创建一个新的 / 或复制一个现有实体 如果超过了实体配额,则返回

搜索

querySelector (selector: / ): Box3Entity / |
通过选择器来查找一个实体,如果找不到,则会返回
querySelectorAll (selector: / ): [] / []

querySelector类似,但是可以查找所有符合选择器的实体,返回一个 / 组成的数组。如果没有符合条件的实体,则返回空数组。

示例
world.querySelectorAll('player').
  forEach(entity => {
    world.say(entity.player.name)
  }) // 遍历世界中的所有玩家并且广播其玩家昵称
testSelector (selector: / , entity: ): boolean

测试实体是否符合选择器,如果实体能被指定的选择器选择,则返回true,否则返回false

示例
world.testSelector('.groupA', a_Entity_Has_Tag_groupA)
raycast (origin: / , direction: / , options?: Partial<> / >): string[][]

射线检测,从 origin 原点位置向 direction 方向投射一条隐形的射线,返回碰到的实体或方块 Box3RaycastOptions / GameRaycastOptions
Box3RaycastResult / GameRaycastResult

示例
/* 按下左键,在玩家位置向脚下发射一条射线,在控制台输出检测结果 */
world.onPress(({ button, entity }) => {
    if(button === 'action0'){
       const res = world.raycast( entity.position, new Box3Vector3(0,-1,0))
       console.log(JSON.stringify(res))
    }
})

提示

有可能遇到射线击中的位置和原点相同,那是因为射线刚发射就碰到其他东西。
举个例子,在玩家位置发射射线,射线原点和玩家重合,射线就会击中玩家。
所以要计算好发射的原点,避免碰到你不想它碰到的东西,或设置options?

searchBox (bounds: / ): [] / []
搜索指定区域内的实体

聊天

say (message: string):

向世界中所有玩家广播

示例
world.onPlayerJoin(({ entity }) => {
    world.say('天空一声巨响,' + entity.player.name + '闪亮登场');
});
createTempChat (userIds?: []): <>

创建临时聊天频道

Arena 独有

该方法仅在Arena编辑器中使用

参数 类型 说明
userIds [] 可选,创建临时频道时同时加入频道的玩家id数组
返回值 类型 说明
Promise<> 创建临时频道后的频道id
示例

world.createTempChat().then(chatId => {
    console.log(`创建临时频道成功,频道id是${chatId}`)
});
(async () => {
    var chatId = await world.createTempChat();
    console.log(`创建临时频道成功,频道id是${chatId}`);
})();
(async () => {
    var chatId = await world.createTempChat(["455", "13006055", "85487", "12750782", "19"]);
    console.log(`创建临时频道成功,频道id是${chatId}`);
})();

destroyTempChat (chatIds: []): <[]>

批量销毁临时聊天频道

Arena 独有

该方法仅在Arena编辑器中使用

参数 类型 说明
chatIds [] 必填,需要销毁的临时频道id数组
返回值 类型 说明
Promise<> 删除失败的临时频道id数组
示例

world.destroyTempChat(['chatId1','chatId2']).then(failedChatIds => {
    if (!failedChatIds.length) {
        console.log(`聊天室销毁成功`);
    } else {
        console.log(`以下聊天室销毁失败:${failedChatIds.join(',')}`);
    }
});
(async () => {
    var failedChatIds = await world.destroyTempChat(['chatId1','chatId2']);
    if (!failedChatIds.length) {
        console.log(`聊天室销毁成功`);
    } else {
        console.log(`以下聊天室销毁失败:${failedChatIds.join(',')}`);
    }
})();

addTempChatPlayer (chatId: , userIds: []): <[]>

向临时聊天频道添加玩家

Arena 独有

该方法仅在Arena编辑器中使用

参数 类型 说明
chatId 必填,临时聊天频道id
userIds [] 必填,加入聊天频道的玩家id数组
返回值 类型 说明
Promise<[]> 添加成功的玩家id数组
示例

world.createTempChat().then(chatId => {
    world.addTempChatPlayer(chatId, ["455", "13006055", "85487", "12750782", "19"]).then(userIds => {
        console.log(`以下玩家id添加聊天频道成功:${userIds.join(',')}`);
    });
});
(async () => {
    var chatId = await world.createTempChat();
    var userIds = await world.addTempChatPlayer(chatId, ["455", "13006055", "85487", "12750782", "19"]);
    console.log(`以下玩家id添加聊天频道成功:${userIds.join(',')}`);
})();

removeTempChatPlayer (chatId: , userIds: []): <[]>

向临时聊天频道移除玩家

Arena 独有

该方法仅在Arena编辑器中使用

参数 类型 说明
chatId 必填,临时聊天频道id
userIds [] 必填,需要在聊天频道中移除的玩家id数组
返回值 类型 说明
Promise<[]> 移除成功的玩家id数组
示例

world.createTempChat(["455", "13006055", "85487", "12750782", "19"]).then(chatId => {
    world.removeTempChatPlayer(chatId, ["13006055", "12750782"]).then(userIds => {
        console.log(`从聊天频道移除以下玩家id成功:${userIds.join(',')}`);
    });
});
(async () => {
    var chatId = await world.createTempChat(["455", "13006055", "85487", "12750782", "19"]);
    var userIds = await world.removeTempChatPlayer(chatId, ["13006055", "12750782"]);
    console.log(`从聊天频道移除以下玩家id成功:${userIds.join(',')}`);
})();

getTempChats (): <[]>

获取当前地图存在的临时聊天频道

Arena 独有

该方法仅在Arena编辑器中使用

返回值 类型 说明
Promise<[]> 当前地图存在的临时聊天频道id数组
示例

world.getTempChats().then(chatIds => {
    console.log(`当前有以下临时聊天频道${chatIds.join(',')}`);
});
(async () => {
    var chatIds = await world.getTempChats();
    console.log(`当前有以下临时聊天频道${chatIds.join(',')}`);
})();

getTempChatUsers (chatId: ): <[]>

获取临时聊天频道中的玩家

Arena 独有

该方法仅在Arena编辑器中使用

参数 类型 说明
chatId 必填,临时聊天频道id
返回值 类型 说明
Promise<[]> 在临时聊天频道中的玩家id数组
示例

world.getTempChatUsers('chatId').then(userIds => {
    console.log(`临时聊天频道有以下玩家${userIds.join(',')}`)
});
(async () => {
    var userIds = await world.getTempChatUsers('chatId');
    console.log(`临时聊天频道有以下玩家${userIds.join(',')}`)
})();

物理

addCollisionFilter (aSelector: / , bSelector: / ):

添加碰撞过滤器,关闭两个实体组之间的碰撞

示例
world.addCollisionFilter('player','player') // 关闭玩家和玩家之间的碰撞
removeCollisionFilter (aSelector: Box3SelectorString / , bSelector: Box3SelectorString / ):
移除碰撞过滤器,不再关闭两个实体组 aSelectorbSelector 之间的碰撞
clearCollisionFilters () =>
清除所有的碰撞过滤器
collisionFilters () => string[][]
返回当前所有的碰撞过滤器
示例
world.collisionFilters().forEach(([ a, b ]) => console.log(a, b)) // 打印全部碰撞过滤器

区域

addZone (config: < / >): /

创建一个区域

待测试

据说一张地图的区域创建上限为129个

待测试

官方示例中使用[]来代替 /
需检查是否可以使用 / []

示例
const area = world.addZone({
    selector: 'player',
    bounds: {
        lo: [48,  8, 50],
        hi: [64, 20, 72],
    },
});
removeZone (trigger: / ):

删除区域

示例
const area = world.addZone({
    selector: 'player',
    bounds: {
        lo: [48,  8, 50],
        hi: [64, 20, 72],
    },
});
world.removeZone(area);
zones(): [] / []

获取该地图的所有区域

警告

尽管其命名非常像一个属性,但这其实是一个方法

addTrigger removeTrigger triggers

已弃用

这三个方法在所有编辑器已经弃用,请使用addZoneremoveZonezones代替

内容缺失

由于该方法过于久远,只能在非常远古的地图(旧岛ID只有五位的部分地图)中找到,并且执行也会提示警告

动画

animate (keyframes: < / >[], playbackInfo?: < / >): / < / , / >
创建一个关键帧动画 /
示例
const ani = world.animate([
    { rainDensity: 0, duration: 1 },
    { rainDensity: 1, duration: 1 },
], {
    iterations: Infinity,// 无限循环
    direction: Box3AnimationDirection.REVERSE, // 雨量反复变大变小
    duration: 16 * 5, // 5秒1个周期(每秒16 ticks)
});

world.onPress(({ button }) => {
    if (button === Box3ButtonType.ACTION0) { // 左键停雨
        ani.cancel();
        world.rainDensity = 0;
    }
});
const ani = world.animate([
    { rainDensity: 0, duration: 1 },
    { rainDensity: 1, duration: 1 },
], {
    iterations: Infinity,// 无限循环
    direction: GameAnimationDirection.REVERSE, // 雨量反复变大变小
    duration: 16 * 5, // 5秒1个周期(每秒16 ticks)
});

world.onPress(({ button }) => {
    if (button === GameButtonType.ACTION0) { // 左键停雨
        ani.cancel();
        world.rainDensity = 0;
    }
});
getAnimations(): / <Box3WorldKeyframe / GameWorldKeyframe, / >[]
获取所有的动画对象
getEntityAnimations(): / <Box3EntityKeyframe / GameEntityKeyframe, / >[]
获取所有实体的动画对象
getPlayerAnimations(): / <Box3PlayerKeyframe / GamePlayerKeyframe, / >[]
获取所有玩家的动画对象

声音

sound (spec: {sample: , position?: / , radius?: , gain?: , pitch?: } | string):

在指定位置播放声音

参数 类型 说明
spec 声音路径
spec 声音播放参数
sample 声音路径
position? / 声音播放的位置。可以指定在某个实体身上发出声音
radius? = 32 声音范围,单位是\(\frac{1}{16}\)个方块
gain? = 1 音量增益。正常为 1,数值越大,声音越大
pitch? = 1 音高增益。正常为 1,大于 1,音调越高,播放速度越快

Bug

position疑似无效,无论设置成什么,实际播放位置都为{ x:0, y:0, z:0 }

示例
// 播放一段声音,所有玩家都能听见
world.sound('audio/drama.mp3');
// 在指定的位置播放 'airhorn' 声音
world.sound({
    sample: 'audio/airhorn.mp3',
    position: new Box3Vector3(64, 10, 64),
    radius: 64  // 只有距离位置4格内的玩家能听见。(1个方块的距离是16)
});
// 在指定的位置播放 'airhorn' 声音
world.sound({
    sample: 'audio/airhorn.mp3',
    position: new GameVector3(64, 10, 64),
    radius: 64  // 只有距离位置4格内的玩家能听见。(1个方块的距离是16)
});

传送

警告

  • 传送进入的地图为独立服务器,因此同一张目标地图,分批次传送不同的人,所进入的是 不同 服务器。
  • 只能在已发布地图中生效
  • players的长度不能超过50
  • players中不能存在游客(没有UserID)
teleport (mapId: , players: []): <>

地图组内传送能力,能够令 Player 被传送到其他地图中。

Arena 独有

该方法仅在Arena编辑器中使用

该方法仅在扩展地图中使用

该方法需要图主有特定权限才能使用

此方法受权限影响,无权限用户可见,但调用后直接报错。

参数 类型 说明
mapId 必填,目标频道id
players [] 必填,需要传送的玩家
示例
while (true) {
    try {
        var players = world.querySelectorAll('player').slice(0, 50);
        players = players.filter(e => e.player.userId !== '' && e.player.userId !== '0' && e.player.userId !== 0);
        await world.teleport('100001157', players);
        break;
    } catch (e) {
        console.error(e.stack);
    }
    await sleep(1000);
}
world.say('传送成功 ');

事件

基本

onTick: / < / >
nextTick: / < / >
Tick 事件,详情请看 /

实体创建/销毁

onPlayerJoin: / < / >
nextPlayerJoin: / < / >

当玩家进入世界(或未来)触发

示例
// 玩家进入地图时,向TA发送一条私信。
world.onPlayerJoin(({ entity }) => {
    entity.player.directMessage(`你好,${entity.player.name}`);
});
onPlayerLeave: / < / >
nextPlayerLeave: / < / >
当玩家离开世界(或未来)触发
onEntityCreate: / < / >
nextEntityCreate: / < / >
当实体被创建(或未来)触发
onEntityDestroy: / < / >
nextEntityDestroy: / < / >
当实体被销毁(或未来)触发

聊天

onChat: / < / >
nextChat: / < / >
当玩家发言(或未来)触发

世界交互

onClick: / < / >
nextClick: / < / >
当玩家点击实体(或未来)触发
onPress: / < / >
nextPress: / < / >
当玩家按下按键(或未来)触发
onRelease: / < / >
nextRelease: / < / >
当玩家松开按键(或未来)触发
onInteract: / < / >
nextInteract: / < / >
当玩家与实体互动(或未来)触发

物理

onEntityContact: / < / >
nextEntityContact: / < / >
当实体碰撞(或未来)触发
onEntitySeparate: / < / >
nextEntitySeparate: / < / >
当实体分开(或未来)触发
onVoxelContact: / < / >
nextVoxelContact: / < / >
当实体碰到方块(或未来)触发
onVoxelSeparate: / < / >
nextVoxelSeparate: / < / >
当实体离开方块(或未来)触发
onFluidEnter: / < / >
nextFluidEnter: / < / >
当实体进入液体(或未来)触发
onFluidLeave: / < / >
nextFluidLeave: / < / >
当实体离开液体(或未来)触发

战斗相关

onTakeDamage: / < / >
nextTakeDamage: / < / >
当实体收到伤害(或未来)触发
onDie: / < / >
nextDie: / < / >
当实体死亡(或未来)触发
onRespawn: / < / >
nextRespawn: / < / >
玩家复活(或未来)触发

商业化

onPlayerPurchaseSuccess: / <>
nextPlayerPurchaseSuccess: / <>

当玩家成功购买物品(或未来)时触发

Arena 独有

该事件仅在Arena编辑器中使用

评论区