Appearance
hoscene シーン定義 v1(草案)
この文書は、com.hoshiboshi.asset.hoscene パッケージの Scenes/*.scene.json ファイルの内部構造と全コンポーネントの仕様を定義します。
シーンファイル全体構造
json
{
"sceneId": "main",
"displayName": "Main Lobby",
"physicsOverrides": {
"gravity": [0.0, -9.80665, 0.0]
},
"nodes": [
{
"id": "root",
"name": "Root",
"parentId": null,
"components": []
}
]
}| フィールド | 必須 | 説明 |
|---|---|---|
sceneId | はい | シーン識別子。manifest.json の scenes[].id と一致する。 |
displayName | いいえ | エディタ・デバッグ用の表示名。 |
physicsOverrides | いいえ | このシーン固有の物理パラメータ。manifest.json の physics を上書きする。 |
nodes | はい | ノード配列。1 つ以上必須。parentId: null のルートノードを必ず 1 つ含む。 |
ノード
各ノードはシーングラフの 1 要素です。親子関係は parentId で表現します。
json
{
"id": "floor_01",
"name": "LobbyFloor",
"parentId": "root",
"components": []
}| フィールド | 必須 | 説明 |
|---|---|---|
id | はい | シーン内で一意なノード識別子。 |
name | いいえ | エディタ・デバッグ用の表示名。 |
parentId | root 以外は必須 | 親ノードの id。ルートノードのみ null。 |
components | いいえ | コンポーネント配列。空配列可。 |
ノードの挙動
- 各シーンにルートノード(
parentId: null)は 1 つだけ。 - 子ノードの Transform は親からの相対変換。ワールド行列の合成はホストエンジンが担当。
- 親が削除または非表示になると、すべての子孫に波及する。
- 親子関係に循環がないことをパース時に検証する。
- 未知のコンポーネントは無視し、ワーニングを出力する。既知のコンポーネント内の未知フィールドも無視する。
- ルートノードが存在しないシーンは無効とし、ロードを拒否する。
座標系・単位系
| 項目 | 値 |
|---|---|
| 座標系 | 右手系 |
| 上方向 | Y+ |
| 前方向 | Z- |
| 距離 | meter |
| 質量 | kilogram |
| 時間 | second |
| 回転 | クォータニオン [x, y, z, w] |
| スケール | 各軸独立 |
Transform の適用順: scale → rotation → translation。
コンポーネント一覧
全コンポーネントは component+com.hoshiboshi.* 名前空間に属します。type フィールドで種別を宣言します。
シーン内の type は常に具体的なコンポーネント ID にします。component+dev.yoking.specialComponents.* のような末尾 wildcard は、Nocturne の componentKinds や client runtime capability など、対応範囲を宣言するメタデータでのみ使用できます。実際のシーンファイルに wildcard type を書いてはいけません。
1. Transform — component+com.hoshiboshi.transform
ノードの位置・回転・スケールを定義します。
json
{
"type": "component+com.hoshiboshi.transform",
"position": [0.0, 1.2, 2.0],
"rotation": [0.0, 0.0, 0.0, 1.0],
"scale": [1.0, 1.0, 1.0]
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
position | float[3] | [0, 0, 0] | 位置(m)。 |
rotation | float[4] | [0, 0, 0, 1] | クォータニオン(x, y, z, w)。 |
scale | float[3] | [1, 1, 1] | 各軸スケール。 |
挙動:
- クォータニオンはロード時に正規化する。単位長でない場合はワーニング。
- スケール
0は有効(隠蔽用途)。負スケールはミラーリングに使用可能。 - 親ノードの Transform と合成したワールド行列はエンジン側で計算する。
2. StaticMesh — component+com.hoshiboshi.staticMesh
静的メッシュを表示します。頂点変形を伴わないモデルに使用します。
json
{
"type": "component+com.hoshiboshi.staticMesh",
"resourceId": "model.lobby",
"visible": true,
"castShadows": true,
"receiveShadows": true
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
resourceId | string | 必須 | manifest.json の resources[] 内の id。kind は "model"。 |
visible | bool | true | 初期表示状態。 |
castShadows | bool | true | 影を落とすか。 |
receiveShadows | bool | true | 影を受けるか。 |
挙動:
resourceIdから GLB メッシュをロード。解決失敗時は非表示(エラーログのみ。クラッシュしない)。- 「静的」とは頂点変形なしの意味。Transform による移動・回転・拡縮は可能。
- 複数ノードが同一
resourceIdを参照可能(インスタンシング最適化の余地)。 visibleはランタイムにスクリプトから切り替え可能。castShadows、receiveShadowsも動的変更可。
3. SkinnedMesh — component+com.hoshiboshi.skinnedMesh
ボーン変形を伴うメッシュを表示します。アバターやアニメーション付きオブジェクトに使用します。
json
{
"type": "component+com.hoshiboshi.skinnedMesh",
"resourceId": "model.avatar",
"skeletonRoot": "avatar_root",
"visible": true,
"castShadows": true
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
resourceId | string | 必須 | スキンメッシュ GLB の resources[] 内 id。 |
skeletonRoot | string | 自身の id | スケルトン階層のルートノード id。 |
visible | bool | true | |
castShadows | bool | true |
挙動:
- スケルトン階層はシーングラフのノードとして定義する(GLB 内に埋め込まない)。
skeletonRoot省略時は自身のノードをスケルトンルートとみなす。- ボーンの Transform がスキニングに反映される。ボーン位置はスクリプトまたはサーバー RPC で操作可能。
- スキンデータがない GLB を指定した場合、StaticMesh 相当でレンダリングしワーニングを出力。
4. Collider — component+com.hoshiboshi.collider
物理衝突の形状を定義します。単体では静的コライダー(動かない)として機能し、RigidBody と併用すると動的物理の形状を提供します。
json
{
"type": "component+com.hoshiboshi.collider",
"shape": {
"type": "box",
"halfExtents": [0.5, 0.5, 0.5]
},
"isTrigger": false,
"layer": 0,
"material": {
"staticFriction": 0.6,
"dynamicFriction": 0.6,
"restitution": 0.0
}
}Shape バリエーション
Box:
json
{ "type": "box", "halfExtents": [0.5, 1.0, 0.5] }Sphere:
json
{ "type": "sphere", "radius": 1.0 }Capsule:
json
{ "type": "capsule", "radius": 0.5, "height": 1.8 }Mesh:
json
{ "type": "mesh", "resourceId": "collision.lobby" }| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
shape.type | string | 必須 | "box" | "sphere" | "capsule" | "mesh"。 |
shape.halfExtents | float[3] | box 時必須 | 各軸の半分のサイズ(m)。 |
shape.radius | float | sphere/capsule 時必須 | 半径(m)。 |
shape.height | float | capsule 時必須 | 中心の円柱部の高さ(m)。カプセル全体の高さは height + 2 × radius。 |
shape.resourceId | string | mesh 時必須 | コリジョン用 GLB の resources[] 内 id。kind は "collision"。 |
isTrigger | bool | false | true で物理応答なし、進入/退出イベントのみ発火。 |
layer | int | 0 | 衝突レイヤーマスク。 |
material.staticFriction | float | 0.6 | 静止摩擦係数。 |
material.dynamicFriction | float | 0.6 | 動摩擦係数。 |
material.restitution | float | 0.0 | 反発係数(0 = 跳ねない、1 = 完全弾性)。 |
挙動:
isTrigger: true: 物理衝突なし。進入/退出イベントのみ。検出ゾーン・ゲート通過判定用。isTrigger: false: 物理衝突 + イベント両方。- 同一ノードに複数の Collider を配置して複合形状にできる。
- Mesh Collider はレンダリングメッシュと別物。必ず簡略化コリジョン用 GLB を推奨。
- Box: ノード原点中心に
halfExtents分拡張。 - Sphere: ノード原点中心。
- Capsule: ノード原点中心、Y 軸方向にカプセルを配置。
- RigidBody がないノードの Collider は静的(不動物)。動的な力を受けない。
layerで衝突相手をフィルタリングする。
5. RigidBody — component+com.hoshiboshi.rigidBody
ノードに物理シミュレーション対象としての挙動を与えます。同じノードに Collider が必要です。
json
{
"type": "component+com.hoshiboshi.rigidBody",
"mass": 1.0,
"isKinematic": false,
"freezePosition": { "x": false, "y": false, "z": false },
"freezeRotation": { "x": false, "y": false, "z": false },
"linearDamping": 0.0,
"angularDamping": 0.05,
"useGravity": true
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
mass | float | 1.0 | 質量(kg)。0 で無限質量(static 相当)。 |
isKinematic | bool | false | true で物理シミュレーション対象外。スクリプト変更のみで動く。 |
freezePosition | 全 false | 位置軸のロック。 | |
freezeRotation | 全 false | 回転軸のロック。 | |
linearDamping | float | 0.0 | 速度減衰(空気抵抗)。 |
angularDamping | float | 0.05 | 回転減衰。 |
useGravity | bool | true | ワールド重力の影響を受けるか。 |
挙動:
- 同じノードに Collider がない場合はワーニング(衝突判定なしで落下し続ける)。
isKinematic: true: 重力・衝突力を無視。動く床・エレベーター・ドア用。Transform をスクリプトで操作。isKinematic: false: 完全物理シミュレーション。重力・力積・衝突を計算。freezePosition/Rotation: 該当軸は物理で変化せず、スクリプト変更のみ可。- 質量比で衝突応答: 重い物体ほど軽い物体を押す。
- 静止時のスリープ最適化はエンジン任せ。
6. Light — component+com.hoshiboshi.light
シーンに光源を配置します。
json
{
"type": "component+com.hoshiboshi.light",
"lightType": "point",
"color": [1.0, 0.9, 0.8],
"intensity": 1.5,
"range": 10.0,
"spotAngle": 30.0,
"innerSpotAngle": 25.0,
"castShadows": false
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
lightType | string | 必須 | "directional" | "point" | "spot"。 |
color | float[3] | [1, 1, 1] | RGB(HDR 可、1.0 超も許容)。 |
intensity | float | 1.0 | 光度。 |
range | float | point: 10, spot: 20 | 減衰距離(m)。Directional では無視。 |
spotAngle | float | 30.0 | スポットライト外側コーン角(度)。Point/Directional では無視。 |
innerSpotAngle | float | spotAngle と同じ | スポットライト内側フル照度角。外側との間で減衰。 |
castShadows | bool | false | 影を落とすか(高コスト)。 |
挙動:
- Directional: 位置無視・回転のみ有効。無限遠光源(太陽/月)。シーン全体に影響。
- Point: 位置から全方向に放射。逆二乗距離減衰。
rangeで完全に消灯。 - Spot: 位置からコーン状に放射。方向はノードの前方(Z-)。
- 最終色 =
color × intensity。 - 影を落とす Directional Light はシーン内 1 つ推奨。クライアントハードウェアで上限あり。
7. AudioSource — component+com.hoshiboshi.audioSource
音声を再生します。3D 空間オーディオまたは 2D グローバルオーディオとして使用します。
json
{
"type": "component+com.hoshiboshi.audioSource",
"resourceId": "audio.bgm",
"loop": true,
"autoplay": true,
"spatial": false,
"volume": 0.7,
"pitch": 1.0,
"minDistance": 1.0,
"maxDistance": 50.0,
"rolloffMode": "inverse",
"priority": 128
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
resourceId | string | 必須 | kind が "audio" の resources[] 内 id。 |
loop | bool | false | ループ再生。 |
autoplay | bool | false | シーンロード時に自動再生。 |
spatial | bool | true | true で 3D 空間オーディオ。false で 2D グローバル。 |
volume | float | 1.0 | 0.0–1.0。 |
pitch | float | 1.0 | 再生速度兼ピッチ。 |
minDistance | float | 1.0 | 最大音量の距離(m)。 |
maxDistance | float | 50.0 | 音量 0 になる距離(m)。 |
rolloffMode | string | "inverse" | "inverse" | "linear" | "logarithmic"。 |
priority | int | 128 | 小さいほど優先度高。同時発音数超過時に低優先度から停止。 |
挙動:
spatial: true: 3D オーディオ。リスナーからの距離・角度で音量・パンを変化。spatial: false: 2D グローバル。BGM・UI 音用。位置無視。- 距離減衰:
< minDistance= 100% 音量。min–max 間= rolloffMode に従う。> maxDistance= 無音。 - 同一ノードに複数 AudioSource を配置してレイヤー再生可能。
8. SpawnPoint — component+com.hoshiboshi.spawnPoint
プレイヤーの出現地点を定義します。視覚表現を持たないメタデータコンポーネントです。
json
{
"type": "component+com.hoshiboshi.spawnPoint",
"spawnTag": "default",
"spawnRadius": 0.5,
"spawnRotation": [0.0, 0.0, 0.0, 1.0],
"priority": 0,
"playerLimit": 0
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
spawnTag | string | "default" | スポーン地点カテゴリ("teamA", "respawn" など)。 |
spawnRadius | float | 0.0 | XZ 平面ランダムオフセット半径(m)。プレイヤー重なり防止。 |
spawnRotation | float[4] | Transform の値 | 上書き回転。省略時はノードの Transform.rotation を使用。 |
priority | int | 0 | 大きいほど優先。 |
playerLimit | int | 0 | この地点の最大利用人数。0 = 無制限。 |
挙動:
- サーバーがプレイヤー配置時に参照する。視覚表現なし。
spawnTag一致する中からpriority降順で選択。同 priority はラウンドロビン。playerLimit到達済みの地点は選択対象外。spawnRadius > 0: XZ 平面でランダム位置加算。spawnRotation指定時はそちらを優先。
9. Visibility — component+com.hoshiboshi.visibility
ノードとその子孫のレンダリング設定を制御します。物理やコリジョンには影響しません。
json
{
"type": "component+com.hoshiboshi.visibility",
"visible": true,
"castShadows": true,
"receiveShadows": true,
"renderLayer": 0
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
visible | bool | true | ノードの表示状態。 |
castShadows | bool | true | 影を落とすか。 |
receiveShadows | bool | true | 影を受けるか。 |
renderLayer | int | 0 | レンダリングレイヤーマスク。 |
挙動:
visible: false→ 全子孫も非表示。parent.visible: false, child.visible: trueでも child は非表示(親優先)。- 非表示でも Collider・RigidBody は機能し続ける。レンダリングのみの制御。
renderLayer: カメラごとに描画レイヤーをフィルタ(防犯カメラ視点 / 通常視点の分離など)。- StaticMesh/SkinnedMesh 側の同名フィールドと併用時は AND 条件。
10. Script — component+com.hoshiboshi.script
ノードに WASM モジュールを紐付けます。このコンポーネント自体はロジックを持たず、どの WASM がどのノードを担当するかのマーカーです。
json
{
"type": "component+com.hoshiboshi.script",
"modulePath": "WASM/door_logic.wasm",
"interactable": true,
"parameters": {
"openSpeed": 2.0,
"requireKey": false
}
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
modulePath | string | 必須 | 対応する WASM ファイルのパス。manifest.json の wasmModules[].path と一致する。 |
interactable | bool | false | プレイヤーのインタラクト対象か。 |
parameters | object | {} | WASM に渡す読み取り専用の初期パラメータ。 |
挙動:
- 同じ
modulePathを持つ全ノードが 1 つの WASM インスタンスに束ねられる。 - WASM は
runtime.initで担当オブジェクトの handle 一覧とパラメータを受け取る。 interactable: trueのノードにプレイヤーが近づいてインタラクト操作をすると、host から WASM にrpc.event.object_interactedが配送される。- パラメータはランタイムに読み取り専用で渡され、変更不可。
- WASM がクラッシュしても、host は他の WASM インスタンスやワールド全体に影響させない。
- WASM からは host API(
core.object.*,core.rpc.*,core.log.*)経由でオブジェクト操作・RPC・ログ出力を行う。
WASM の実行モデル:
text
WorldServer / InstanceServer
^
| RPC (host bridge)
v
Client host runtime <----> Engine main thread (render, physics)
^
| message bridge
v
Dedicated WASM worker (per modulePath)- WASM は main thread ではなく専用 worker thread で実行される。
- WASM から engine object への直接アクセスは不可。すべて host API 経由。
- ネットワークアクセスは host bridge の RPC のみ。raw socket 不可。
- 1 tick あたりの実行時間上限を超過したモジュールは停止される。
11. Camera — component+com.hoshiboshi.camera
クライアントの初期視点を定義します。SpawnPoint が「どこに出るか」なら、Camera は「そこからどう見るか」です。
json
{
"type": "component+com.hoshiboshi.camera",
"fov": 90.0,
"nearPlane": 0.1,
"farPlane": 1000.0,
"mode": "firstPerson",
"priority": 0
}| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
fov | float | 90.0 | 垂直視野角(度)。 |
nearPlane | float | 0.1 | 近クリップ面(m)。 |
farPlane | float | 1000.0 | 遠クリップ面(m)。 |
mode | string | "firstPerson" | "firstPerson" | "thirdPerson"。 |
priority | int | 0 | 複数 Camera がある場合の優先度。大きいほど優先。 |
挙動:
- シーン内に Camera がない場合、クライアントは自身の既定カメラ設定を使用する。
- 複数の Camera がある場合、
priorityが最大のものを選択する。同 priority の場合は最初に見つかったものを使用。 mode: "firstPerson": カメラはプレイヤーの頭部位置から前方を向く。mode: "thirdPerson": カメラはプレイヤーの後方上空からプレイヤーを追跡する。オフセット距離は実装依存。fov、nearPlane、farPlaneはクライアントの設定やユーザー選択で上書き可能。シーン定義はあくまで初期値。- Camera の Transform はカメラ自体の位置ではなく、注視点の基準位置として扱う。実際のカメラ位置は
modeに従ってオフセットが計算される。
共通ルール
未知のコンポーネント・フィールド
- 未知の
typeを持つコンポーネントは無視し、ワーニングを出力する。シーンロードは継続する。 - 既知のコンポーネント内の未知フィールドも無視する。前方互換性のため。
- 未知の
shape.type(Collider)、未知のlightType(Light)、未知のmode(Camera)は、そのコンポーネントを無効化する。
リソース解決失敗
コンポーネントが参照する resourceId が manifest.json の resources[] に存在しない、またはリソースの digest 検証に失敗した場合:
- StaticMesh: 非表示(エラーログのみ)。
- SkinnedMesh: 非表示(エラーログのみ)。
- Collider (mesh): その Collider を無効化。他の Collider は維持。
- AudioSource: 無音(エラーログのみ)。
- Script:
modulePathがwasmModules[]に存在しない場合、その Script コンポーネントを無効化(該当ノードは WASM に割り当てられない)。
いずれもクラッシュしない。 ワールドは可能な限りレンダリングを継続する。
コンポーネントの重複
- 1 ノードに Transform は 1 つだけ。重複時は最初の 1 つを使用し、残りは無視。
- StaticMesh と SkinnedMesh は同じノードに共存不可。両方ある場合は SkinnedMesh を優先。
- Collider は同一ノードに複数可(複合形状)。
- RigidBody は 1 ノードに 1 つ。重複時は最初の 1 つを使用。
- Script は同一ノードに複数可(複数 WASM が同じオブジェクトを担当できる)。
拡張コンポーネント
component+com.hoshiboshi.* 以外の名前空間(component+com.example.* など)のコンポーネントは拡張として扱います。未知の拡張コンポーネントは無視し、ワーニングを出力します。拡張コンポーネントがないとワールドが成立しない設計は避けてください。
拡張コンポーネントの対応宣言では、名前空間末尾の .* を wildcard として使えます。たとえば component+dev.yoking.specialComponents.* は、component+dev.yoking.specialComponents. で始まるすべての具体コンポーネント ID に対応していることを示します。wildcard は末尾の .* のみ有効で、シーン内の components[].type には使いません。
シーン完全な例
json
{
"sceneId": "main",
"displayName": "Main Lobby",
"physicsOverrides": {
"gravity": [0.0, -9.80665, 0.0]
},
"nodes": [
{
"id": "root",
"name": "Root",
"parentId": null,
"components": []
},
{
"id": "lobby_floor",
"name": "LobbyFloor",
"parentId": "root",
"components": [
{
"type": "component+com.hoshiboshi.transform",
"position": [0.0, 0.0, 0.0],
"rotation": [0.0, 0.0, 0.0, 1.0],
"scale": [1.0, 1.0, 1.0]
},
{
"type": "component+com.hoshiboshi.staticMesh",
"resourceId": "model.lobby"
},
{
"type": "component+com.hoshiboshi.collider",
"shape": { "type": "mesh", "resourceId": "collision.lobby" }
},
{
"type": "component+com.hoshiboshi.visibility",
"visible": true,
"castShadows": true,
"receiveShadows": true
}
]
},
{
"id": "spawn_default",
"name": "DefaultSpawn",
"parentId": "root",
"components": [
{
"type": "component+com.hoshiboshi.transform",
"position": [0.0, 1.2, 2.0],
"rotation": [0.0, 0.0, 0.0, 1.0],
"scale": [1.0, 1.0, 1.0]
},
{ "type": "component+com.hoshiboshi.spawnPoint", "spawnTag": "default" },
{
"type": "component+com.hoshiboshi.camera",
"fov": 90.0,
"mode": "firstPerson",
"priority": 0
}
]
},
{
"id": "bgm_source",
"name": "BgmSource",
"parentId": "root",
"components": [
{
"type": "component+com.hoshiboshi.transform",
"position": [0.0, 2.0, 0.0],
"rotation": [0.0, 0.0, 0.0, 1.0],
"scale": [1.0, 1.0, 1.0]
},
{
"type": "component+com.hoshiboshi.audioSource",
"resourceId": "audio.bgm",
"loop": true,
"autoplay": true,
"spatial": false,
"volume": 0.7
},
{
"type": "component+com.hoshiboshi.script",
"modulePath": "WASM/bgm_controller.wasm",
"interactable": false
}
]
},
{
"id": "door_main",
"name": "MainDoor",
"parentId": "root",
"components": [
{
"type": "component+com.hoshiboshi.transform",
"position": [3.0, 0.0, -1.0],
"rotation": [0.0, 0.0, 0.0, 1.0],
"scale": [1.0, 1.0, 1.0]
},
{
"type": "component+com.hoshiboshi.staticMesh",
"resourceId": "model.door"
},
{
"type": "component+com.hoshiboshi.collider",
"shape": { "type": "box", "halfExtents": [1.0, 1.5, 0.2] }
},
{
"type": "component+com.hoshiboshi.rigidBody",
"mass": 10.0,
"isKinematic": true
},
{
"type": "component+com.hoshiboshi.script",
"modulePath": "WASM/door_logic.wasm",
"interactable": true,
"parameters": { "openSpeed": 2.0 }
}
]
}
]
}参考
- hoscene パッケージ全体の構造: hoscene format
- Nocturne プロトコルとアセット交換: Nocturne
- WASM host API 草案:
.mwWASM host API v1 .mwフォーマット v1 レイアウト:.mwlayout