scene-runtime について
Decentraland SDK7 のシーン開発者向けランタイム API リファレンスです。非同期処理、ネットワーク、シーン情報、タイマー、テストなどの共通機能をまとめています。
主な機能
- 01非同期タスクの実行
- 02HTTP と WebSocket API の利用
- 03シーン、Realm、ワールド時間の取得
- 04シーンにデプロイされたファイルの読み取り
- 05ランタイムタイマーと変更リスナーの利用
- 06シーンの自動テスト実行
活用シーン
- 01シーンから公開 API や署名付き API を呼び出す
- 02ワールド時間やフレームループでロジックを駆動する
- 03シーンに配置した設定ファイルを読み込む
- 04SDK7 シーンの自動テストを作成する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dcl-regenesislabs/ opendcl --skill scene-runtimeインストール後、Claude Code で/scene-runtimeを入力して読み込まれたか確認します。
同梱ファイルreferences/runtime-apis.md
詳細
シーンランタイム API
Decentraland SDK7 のすべてのシーンで利用できる、シーン横断型のランタイム API です。
非同期タスク
シーンランタイムはシングルスレッドです。非同期処理はすべて executeTask() でラップしてください:
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
通常の fetch は公開 API で動作します:
const res = await fetch('https://api.example.com/data')
signedFetch はプレイヤーの身元をバックエンドに証明します。署名済みヘッダーのみを取得するには getHeaders() を使用してください(ライブラリが独自の fetch を管理する場合に便利です):
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' })
権限:外部 HTTP には
"ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE"が必要です。ただし、通常の fetch には特別な権限は不要です。signedFetchを使用するには、プレイヤーがシーンを操作している必要があります。
WebSocket
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')
シーンとレルムの情報
import { getSceneInformation, getRealm } from '~system/Runtime'
import { getExplorerInformation } from '~system/EnvironmentApi'
executeTask(async () => {
// Scene info: URN, content mappings, metadata JSON, baseUrl
const scene = await getSceneInformation({})
const metadata = JSON.parse(scene.metadataJson)
console.log(scene.urn, scene.baseUrl, metadata)
// Realm info: baseUrl, realmName, isPreview, networkId, commsAdapter
const realm = await getRealm({})
console.log(realm.realmInfo?.realmName, realm.realmInfo?.isPreview)
// Explorer info: agent string, platform, configurations
const explorer = await getExplorerInformation({})
console.log(explorer.agent, explorer.platform)
})
ワールド時刻
import { getWorldTime } from '~system/Runtime'
executeTask(async () => {
const { seconds } = await getWorldTime({})
// seconds = coordinated world time (cycles 0-86400 for day/night)
})
デプロイ済みファイルの読み取り
実行時に、シーンとともにデプロイされたファイルを読み取ります:
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 コンポーネント
フレーム単位のタイミング情報にアクセスします:
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/RestrictedActions からインポートしてください:
import {
movePlayerTo,
teleportTo,
triggerEmote,
changeRealm,
openExternalUrl,
openNftDialog,
triggerSceneEmote,
copyToClipboard,
setCommunicationsAdapter
} from '~system/RestrictedActions'
// Move player within scene bounds
movePlayerTo({ newRelativePosition: { x: 8, y: 0, z: 8 } })
// 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({ value: 'Hello from Decentraland!' })
// Change realm
changeRealm({ realm: 'other-realm.dcl.eth', message: 'Join this realm?' })
タイマー
グローバルな setTimeout/setInterval ではなく、@dcl/sdk/ecs の timers API を使用してください。QuickJS ランタイムではグローバル関数の動作が不安定です。
import { timers } from '@dcl/sdk/ecs'
const t = timers.setTimeout(() => console.log('delayed'), 2000)
timers.clearTimeout(t)
const id = timers.setInterval(() => console.log('tick'), 1000)
timers.clearInterval(id)
システムベースのタイマー(ゲームロジックに推奨。フレームループと同期します):
let elapsed = 0
engine.addSystem((dt: number) => {
elapsed += dt
if (elapsed >= 3) {
elapsed = 0
// Do something every 3 seconds
}
})
Component.onChange() リスナー
任意のエンティティのコンポーネント変更に反応します:
Transform.onChange(engine.PlayerEntity, (newValue) => {
if (newValue) {
console.log('Player moved to', newValue.position)
}
})
ユーティリティ:removeEntityWithChildren
エンティティとそのすべての子エンティティを再帰的に削除します:
import { removeEntityWithChildren } from '@dcl/sdk/ecs'
removeEntityWithChildren(engine, parentEntity)
ポータブルエクスペリエンス
ワールド間の移動後も維持されるシーンです:
import { spawn, kill, exit, getPortableExperiencesLoaded } from '~system/PortableExperiences'
// Spawn a portable experience by URN
const result = await spawn({ urn: 'urn:decentraland:entity:bafk...' })
// List currently loaded portable experiences
const loaded = await getPortableExperiencesLoaded({})
// Kill a specific portable experience
await kill({ urn: 'urn:decentraland:entity:bafk...' })
// Exit self (if this scene IS a portable experience)
await exit({})
テストフレームワーク
SDK7 には、シーンの自動テスト用テストフレームワークが含まれています:
import { test, assert, assertEquals, assertComponentValue } from '@dcl/sdk/testing'
import { setCameraTransform } from '@dcl/sdk/testing'
test('cube is at correct position', async (context) => {
// Set up camera for the test
setCameraTransform({ position: { x: 8, y: 1, z: 8 } })
// Wait for systems to run
await context.helpers.waitNTicks(2)
// Assert component values
assertComponentValue(cubeEntity, Transform, {
position: Vector3.create(8, 1, 8)
})
// Basic assertions
assert(Transform.has(cubeEntity), 'Entity should have Transform')
assertEquals(1 + 1, 2)
})
テストを実行するには:
npx @dcl/sdk-commands test
ベストプラクティス
- 非同期コードは必ず
executeTask()でラップしてください。ラップされていない Promise は暗黙に破棄されます - バックエンドでプレイヤーの身元を検証する必要がある場合は、通常の
fetchではなくsignedFetchを使用してください - ゲームロジックには
timers.setTimeout/timers.setIntervalよりもシステムベースのタイマーを優先してください。フレームループと同期します。1 回限りの予約アクション(ドアの自動クローズ、遅延サウンドなど)にはtimers.*を使用してください。 realm.realmInfo?.isPreviewを確認してプレビューモードを検出し、デバッグ機能を有効にしてください- シーンとともにデプロイされるデータファイル(JSON 設定、レベルデータなど)には
readFile()を使用してください - 複雑なエンティティ階層をクリーンアップする際には、
removeEntityWithChildren()が不可欠です
executeTask のすべての使用パターン、すべての RestrictedActions、レルムの検出、ポータブルエクスペリエンスについては、{baseDir}/references/runtime-apis.md を参照してください。