scene-runtime
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseScene Runtime APIs
场景运行时 API
Cross-cutting runtime APIs available in every Decentraland SDK7 scene.
每个 Decentraland SDK7 场景都可使用的横跨各领域运行时 API。
Async Tasks
异步任务
The scene runtime is single-threaded. Wrap any async work in (or an async function) — bare promises are silently dropped:
executeTask()typescript
import { executeTask } from "@dcl/sdk/ecs";
executeTask(async () => {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
console.log(data);
});场景运行时是单线程的。将所有异步工作包装在 (或 async 函数)中——未包装的 Promise 会被静默丢弃:
executeTask()typescript
import { executeTask } from "@dcl/sdk/ecs";
executeTask(async () => {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
console.log(data);
});HTTP: fetch & signedFetch
HTTP:fetch 与 signedFetch
Plain fetch works for public APIs:
typescript
const res = await fetch("https://api.example.com/data");signedFetch proves the player's identity to your backend. Use to obtain only the signed headers (useful when a library manages its own fetch):
getHeaders()typescript
import { signedFetch, getHeaders } from "~system/SignedFetch";
// Full signed request
const res = await signedFetch({
url: "https://your-server.com/api",
init: { method: "POST", body: JSON.stringify(payload) },
});
// Get signed headers only (for custom fetch calls)
const { headers } = await getHeaders({ url: "https://your-server.com/api" });signedFetch{ ok, status, statusText, headers, body }bodyJSON.parse(response.body).json()Permission: the nominal permission for external HTTP is(and"USE_FETCH"for sockets), declared in"USE_WEBSOCKET"scene.json. In practice plain/signedrequiredPermissionsto non-media hosts is not hard-blocked in preview/Worlds even without it (thefetchtest scene calls66,6-signed-fetchwith an emptysignedFetch). DeclarerequiredPermissionsanyway for correctness and forward-compat.USE_FETCHdoes not require prior player interaction — restricted actions do,signedFetch/fetchdo not.signedFetch
普通 fetch 可用于公共 API:
typescript
const res = await fetch("https://api.example.com/data");signedFetch 用于向后端证明玩家身份。使用 仅获取已签名的标头(当某个库自行管理其 fetch 时,这会很有用):
getHeaders()typescript
import { signedFetch, getHeaders } from "~system/SignedFetch";
// Full signed request
const res = await signedFetch({
url: "https://your-server.com/api",
init: { method: "POST", body: JSON.stringify(payload) },
});
// Get signed headers only (for custom fetch calls)
const { headers } = await getHeaders({ url: "https://your-server.com/api" });signedFetch{ ok, status, statusText, headers, body }bodyJSON.parse(response.body).json()权限:外部 HTTP 的权限名称为(socket 则为"USE_FETCH"),需在"USE_WEBSOCKET"的scene.json中声明。实际上,即使未声明该权限,在预览/Worlds 中对非媒体主机的普通/签名requiredPermissions也不会被硬性拦截(fetch测试场景在66,6-signed-fetch为空时调用了requiredPermissions)。为确保正确性和向前兼容,仍应声明signedFetch。USE_FETCH不要求玩家事先进行交互——受限操作需要,而signedFetch/fetch不需要。signedFetch
WebSocket
WebSocket
typescript
const ws = new WebSocket("wss://your-server.com/ws");
ws.onopen = () => ws.send("hello");
ws.onmessage = (event) => console.log(event.data);
ws.onclose = () => console.log("disconnected");typescript
const ws = new WebSocket("wss://your-server.com/ws");
ws.onopen = () => ws.send("hello");
ws.onmessage = (event) => console.log(event.data);
ws.onclose = () => console.log("disconnected");Scene & Realm Information
场景与 Realm 信息
typescript
import { getSceneInformation, getRealm, getExplorerInformation } from "~system/Runtime";
executeTask(async () => {
// Scene info: URN, content mappings, metadata JSON, baseUrl
const scene = await getSceneInformation({});
const metadata = JSON.parse(scene.metadataJson);
// metadata.scene?.parcels (parcel list), metadata.display?.title (scene title)
console.log(scene.urn, scene.baseUrl, metadata);
// Realm info: baseUrl, realmName, isPreview, networkId (1 = mainnet, 5 = goerli), commsAdapter
const realm = await getRealm({});
console.log(realm.realmInfo?.realmName, realm.realmInfo?.isPreview); // realmName e.g. "peer-us-1"
// Explorer info: agent string, platform, configurations
const explorer = await getExplorerInformation({});
console.log(explorer.agent, explorer.platform);
});Check to detect preview mode and gate debug features.
realm.realmInfo?.isPreviewtypescript
import { getSceneInformation, getRealm, getExplorerInformation } from "~system/Runtime";
executeTask(async () => {
// Scene info: URN, content mappings, metadata JSON, baseUrl
const scene = await getSceneInformation({});
const metadata = JSON.parse(scene.metadataJson);
// metadata.scene?.parcels (parcel list), metadata.display?.title (scene title)
console.log(scene.urn, scene.baseUrl, metadata);
// Realm info: baseUrl, realmName, isPreview, networkId (1 = mainnet, 5 = goerli), commsAdapter
const realm = await getRealm({});
console.log(realm.realmInfo?.realmName, realm.realmInfo?.isPreview); // realmName e.g. "peer-us-1"
// Explorer info: agent string, platform, configurations
const explorer = await getExplorerInformation({});
console.log(explorer.agent, explorer.platform);
});检查 以检测预览模式,并据此控制调试功能的启用。
realm.realmInfo?.isPreviewWorld Time
世界时间
typescript
import { getWorldTime } from "~system/Runtime";
executeTask(async () => {
const { seconds } = await getWorldTime({});
// seconds = coordinated world time (cycles 0-86400 for day/night)
});typescript
import { getWorldTime } from "~system/Runtime";
executeTask(async () => {
const { seconds } = await getWorldTime({});
// seconds = coordinated world time (cycles 0-86400 for day/night)
});Read Deployed Files
读取已部署文件
Read files deployed with the scene at runtime — use it for data files like JSON configs or level data:
typescript
import { readFile } from "~system/Runtime";
executeTask(async () => {
const result = await readFile({ fileName: "data/config.json" });
const text = new TextDecoder().decode(result.content);
const config = JSON.parse(text);
});在运行时读取随场景部署的文件——适用于 JSON 配置或关卡数据等数据文件:
typescript
import { readFile } from "~system/Runtime";
executeTask(async () => {
const result = await readFile({ fileName: "data/config.json" });
const text = new TextDecoder().decode(result.content);
const config = JSON.parse(text);
});EngineInfo Component
EngineInfo 组件
Access frame-level timing:
typescript
import { EngineInfo } from "@dcl/sdk/ecs";
engine.addSystem(() => {
const info = EngineInfo.getOrNull(engine.RootEntity);
if (info) {
console.log(info.frameNumber, info.tickNumber, info.totalRuntime);
}
});访问帧级计时信息:
typescript
import { EngineInfo } from "@dcl/sdk/ecs";
engine.addSystem(() => {
const info = EngineInfo.getOrNull(engine.RootEntity);
if (info) {
console.log(info.frameNumber, info.tickNumber, info.totalRuntime);
}
});System Execution Order & Priority
系统执行顺序与优先级
engine.addSystem(fn, priority?, name?)fn(dt)priorityHIGHER priority number = runs EARLIER in the frame. Systems are sorted descending by priority ( in ). The SDK's own JSDoc states: "a number with the priority, big number are called before smaller ones."
sort((a, b) => b.priority - a.priority)@dcl/ecsWARNING — counter-intuitive: This is the OPPOSITE of Unity/Godot/many engines where a lower number runs first. In Decentraland SDK7, "make this run first" means giving it a large priority number, NOT. A system with priority1runs almost LAST.1
Key numbers:
- Default priority is (
100000).SYSTEMS_REGULAR_PRIORITY = 100e3with no priority uses this.engine.addSystem(fn) - registers its UI renderer system at
@dcl/react-ecs(and a UI-scale system at100000). So UI runs alongside/just before default-priority systems.100001 - To run before all regular systems, pass a priority above (e.g.
100000). To run after them, pass a priority belowengine.addSystem(fn, 1000000)(e.g.100000, or the default10used by scripts).0
typescript
engine.addSystem(earlySystem, 1000000); // runs before regular systems
engine.addSystem(regularSystem); // priority 100000 (default)
engine.addSystem(lateSystem, 10); // runs after regular systems- Physics libraries (e.g. cannon.js) stepped inside a system's obey the same rule — there is no separate physics loop. Give the physics-stepping system a high priority if other systems must read post-step state the same frame.
update(dt) - Systems with the same priority currently run in insertion order, but the engine does not guarantee a stable tie-break (noted as a TODO in the source). Do not rely on ordering between equal-priority systems.
engine.addSystem(fn, priority?, name?)fn(dt)priority优先级数字越高 = 在帧中运行得越早。 系统按优先级降序排列( 中为 )。SDK 自身的 JSDoc 写道:“一个表示优先级的数字,数值越大越先于较小值调用。”
@dcl/ecssort((a, b) => b.priority - a.priority)警告——这违反直觉: 这与 Unity/Godot/许多引擎的做法相反,在那些引擎中,较小数字会先运行。在 Decentraland SDK7 中,“让该系统先运行”意味着赋予其较大的优先级数字,而不是。优先级为1的系统几乎最后运行。1
关键数值:
- 默认优先级为 (
100000)。调用SYSTEMS_REGULAR_PRIORITY = 100e3且不指定优先级时使用此值。engine.addSystem(fn) - 会将其 UI 渲染器系统注册为
@dcl/react-ecs(UI 缩放系统为100000)。因此,UI 会与默认优先级系统同时或刚好在其前运行。100001 - 要在所有常规系统之前运行,请传入高于 的优先级(例如
100000)。要在其之后运行,请传入低于engine.addSystem(fn, 1000000)的优先级(例如100000,或脚本使用的默认值10)。0
typescript
engine.addSystem(earlySystem, 1000000); // runs before regular systems
engine.addSystem(regularSystem); // priority 100000 (default)
engine.addSystem(lateSystem, 10); // runs after regular systems- 物理库(例如 cannon.js)在某个系统的 内执行步进时,同样遵循此规则——不存在独立的物理循环。如果其他系统必须在同一帧读取步进后的状态,请为物理步进系统设置高优先级。
update(dt) - 优先级相同的系统目前按插入顺序运行,但引擎不保证稳定的平局决胜顺序(源码中将其标记为 TODO)。不要依赖优先级相同的系统之间的执行顺序。
Restricted Actions
受限操作
These require prior player interaction (e.g. a click) before they can execute. Import from :
~system/RestrictedActionstypescript
import {
movePlayerTo,
teleportTo,
triggerEmote,
changeRealm,
openExternalUrl,
openNftDialog,
triggerSceneEmote,
copyToClipboard,
setCommunicationsAdapter,
} from "~system/RestrictedActions";
// Move player within scene bounds. Optional: cameraTarget (where the
// CAMERA looks), avatarTarget (where the AVATAR faces — for rotating in
// place), duration (seconds, for a smooth glide instead of a snap).
movePlayerTo({
newRelativePosition: { x: 8, y: 0, z: 8 },
cameraTarget: { x: 8, y: 1, z: 12 },
});
// Teleport to coordinates in Genesis City
teleportTo({ worldCoordinates: { x: 50, y: 70 } });
// Play a built-in emote
triggerEmote({ predefinedEmote: "wave" });
// Open URL in browser (prompts user)
openExternalUrl({ url: "https://decentraland.org" });
// Open NFT detail dialog
openNftDialog({
urn: "urn:decentraland:ethereum:erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d:558536",
});
// Copy text to clipboard
copyToClipboard({ text: "Hello from Decentraland!" });
// Change realm. `message` is OPTIONAL: omit it to switch with no prompt,
// include it to show the player a confirmation dialog first.
changeRealm({ realm: "https://peer.decentraland.org" }); // no prompt
changeRealm({ realm: "other-realm.dcl.eth", message: "Join this realm?" });这些操作要求玩家事先进行交互(例如点击)后才能执行。请从 导入:
~system/RestrictedActionstypescript
import {
movePlayerTo,
teleportTo,
triggerEmote,
changeRealm,
openExternalUrl,
openNftDialog,
triggerSceneEmote,
copyToClipboard,
setCommunicationsAdapter,
} from "~system/RestrictedActions";
// Move player within scene bounds. Optional: cameraTarget (where the
// CAMERA looks), avatarTarget (where the AVATAR faces — for rotating in
// place), duration (seconds, for a smooth glide instead of a snap).
movePlayerTo({
newRelativePosition: { x: 8, y: 0, z: 8 },
cameraTarget: { x: 8, y: 1, z: 12 },
});
// Teleport to coordinates in Genesis City
teleportTo({ worldCoordinates: { x: 50, y: 70 } });
// Play a built-in emote
triggerEmote({ predefinedEmote: "wave" });
// Open URL in browser (prompts user)
openExternalUrl({ url: "https://decentraland.org" });
// Open NFT detail dialog
openNftDialog({
urn: "urn:decentraland:ethereum:erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d:558536",
});
// Copy text to clipboard
copyToClipboard({ text: "Hello from Decentraland!" });
// Change realm. `message` is OPTIONAL: omit it to switch with no prompt,
// include it to show the player a confirmation dialog first.
changeRealm({ realm: "https://peer.decentraland.org" }); // no prompt
changeRealm({ realm: "other-realm.dcl.eth", message: "Join this realm?" });Timers
定时器
Always use the engine-bound object from . Do NOT use the native JS / globals. Although the QuickJS runtime exposes JS-standard / / / as globals (declared in ), calling them in a Decentraland scene may appear to work but can introduce subtle problems — they are not bound to the scene's engine. Use instead.
timers@dcl/sdk/ecssetTimeoutsetIntervalsetTimeoutclearTimeoutsetIntervalclearInterval@dcl/js-runtime/index.d.tstimers.setTimeouttypescript
import { timers } from "@dcl/sdk/ecs";
const timeoutId = timers.setTimeout(() => console.log("delayed"), 2000);
timers.clearTimeout(timeoutId);
const intervalId = timers.setInterval(() => console.log("tick"), 1000);
timers.clearInterval(intervalId);The signatures match the JS-standard timers:
ts
timers.setTimeout(callback: () => void, ms: number): number
timers.clearTimeout(timerId: number): void
timers.setInterval(callback: () => void, ms: number): number
timers.clearInterval(timerId: number): voidArgument order is — not . Do NOT write a custom helper that flips them.
(callback, ms)(ms, callback)Do NOT write a custom per-frame timer system that accumulates to fire delayed callbacks. The SDK already ships . Custom systems duplicate work, drift from the engine's own scheduling, and are the wrong abstraction for one-shot delays.
dttimersFor a custom engine instance, use from to get a object scoped to that engine.
createTimers(engineInstance)@dcl/sdk/ecsTimersSystem-based timers (recommended for game logic — synchronized with the frame loop):
typescript
let elapsed = 0;
engine.addSystem((dt: number) => {
elapsed += dt;
if (elapsed >= 3) {
elapsed = 0;
// Do something every 3 seconds
}
});始终使用从 获取的、绑定至 engine 的 对象。不要使用原生 JS / 全局函数。尽管 QuickJS 运行时将符合 JS 标准的 / / / 暴露为全局函数( 中有声明),但在 Decentraland 场景中调用它们可能看似正常,却可能引入微妙问题——它们没有绑定到场景的 engine。请改用 。
@dcl/sdk/ecstimerssetTimeoutsetIntervalsetTimeoutclearTimeoutsetIntervalclearInterval@dcl/js-runtime/index.d.tstimers.setTimeouttypescript
import { timers } from "@dcl/sdk/ecs";
const timeoutId = timers.setTimeout(() => console.log("delayed"), 2000);
timers.clearTimeout(timeoutId);
const intervalId = timers.setInterval(() => console.log("tick"), 1000);
timers.clearInterval(intervalId);其签名与符合 JS 标准的定时器一致:
ts
timers.setTimeout(callback: () => void, ms: number): number
timers.clearTimeout(timerId: number): void
timers.setInterval(callback: () => void, ms: number): number
timers.clearInterval(timerId: number): void参数顺序为 ——不是 。不要编写会颠倒它们的自定义辅助函数。
(callback, ms)(ms, callback)不要编写自定义的逐帧定时器系统来累积 以触发延迟回调。SDK 已经提供了 。自定义系统会重复执行工作、与 engine 自身的调度产生偏差,而且对于一次性延迟而言是错误的抽象。
dttimers对于自定义 engine 实例,请使用 (来自 )获取作用域限定到该 engine 的 对象。
createTimers(engineInstance)@dcl/sdk/ecsTimers基于系统的定时器(推荐用于游戏逻辑——与帧循环同步):
typescript
let elapsed = 0;
engine.addSystem((dt: number) => {
elapsed += dt;
if (elapsed >= 3) {
elapsed = 0;
// Do something every 3 seconds
}
});Component.onChange() Listener
Component.onChange() 监听器
React to component changes on any entity:
typescript
Transform.onChange(engine.PlayerEntity, (newValue) => {
if (newValue) {
console.log("Player moved to", newValue.position);
}
});响应任意实体上的组件变化:
typescript
Transform.onChange(engine.PlayerEntity, (newValue) => {
if (newValue) {
console.log("Player moved to", newValue.position);
}
});Utility: removeEntityWithChildren
工具函数:removeEntityWithChildren
Recursively remove an entity and all its children — reach for this when cleaning up complex entity hierarchies:
typescript
import { removeEntityWithChildren } from "@dcl/sdk/ecs";
removeEntityWithChildren(engine, parentEntity);递归移除一个实体及其所有子实体——清理复杂实体层级时应使用此工具:
typescript
import { removeEntityWithChildren } from "@dcl/sdk/ecs";
removeEntityWithChildren(engine, parentEntity);Portable Experiences
可移植体验
Scenes that persist across world navigation. Import from .
~system/PortableExperiencestypescript
import {
spawn,
kill,
exit,
getPortableExperiencesLoaded,
} from "~system/PortableExperiences";
// Spawn by ENS name (a deployed World) OR by pid. NOT by "urn".
const result = await spawn({ ens: "boedo.dcl.eth" });
// result: { pid, parentCid, name, ens }
// Kill a running one by its pid (from the spawn response). NOT by urn.
if (result.pid) await kill({ pid: result.pid });
// List currently loaded portable experiences
const { loaded } = await getPortableExperiencesLoaded({});
// Exit self (only if THIS scene IS a portable experience)
await exit({});- →
spawn({ ens?, pid? }). Field isSpawnResponse { pid, parentCid, name, ens? }/ens, notpid.urn - returns
kill({ pid });{ status: boolean }andkill({ pid })both key offgetPortableExperiencesLoaded({}), neverpid.urn - The host scene must enable them in :
scene.json. Values:"featureToggles": { "portableExperiences": "enabled" }|"enabled"|"disabled"(spawns PX but hides their UI). With"hideUi","disabled"is a no-op / rejected.spawn()
跨世界导航仍会保留的场景。请从 导入。
~system/PortableExperiencestypescript
import {
spawn,
kill,
exit,
getPortableExperiencesLoaded,
} from "~system/PortableExperiences";
// Spawn by ENS name (a deployed World) OR by pid. NOT by "urn".
const result = await spawn({ ens: "boedo.dcl.eth" });
// result: { pid, parentCid, name, ens }
// Kill a running one by its pid (from the spawn response). NOT by urn.
if (result.pid) await kill({ pid: result.pid });
// List currently loaded portable experiences
const { loaded } = await getPortableExperiencesLoaded({});
// Exit self (only if THIS scene IS a portable experience)
await exit({});- →
spawn({ ens?, pid? })。字段是SpawnResponse { pid, parentCid, name, ens? }/ens,不是pid。urn - 返回
kill({ pid });{ status: boolean }和kill({ pid })都以getPortableExperiencesLoaded({})为键,绝不会使用pid。urn - 宿主场景必须在 中启用它们:
scene.json。可选值为"featureToggles": { "portableExperiences": "enabled" }|"enabled"|"disabled"(生成 PX 但隐藏其 UI)。使用"hideUi"时,"disabled"不会执行任何操作/会被拒绝。spawn()
Testing Framework
测试框架
Scenes can ship unit tests using . Tests are generators — yielding pauses until the next frame so you can observe engine state across ticks.
@dcl/sdk/testingtypescript
import { test } from "@dcl/sdk/testing";
import {
assertComponentValue,
assertEquals,
} from "@dcl/sdk/testing/assert";
import { engine, Transform, MeshRenderer } from "@dcl/sdk/ecs";
import { Vector3, Quaternion } from "@dcl/sdk/math";
test("transform is applied after one frame", function* () {
const entity = engine.addEntity();
Transform.create(entity, { position: Vector3.One() });
// Let the engine run for a frame before asserting
yield;
assertComponentValue(entity, Transform, {
position: Vector3.One(),
scale: Vector3.One(),
rotation: Quaternion.Identity(),
parent: 0 as any,
});
});
test("five meshes are present", function* () {
yield;
assertEquals(1 + 1, 2, "basic math");
// No count assertion exists — count via getEntitiesWith + Array.from
assertEquals(
Array.from(engine.getEntitiesWith(MeshRenderer)).length,
5,
"should have 5 meshes"
);
});Available assertions () — exactly these four:
@dcl/sdk/testing/assert- — deep-equals check
assertEquals(actual, expected, message?) - — truthiness check
assert(condition, message?) - — full component value comparison
assertComponentValue(entity, Component, expected) - — deep numeric comparison with tolerance (for floats)
deepCloseTo(actual, expected, options?)
There is NO count assertion — count entities with .
assertEquals(Array.from(engine.getEntitiesWith(Comp)).length, n)Running tests: there is no CLI test command ( does not exist). Tests execute only when the hosting runtime exposes the module — CI test runners or test-enabled explorers. In a normal preview the test runner is a no-op that just logs, and it's guarded behind DEBUG in production builds. Tests run inside the same QuickJS runtime as the scene, so the same restrictions apply (no Node.js APIs, use SDK timers, etc.).
npx @dcl/sdk-commands test~system/Testing场景可以随包提供使用 编写的单元测试。测试以生成器形式实现——通过 暂停至下一帧,以便跨 tick 观察 engine 状态。
@dcl/sdk/testingyieldtypescript
import { test } from "@dcl/sdk/testing";
import {
assertComponentValue,
assertEquals,
} from "@dcl/sdk/testing/assert";
import { engine, Transform, MeshRenderer } from "@dcl/sdk/ecs";
import { Vector3, Quaternion } from "@dcl/sdk/math";
test("transform is applied after one frame", function* () {
const entity = engine.addEntity();
Transform.create(entity, { position: Vector3.One() });
// Let the engine run for a frame before asserting
yield;
assertComponentValue(entity, Transform, {
position: Vector3.One(),
scale: Vector3.One(),
rotation: Quaternion.Identity(),
parent: 0 as any,
});
});
test("five meshes are present", function* () {
yield;
assertEquals(1 + 1, 2, "basic math");
// No count assertion exists — count via getEntitiesWith + Array.from
assertEquals(
Array.from(engine.getEntitiesWith(MeshRenderer)).length,
5,
"should have 5 meshes"
);
});可用断言()——仅限以下四种:
@dcl/sdk/testing/assert- — 深度相等检查
assertEquals(actual, expected, message?) - — 真值检查
assert(condition, message?) - — 完整组件值比较
assertComponentValue(entity, Component, expected) - — 带容差的深度数值比较(用于浮点数)
deepCloseTo(actual, expected, options?)
不存在数量断言——使用 统计实体。
assertEquals(Array.from(engine.getEntitiesWith(Comp)).length, n)运行测试:没有 CLI 测试命令( 并不存在)。测试仅在宿主运行时提供 模块时执行——即 CI 测试运行器或启用测试的 explorer。在普通预览中,测试运行器只是一个仅记录日志的空操作,并且在生产构建中受 DEBUG 开关控制。测试在与场景相同的 QuickJS 运行时中执行,因此同样受到限制(无 Node.js API,请使用 SDK 定时器等)。
npx @dcl/sdk-commands test~system/TestingLogging
日志
Only and are declared in the runtime — , , , are NOT available.
console.log()console.error()console.warn().info().debug().trace()运行时中只声明了 和 ——、、、 均不可用。
console.log()console.error()console.warn().info().debug().trace()Example scenes
示例场景
Engine-team test scenes exercising these APIs against the real runtime:
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/66,6-signed-fetch — on click; reads
signedFetch/response.ok/.status, inspects the auto-added signed headers..body - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/80,-4-restricted-actions — every RestrictedAction via UI buttons: (with
movePlayerToandcameraTarget),avatarTarget,teleportTo,triggerEmote,triggerSceneEmote,openExternalUrl,openNftDialog(with and withoutchangeRealm).message - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,8-portable-experience — /
spawn({ ens })from the spawn response; hostkill({ pid })hasscene.json.portableExperiences: "enabled" - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,9-portable-experience-disabled — same, but host sets
scene.json(spawn suppressed).portableExperiences: "disabled" - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,7-portable-experience-hide-ui — host sets
scene.json(PX run, their UI hidden).portableExperiences: "hideUi"
Full RestrictedActions reference — ( requirement), , rotate-in-place, predefined emote names — plus extra variants (error handling, sequential): .
triggerSceneEmote_emote.glbsetCommunicationsAdaptermovePlayerToexecuteTask{baseDir}/references/runtime-apis.md引擎团队用于在真实运行时中测试这些 API 的测试场景:
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/66,6-signed-fetch — 在点击时调用 ;读取
signedFetch/response.ok/.status,检查自动添加的签名标头。.body - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/80,-4-restricted-actions — 通过 UI 按钮触发每个 RestrictedAction:(包含
movePlayerTo和cameraTarget)、avatarTarget、teleportTo、triggerEmote、triggerSceneEmote、openExternalUrl、openNftDialog(包含和不包含changeRealm)。message - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,8-portable-experience — 从生成响应中取得 pid 后执行 /
spawn({ ens });宿主kill({ pid })设置了scene.json。portableExperiences: "enabled" - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,9-portable-experience-disabled — 同上,但宿主 设置了
scene.json(禁止生成 PX)。portableExperiences: "disabled" - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,7-portable-experience-hide-ui — 宿主 设置了
scene.json(运行 PX,但隐藏其 UI)。portableExperiences: "hideUi"
完整的 RestrictedActions 参考——包括 ( 要求)、、 原地旋转、预定义 emote 名称——以及额外的 变体(错误处理、顺序执行):。
triggerSceneEmote_emote.glbsetCommunicationsAdaptermovePlayerToexecuteTask{baseDir}/references/runtime-apis.md