@xrift/world-components 0.15.1 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -516
- package/dist/components/VideoScreen/index.d.ts.map +1 -1
- package/dist/components/VideoScreen/index.js +2 -2
- package/dist/components/VideoScreen/index.js.map +1 -1
- package/dist/components/VideoScreen/types.d.ts +2 -0
- package/dist/components/VideoScreen/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Xrift ワールド開発用の共有コンポーネントとユーティリテ
|
|
|
8
8
|
npm install @xrift/world-components
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### 必要な依存関係
|
|
12
12
|
|
|
13
13
|
以下のパッケージがpeerDependenciesとして必要です:
|
|
14
14
|
|
|
@@ -21,525 +21,17 @@ npm install @xrift/world-components
|
|
|
21
21
|
}
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## ドキュメント
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
コンポーネントやHooksの詳細については、公式ドキュメントをご覧ください:
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
**[docs.xrift.net](https://docs.xrift.net)**
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
import { XRiftProvider, useXRift } from '@xrift/world-components'
|
|
30
|
+
## 関連リンク
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<XRiftProvider baseUrl="https://assets.xrift.net/users/xxx/worlds/yyy/hash123/">
|
|
37
|
-
<MyWorld />
|
|
38
|
-
</XRiftProvider>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ワールド内でbaseUrlを取得
|
|
43
|
-
function MyWorld() {
|
|
44
|
-
const { baseUrl } = useXRift()
|
|
45
|
-
const gltf = useGLTF(baseUrl + 'assets/model.glb')
|
|
46
|
-
|
|
47
|
-
return <primitive object={gltf.scene} />
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### useInstanceState フック
|
|
52
|
-
|
|
53
|
-
インスタンス全体で同期される状態を管理するフックです。React の `useState` と同じAPIを提供します。
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
import { useInstanceState } from '@xrift/world-components'
|
|
57
|
-
|
|
58
|
-
function MyWorld() {
|
|
59
|
-
// インスタンス全体で同期される状態
|
|
60
|
-
const [buttonState, setButtonState] = useInstanceState('button-1', { enabled: false })
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<Interactable
|
|
64
|
-
id="button-1"
|
|
65
|
-
onInteract={() => {
|
|
66
|
-
// 状態を更新(全てのクライアントで同期される)
|
|
67
|
-
setButtonState({ enabled: !buttonState.enabled })
|
|
68
|
-
}}
|
|
69
|
-
>
|
|
70
|
-
<mesh>
|
|
71
|
-
<meshStandardMaterial color={buttonState.enabled ? 'green' : 'red'} />
|
|
72
|
-
</mesh>
|
|
73
|
-
</Interactable>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
#### 使用方法
|
|
79
|
-
|
|
80
|
-
```tsx
|
|
81
|
-
const [state, setState] = useInstanceState<T>(stateId, initialState)
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
- `stateId`: 状態の一意識別子(インスタンス内で一意である必要があります)
|
|
85
|
-
- `initialState`: 初期状態
|
|
86
|
-
- `setState`: 状態を更新する関数(直接値 or 関数型アップデートをサポート)
|
|
87
|
-
|
|
88
|
-
#### 関数型アップデート
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
// 直接値を設定
|
|
92
|
-
setState({ enabled: true })
|
|
93
|
-
|
|
94
|
-
// 前の状態を基に更新
|
|
95
|
-
setState(prev => ({ enabled: !prev.enabled }))
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
#### 注意事項
|
|
99
|
-
|
|
100
|
-
- Context未設定時はローカル `useState` として動作します
|
|
101
|
-
- プラットフォーム側(xrift-frontend)がWebSocket実装を注入することで、インスタンス全体での同期が有効になります
|
|
102
|
-
- 状態はシリアライズ可能な値(JSON)である必要があります
|
|
103
|
-
|
|
104
|
-
### Interactable コンポーネント
|
|
105
|
-
|
|
106
|
-
3Dオブジェクトをインタラクション可能にするラッパーコンポーネントです。
|
|
107
|
-
|
|
108
|
-
```tsx
|
|
109
|
-
import { Interactable } from '@xrift/world-components'
|
|
110
|
-
|
|
111
|
-
function MyWorld() {
|
|
112
|
-
const handleButtonClick = (id: string) => {
|
|
113
|
-
console.log(`${id} がクリックされました!`)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<Interactable
|
|
118
|
-
id="my-button"
|
|
119
|
-
onInteract={handleButtonClick}
|
|
120
|
-
interactionText="ボタンを押す"
|
|
121
|
-
>
|
|
122
|
-
<mesh position={[0, 1, -3]}>
|
|
123
|
-
<boxGeometry args={[1, 1, 1]} />
|
|
124
|
-
<meshStandardMaterial color="blue" />
|
|
125
|
-
</mesh>
|
|
126
|
-
</Interactable>
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
#### Props
|
|
132
|
-
|
|
133
|
-
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|
|
134
|
-
|-----------|-----|------|-----------|------|
|
|
135
|
-
| `id` | `string` | ✓ | - | オブジェクトの一意なID |
|
|
136
|
-
| `onInteract` | `(id: string) => void` | ✓ | - | インタラクション時のコールバック |
|
|
137
|
-
| `interactionText` | `string` | - | `"クリックする"` | インタラクション時に表示するテキスト |
|
|
138
|
-
| `enabled` | `boolean` | - | `true` | インタラクション可能かどうか |
|
|
139
|
-
| `children` | `ReactNode` | ✓ | - | 3Dオブジェクト |
|
|
140
|
-
|
|
141
|
-
### Mirror コンポーネント
|
|
142
|
-
|
|
143
|
-
リアルタイムで周囲を反射する鏡のコンポーネントです。
|
|
144
|
-
|
|
145
|
-
```tsx
|
|
146
|
-
import { Mirror } from '@xrift/world-components'
|
|
147
|
-
|
|
148
|
-
function MyWorld() {
|
|
149
|
-
return (
|
|
150
|
-
<>
|
|
151
|
-
{/* 基本的な使い方 */}
|
|
152
|
-
<Mirror position={[0, 2.5, -5]} size={[4, 3]} />
|
|
153
|
-
|
|
154
|
-
{/* 高解像度の鏡 */}
|
|
155
|
-
<Mirror
|
|
156
|
-
position={[5, 2.5, -5]}
|
|
157
|
-
size={[3, 3]}
|
|
158
|
-
textureResolution={1024}
|
|
159
|
-
/>
|
|
160
|
-
|
|
161
|
-
{/* 金色の鏡 */}
|
|
162
|
-
<Mirror
|
|
163
|
-
position={[-5, 2.5, -5]}
|
|
164
|
-
size={[2, 4]}
|
|
165
|
-
color={0xffd700}
|
|
166
|
-
/>
|
|
167
|
-
</>
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
#### Props
|
|
173
|
-
|
|
174
|
-
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|
|
175
|
-
|-----------|-----|------|-----------|------|
|
|
176
|
-
| `position` | `[number, number, number]` | - | `[0, 2.5, -9]` | 鏡の位置 |
|
|
177
|
-
| `rotation` | `[number, number, number]` | - | `[0, 0, 0]` | 鏡の回転 |
|
|
178
|
-
| `size` | `[number, number]` | - | `[8, 5]` | 鏡のサイズ [幅, 高さ] |
|
|
179
|
-
| `color` | `number` | - | `0xcccccc` | 反射の色(16進数カラー) |
|
|
180
|
-
| `textureResolution` | `number` | - | `512` | 反射テクスチャの解像度。sizeの比率に応じて自動調整されます |
|
|
181
|
-
|
|
182
|
-
#### 注意事項
|
|
183
|
-
|
|
184
|
-
- 物理コライダーは含まれていません。必要な場合は`@react-three/rapier`などを使って別途追加してください
|
|
185
|
-
- Meta Quest(Android Chrome)での互換性のため、マルチサンプリングは無効化されています
|
|
186
|
-
|
|
187
|
-
### VideoScreen コンポーネント
|
|
188
|
-
|
|
189
|
-
ワールド内で動画を再生できるスクリーンコンポーネントです。`useInstanceState`を使用してインスタンス内の全ユーザーで再生状態が同期されます。
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
import { VideoScreen } from '@xrift/world-components'
|
|
193
|
-
|
|
194
|
-
function MyWorld() {
|
|
195
|
-
return (
|
|
196
|
-
<>
|
|
197
|
-
{/* メインスクリーン */}
|
|
198
|
-
<VideoScreen
|
|
199
|
-
id="main-screen"
|
|
200
|
-
position={[0, 2, -5]}
|
|
201
|
-
scale={[16/9 * 3, 3]}
|
|
202
|
-
url="https://example.com/video.mp4"
|
|
203
|
-
/>
|
|
204
|
-
|
|
205
|
-
{/* サブスクリーン */}
|
|
206
|
-
<VideoScreen
|
|
207
|
-
id="sub-screen"
|
|
208
|
-
position={[5, 1.5, -3]}
|
|
209
|
-
scale={[16/9 * 1.5, 1.5]}
|
|
210
|
-
/>
|
|
211
|
-
</>
|
|
212
|
-
)
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
#### Props
|
|
217
|
-
|
|
218
|
-
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|
|
219
|
-
|-----------|-----|------|-----------|------|
|
|
220
|
-
| `id` | `string` | ✓ | - | スクリーンの一意なID(インスタンス内で一意である必要があります) |
|
|
221
|
-
| `position` | `[number, number, number]` | - | `[0, 2, -5]` | スクリーンの位置 |
|
|
222
|
-
| `rotation` | `[number, number, number]` | - | `[0, 0, 0]` | スクリーンの回転 |
|
|
223
|
-
| `scale` | `[number, number]` | - | `[16/9 * 3, 3]` | スクリーンのサイズ [幅, 高さ] |
|
|
224
|
-
| `url` | `string` | - | `''` | 動画のURL |
|
|
225
|
-
| `playing` | `boolean` | - | `true` | 再生中かどうか |
|
|
226
|
-
| `currentTime` | `number` | - | `0` | 再生位置(秒) |
|
|
227
|
-
| `sync` | `'global' \| 'local'` | - | `'global'` | 同期モード: `global` = インスタンス全体で同期, `local` = ローカルのみ |
|
|
228
|
-
|
|
229
|
-
#### 同期モード
|
|
230
|
-
|
|
231
|
-
VideoScreenは2つの同期モードをサポートしています:
|
|
232
|
-
|
|
233
|
-
- **`sync="global"`(デフォルト)**: インスタンス内の全ユーザーで動画の状態が同期されます。シアターのメインスクリーンなど、全員で同じ動画を見る場合に使用します。
|
|
234
|
-
- **`sync="local"`**: ローカルのみで動作し、他のユーザーとは同期されません。個人用モニターなど、各ユーザーが別々の動画を見る場合に使用します。
|
|
235
|
-
|
|
236
|
-
```tsx
|
|
237
|
-
// グローバル同期(デフォルト)
|
|
238
|
-
<VideoScreen
|
|
239
|
-
id="theater-screen"
|
|
240
|
-
url="https://example.com/movie.mp4"
|
|
241
|
-
sync="global"
|
|
242
|
-
/>
|
|
243
|
-
|
|
244
|
-
// ローカルのみ
|
|
245
|
-
<VideoScreen
|
|
246
|
-
id="personal-monitor"
|
|
247
|
-
url="https://example.com/video.mp4"
|
|
248
|
-
sync="local"
|
|
249
|
-
/>
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
#### 状態管理
|
|
253
|
-
|
|
254
|
-
VideoScreenは`useInstanceState`を使用して以下の状態を管理します:
|
|
255
|
-
|
|
256
|
-
```tsx
|
|
257
|
-
interface VideoState {
|
|
258
|
-
url: string // 動画のURL
|
|
259
|
-
isPlaying: boolean // 再生中かどうか
|
|
260
|
-
currentTime: number // 現在の再生位置(秒)
|
|
261
|
-
serverTime: number // サーバータイム(VRChat方式の同期用)
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
#### 再生状態の制御(方法1: propsで制御)
|
|
266
|
-
|
|
267
|
-
VideoScreenはpropsで直接制御できます:
|
|
268
|
-
|
|
269
|
-
```tsx
|
|
270
|
-
import { VideoScreen, Interactable } from '@xrift/world-components'
|
|
271
|
-
import { useState } from 'react'
|
|
272
|
-
|
|
273
|
-
function MyWorld() {
|
|
274
|
-
const [playing, setPlaying] = useState(true)
|
|
275
|
-
const [currentTime, setCurrentTime] = useState(0)
|
|
276
|
-
|
|
277
|
-
return (
|
|
278
|
-
<>
|
|
279
|
-
<VideoScreen
|
|
280
|
-
id="main-screen"
|
|
281
|
-
position={[0, 2, -5]}
|
|
282
|
-
scale={[16/9 * 3, 3]}
|
|
283
|
-
url="https://example.com/video.mp4"
|
|
284
|
-
playing={playing}
|
|
285
|
-
currentTime={currentTime}
|
|
286
|
-
/>
|
|
287
|
-
|
|
288
|
-
<Interactable
|
|
289
|
-
id="toggle-button"
|
|
290
|
-
onInteract={() => setPlaying(!playing)}
|
|
291
|
-
>
|
|
292
|
-
<mesh>
|
|
293
|
-
<boxGeometry />
|
|
294
|
-
<meshStandardMaterial color={playing ? 'red' : 'green'} />
|
|
295
|
-
</mesh>
|
|
296
|
-
</Interactable>
|
|
297
|
-
|
|
298
|
-
<Interactable
|
|
299
|
-
id="seek-button"
|
|
300
|
-
onInteract={() => setCurrentTime(currentTime + 10)}
|
|
301
|
-
>
|
|
302
|
-
<mesh>
|
|
303
|
-
<boxGeometry />
|
|
304
|
-
<meshStandardMaterial color="cyan" />
|
|
305
|
-
</mesh>
|
|
306
|
-
</Interactable>
|
|
307
|
-
</>
|
|
308
|
-
)
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
#### 再生状態の制御(方法2: useInstanceStateで制御)
|
|
313
|
-
|
|
314
|
-
VideoScreenの状態は`useInstanceState`を使って外部から制御できます:
|
|
315
|
-
|
|
316
|
-
```tsx
|
|
317
|
-
import { VideoScreen, useInstanceState } from '@xrift/world-components'
|
|
318
|
-
|
|
319
|
-
function MyWorld() {
|
|
320
|
-
const [videoState, setVideoState] = useInstanceState('video-main-screen', {
|
|
321
|
-
url: '',
|
|
322
|
-
isPlaying: false,
|
|
323
|
-
currentTime: 0,
|
|
324
|
-
serverTime: Date.now(),
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
const playVideo = (url: string) => {
|
|
328
|
-
setVideoState({
|
|
329
|
-
url,
|
|
330
|
-
isPlaying: true,
|
|
331
|
-
currentTime: 0,
|
|
332
|
-
serverTime: Date.now(),
|
|
333
|
-
})
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return (
|
|
337
|
-
<>
|
|
338
|
-
<VideoScreen id="main-screen" />
|
|
339
|
-
|
|
340
|
-
<Interactable
|
|
341
|
-
id="play-button"
|
|
342
|
-
onInteract={() => playVideo('https://example.com/video.mp4')}
|
|
343
|
-
>
|
|
344
|
-
<mesh>
|
|
345
|
-
<boxGeometry />
|
|
346
|
-
<meshStandardMaterial color="green" />
|
|
347
|
-
</mesh>
|
|
348
|
-
</Interactable>
|
|
349
|
-
</>
|
|
350
|
-
)
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
#### 注意事項
|
|
355
|
-
|
|
356
|
-
- 動画URLは直接アクセス可能なMP4などの動画ファイルである必要があります
|
|
357
|
-
- CORSの制約により、クロスオリジンの動画を再生する場合は、動画サーバー側で適切なCORSヘッダーの設定が必要です
|
|
358
|
-
- 再生位置の同期はVRChat方式(サーバータイム基準)を採用しており、約0.5秒の誤差で自動補正されます
|
|
359
|
-
- ブラウザのautoplay policyのため、動画は初期状態でミュートされています
|
|
360
|
-
- 物理コライダーは含まれていません。必要な場合は別途追加してください
|
|
361
|
-
|
|
362
|
-
### ScreenShareDisplay コンポーネント
|
|
363
|
-
|
|
364
|
-
画面共有の映像を3D空間内にスクリーンとして表示するコンポーネントです。`ScreenShareContext` から映像と状態を取得します。
|
|
365
|
-
|
|
366
|
-
```tsx
|
|
367
|
-
import { ScreenShareDisplay } from '@xrift/world-components'
|
|
368
|
-
|
|
369
|
-
function MyWorld() {
|
|
370
|
-
return (
|
|
371
|
-
<>
|
|
372
|
-
{/* 基本的な使い方 */}
|
|
373
|
-
<ScreenShareDisplay id="screen-1" />
|
|
374
|
-
|
|
375
|
-
{/* 位置とサイズを指定 */}
|
|
376
|
-
<ScreenShareDisplay
|
|
377
|
-
id="screen-2"
|
|
378
|
-
position={[5, 2, -3]}
|
|
379
|
-
scale={[3, 3 * (9/16)]}
|
|
380
|
-
/>
|
|
381
|
-
</>
|
|
382
|
-
)
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
#### Props
|
|
387
|
-
|
|
388
|
-
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|
|
389
|
-
|-----------|-----|------|-----------|------|
|
|
390
|
-
| `id` | `string` | ✓ | - | スクリーンの一意なID |
|
|
391
|
-
| `position` | `[number, number, number]` | - | `[0, 2, -5]` | スクリーンの位置 |
|
|
392
|
-
| `rotation` | `[number, number, number]` | - | `[0, 0, 0]` | スクリーンの回転 |
|
|
393
|
-
| `scale` | `[number, number]` | - | `[4, 4 * (9/16)]` | スクリーンのサイズ [幅, 高さ] |
|
|
394
|
-
|
|
395
|
-
#### ScreenShareContext
|
|
396
|
-
|
|
397
|
-
画面共有の状態は `ScreenShareContext` を通じて提供されます。`XRiftProvider` に `screenShareImplementation` を渡すことで設定します。
|
|
398
|
-
|
|
399
|
-
```tsx
|
|
400
|
-
import { XRiftProvider, ScreenShareDisplay } from '@xrift/world-components'
|
|
401
|
-
|
|
402
|
-
function App() {
|
|
403
|
-
const screenShareImpl = {
|
|
404
|
-
videoElement: myVideoElement, // HTMLVideoElement
|
|
405
|
-
isSharing: false,
|
|
406
|
-
startScreenShare: () => { /* 共有開始処理 */ },
|
|
407
|
-
stopScreenShare: () => { /* 共有停止処理 */ },
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return (
|
|
411
|
-
<XRiftProvider
|
|
412
|
-
baseUrl="https://..."
|
|
413
|
-
screenShareImplementation={screenShareImpl}
|
|
414
|
-
>
|
|
415
|
-
<MyWorld />
|
|
416
|
-
</XRiftProvider>
|
|
417
|
-
)
|
|
418
|
-
}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
#### useScreenShareContext フック
|
|
422
|
-
|
|
423
|
-
コンポーネント内で画面共有の状態を取得するフックです。
|
|
424
|
-
|
|
425
|
-
```tsx
|
|
426
|
-
import { useScreenShareContext } from '@xrift/world-components'
|
|
427
|
-
|
|
428
|
-
function MyComponent() {
|
|
429
|
-
const { videoElement, isSharing, startScreenShare, stopScreenShare } = useScreenShareContext()
|
|
430
|
-
|
|
431
|
-
return (
|
|
432
|
-
<button onClick={isSharing ? stopScreenShare : startScreenShare}>
|
|
433
|
-
{isSharing ? '共有を停止' : '共有を開始'}
|
|
434
|
-
</button>
|
|
435
|
-
)
|
|
436
|
-
}
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
#### 注意事項
|
|
440
|
-
|
|
441
|
-
- `XRiftProvider` に `screenShareImplementation` を渡すことが必須です
|
|
442
|
-
- 映像がない場合は「クリックして画面共有」というガイドテキストが表示されます
|
|
443
|
-
- スクリーンをクリックすると `startScreenShare` / `stopScreenShare` が呼び出されます
|
|
444
|
-
|
|
445
|
-
### Skybox コンポーネント
|
|
446
|
-
|
|
447
|
-
画像を使わずにグラデーションで空を表現するシンプルなskyboxコンポーネントです。
|
|
448
|
-
|
|
449
|
-
```tsx
|
|
450
|
-
import { Skybox } from '@xrift/world-components'
|
|
451
|
-
|
|
452
|
-
function MyWorld() {
|
|
453
|
-
return (
|
|
454
|
-
<>
|
|
455
|
-
{/* 基本的な使い方(デフォルトは空色→白) */}
|
|
456
|
-
<Skybox />
|
|
457
|
-
|
|
458
|
-
{/* 夕焼け風 */}
|
|
459
|
-
<Skybox topColor={0xff6b35} bottomColor={0xffd93d} />
|
|
460
|
-
|
|
461
|
-
{/* 夜空風 */}
|
|
462
|
-
<Skybox topColor={0x000033} bottomColor={0x000011} />
|
|
463
|
-
</>
|
|
464
|
-
)
|
|
465
|
-
}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
#### Props
|
|
469
|
-
|
|
470
|
-
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|
|
471
|
-
|-----------|-----|------|-----------|------|
|
|
472
|
-
| `topColor` | `number` | - | `0x87ceeb` | 上部の色(16進数カラー) |
|
|
473
|
-
| `bottomColor` | `number` | - | `0xffffff` | 下部の色(16進数カラー) |
|
|
474
|
-
| `offset` | `number` | - | `0` | グラデーションの開始位置 |
|
|
475
|
-
| `exponent` | `number` | - | `1` | グラデーションの範囲 |
|
|
476
|
-
|
|
477
|
-
#### 注意事項
|
|
478
|
-
|
|
479
|
-
- シェーダーを使用した軽量な実装です
|
|
480
|
-
- シーンの背景色も自動的に設定されます
|
|
481
|
-
|
|
482
|
-
### SpawnPoint コンポーネント
|
|
483
|
-
|
|
484
|
-
ワールド内のプレイヤーのスポーン(出現)地点を指定するコンポーネントです。開発時には位置と向きを視覚的に確認できるヘルパーが表示されます。
|
|
485
|
-
|
|
486
|
-
```tsx
|
|
487
|
-
import { SpawnPoint } from '@xrift/world-components'
|
|
488
|
-
|
|
489
|
-
function MyWorld() {
|
|
490
|
-
return (
|
|
491
|
-
<>
|
|
492
|
-
{/* 原点にスポーン、正面向き */}
|
|
493
|
-
<SpawnPoint />
|
|
494
|
-
|
|
495
|
-
{/* 位置と向きを指定 */}
|
|
496
|
-
<SpawnPoint position={[0, 0, 5]} yaw={180} />
|
|
497
|
-
</>
|
|
498
|
-
)
|
|
499
|
-
}
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
#### Props
|
|
503
|
-
|
|
504
|
-
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|
|
505
|
-
|-----------|-----|------|-----------|------|
|
|
506
|
-
| `position` | `[number, number, number]` | - | `[0, 0, 0]` | スポーン位置 |
|
|
507
|
-
| `yaw` | `number` | - | `0` | スポーン時の向き(度数法 0-360) |
|
|
508
|
-
|
|
509
|
-
#### 開発時ヘルパー
|
|
510
|
-
|
|
511
|
-
開発環境(`import.meta.env.DEV === true`)では、スポーン位置と向きを視覚的に確認できるヘルパーが表示されます:
|
|
512
|
-
|
|
513
|
-
- 半透明の円柱(下から上にかけて透明度が増すグラデーション)
|
|
514
|
-
- 黄緑色の矢印でスポーン方向を表示
|
|
515
|
-
|
|
516
|
-
本番ビルドではヘルパーは表示されず、コードも含まれません。
|
|
517
|
-
|
|
518
|
-
#### useSpawnPoint フック
|
|
519
|
-
|
|
520
|
-
プラットフォーム側で現在設定されているスポーン地点を取得するフックです。
|
|
521
|
-
|
|
522
|
-
```tsx
|
|
523
|
-
import { useSpawnPoint } from '@xrift/world-components'
|
|
524
|
-
|
|
525
|
-
function PlayerSpawner() {
|
|
526
|
-
const spawnPoint = useSpawnPoint()
|
|
527
|
-
|
|
528
|
-
useEffect(() => {
|
|
529
|
-
if (spawnPoint) {
|
|
530
|
-
player.position.set(...spawnPoint.position)
|
|
531
|
-
player.rotation.y = THREE.MathUtils.degToRad(spawnPoint.yaw)
|
|
532
|
-
}
|
|
533
|
-
}, [spawnPoint])
|
|
534
|
-
|
|
535
|
-
return null
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
#### 注意事項
|
|
540
|
-
|
|
541
|
-
- 1つのワールドに複数のSpawnPointがある場合、最後に設定されたものが有効になります
|
|
542
|
-
- `useSpawnPoint`は主にプラットフォーム側(xrift-frontend)での使用を想定しています
|
|
32
|
+
- [Xrift 公式サイト](https://xrift.net/)
|
|
33
|
+
- [xrift-world-template](https://github.com/WebXR-JP/xrift-world-template) - ワールド開発用テンプレート
|
|
34
|
+
- [xrift-cli](https://github.com/WebXR-JP/xrift-cli) - Xrift CLI ツール
|
|
543
35
|
|
|
544
36
|
## 開発
|
|
545
37
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VideoScreen/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAc,MAAM,SAAS,CAAA;AAEtD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VideoScreen/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAc,MAAM,SAAS,CAAA;AAEtD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAyG3D;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CA8BlD"}
|
|
@@ -6,7 +6,7 @@ import { useInstanceState } from '../../hooks/useInstanceState';
|
|
|
6
6
|
* 動画を表示する内部コンポーネント
|
|
7
7
|
* useVideoTextureを使用するためSuspense内で使用する必要がある
|
|
8
8
|
*/
|
|
9
|
-
function VideoScreenInner({ id, position = [0, 2, -5], rotation = [0, 0, 0], scale = [16 / 9 * 3, 3], url = '', playing = true, currentTime = 0, sync = 'global', }) {
|
|
9
|
+
function VideoScreenInner({ id, position = [0, 2, -5], rotation = [0, 0, 0], scale = [16 / 9 * 3, 3], url = '', playing = true, currentTime = 0, sync = 'global', muted = false, }) {
|
|
10
10
|
// グローバル同期用の状態
|
|
11
11
|
const [globalState, setGlobalState] = useInstanceState(`video-${id}`, {
|
|
12
12
|
url: url,
|
|
@@ -40,7 +40,7 @@ function VideoScreenInner({ id, position = [0, 2, -5], rotation = [0, 0, 0], sca
|
|
|
40
40
|
}, [url, playing, currentTime, videoState, setVideoState]);
|
|
41
41
|
// useVideoTextureで動画テクスチャを取得
|
|
42
42
|
const texture = useVideoTexture(url || '', {
|
|
43
|
-
muted
|
|
43
|
+
muted,
|
|
44
44
|
loop: true,
|
|
45
45
|
start: playing,
|
|
46
46
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/VideoScreen/index.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAK/D;;;GAGG;AACH,SAAS,gBAAgB,CAAC,EACxB,EAAE,EACF,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EACrB,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACpB,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EACvB,GAAG,GAAG,EAAE,EACR,OAAO,GAAG,IAAI,EACd,WAAW,GAAG,CAAC,EACf,IAAI,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/VideoScreen/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,8 @@ export interface VideoScreenProps {
|
|
|
15
15
|
currentTime?: number;
|
|
16
16
|
/** 同期モード: "global" = インスタンス全体で同期, "local" = ローカルのみ(デフォルト: "global") */
|
|
17
17
|
sync?: 'global' | 'local';
|
|
18
|
+
/** ミュート状態(デフォルト: false)。ブラウザの自動再生ポリシーによりユーザー操作前は音声付き自動再生がブロックされる場合がある */
|
|
19
|
+
muted?: boolean;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* VideoScreenの状態
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/VideoScreen/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,eAAe;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,eAAe;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,wBAAwB;IACxB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxB,aAAa;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/VideoScreen/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,eAAe;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,eAAe;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,wBAAwB;IACxB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxB,aAAa;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;IACzB,yEAAyE;IACzE,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,aAAa;IACb,GAAG,EAAE,MAAM,CAAA;IACX,cAAc;IACd,SAAS,EAAE,OAAO,CAAA;IAClB,iBAAiB;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAA;CACnB"}
|