Coding
PromptBeginner5 minmarkdown
Markdown Converter
Agent skill for markdown-converter
7
このドキュメントは、AI(Claude、ChatGPT、Cursor等)がXRiftワールドを作成・修正する際のガイドです。
Sign in to like and favorite skills
このドキュメントは、AI(Claude、ChatGPT、Cursor等)がXRiftワールドを作成・修正する際のガイドです。
useXRift() の baseUrl を使用public/ ディレクトリに配置baseUrl は末尾に / を含むため、${baseUrl}path で結合(${baseUrl}/path は NG)// ✅ 正しい const { baseUrl } = useXRift() const model = useGLTF(`${baseUrl}models/robot.glb`) // ❌ 間違い const model = useGLTF('/models/robot.glb') // 絶対パス NG const model = useGLTF(`${baseUrl}/models/robot.glb`) // 余分な / NG
| フック | 用途 | 戻り値 |
|---|---|---|
| アセットURL取得 | |
| 全ユーザー間で状態同期 | |
| ユーザー情報・位置取得 | |
| スポーン地点取得 | |
| 画面共有状態 | |
| コンポーネント | 用途 | 主要Props |
|---|---|---|
| クリック可能オブジェクト | (必須), (必須), , |
| プレイヤー出現地点 | , (0-360度) |
| 反射面 | , , , , |
| UI付き動画再生 | (必須), (必須), , , , , , |
| 画面共有表示 | (必須), , , |
VideoPlayer: UIコントロール付き(再生/一時停止、進捗バー、音量調整、URL入力)、VR対応
Three.js のカメラとオブジェクトは 32 のレイヤー(0-31)を持ち、カメラは有効化されたレイヤーに属するオブジェクトのみをレンダリングする。 XRift では以下のレイヤーを使用する。
| 定数名 | 値 | 用途 |
|---|---|---|
| 0 | デフォルトレイヤー(すべてのオブジェクトが初期状態で属する) |
| 9 | 一人称視点のみ表示(VRM のヘッドレスコピー用) |
| 10 | 三人称視点のみ表示(他プレイヤーやミラー用) |
| 11 | インタラクト可能オブジェクト(Raycast 検出用) |
// インポート方法 import { LAYERS } from '@xrift/world-components'
仕組み:
Interactable コンポーネントは子オブジェクトに自動で LAYERS.INTERACTABLE を設定するLAYERS.INTERACTABLE レイヤーで Raycast を行いインタラクションを検出するdev.tsx)でインタラクションをテストする場合、Raycaster のレイヤーを LAYERS.INTERACTABLE に設定する必要がある// Raycaster でインタラクト可能オブジェクトのみを検出 const raycaster = new Raycaster() raycaster.layers.set(LAYERS.INTERACTABLE) // レイヤー11のオブジェクトのみヒット
import { useXRift } from '@xrift/world-components' import { useGLTF } from '@react-three/drei' import { RigidBody } from '@react-three/rapier' export const MyModel = () => { const { baseUrl } = useXRift() const { scene } = useGLTF(`${baseUrl}models/model.glb`) return ( <RigidBody type="fixed"> <primitive object={scene} castShadow receiveShadow /> </RigidBody> ) }
import { useXRift } from '@xrift/world-components' import { useTexture } from '@react-three/drei' export const TexturedMesh = () => { const { baseUrl } = useXRift() const texture = useTexture(`${baseUrl}textures/albedo.png`) return ( <mesh> <boxGeometry args={[1, 1, 1]} /> <meshStandardMaterial map={texture} /> </mesh> ) }
import { useXRift } from '@xrift/world-components' import { useTexture } from '@react-three/drei' export const PBRMaterial = () => { const { baseUrl } = useXRift() const [albedo, normal, roughness] = useTexture([ `${baseUrl}textures/albedo.png`, `${baseUrl}textures/normal.png`, `${baseUrl}textures/roughness.png`, ]) return ( <meshStandardMaterial map={albedo} normalMap={normal} roughnessMap={roughness} /> ) }
import { useXRift } from '@xrift/world-components' import { useTexture } from '@react-three/drei' import { BackSide } from 'three' export const Skybox = ({ radius = 500 }) => { const { baseUrl } = useXRift() const texture = useTexture(`${baseUrl}skybox.jpg`) return ( <mesh> <sphereGeometry args={[radius, 60, 40]} /> <meshBasicMaterial map={texture} side={BackSide} /> </mesh> ) }
import { Interactable, useInstanceState } from '@xrift/world-components' export const InteractiveButton = ({ id }: { id: string }) => { // useInstanceState: 全ユーザー間で同期される状態 const [clickCount, setClickCount] = useInstanceState(`${id}-count`, 0) return ( <Interactable id={id} onInteract={() => setClickCount((prev) => prev + 1)} interactionText={`クリック回数: ${clickCount}`} > <mesh> <boxGeometry args={[1, 1, 0.2]} /> <meshStandardMaterial color={clickCount > 0 ? 'green' : 'gray'} /> </mesh> </Interactable> ) }
import { useRef } from 'react' import { useFrame } from '@react-three/fiber' import type { Mesh } from 'three' export const RotatingCube = ({ speed = 1 }) => { const meshRef = useRef<Mesh>(null) useFrame((_, delta) => { if (meshRef.current) { meshRef.current.rotation.y += delta * speed } }) return ( <mesh ref={meshRef}> <boxGeometry args={[1, 1, 1]} /> <meshStandardMaterial color="hotpink" /> </mesh> ) }
import { useFrame } from '@react-three/fiber' import { useUsers } from '@xrift/world-components' export const UserTracker = () => { const { remoteUsers, getMovement, getLocalMovement } = useUsers() useFrame(() => { // 自分の位置 const myMovement = getLocalMovement() console.log('My position:', myMovement.position) // 他ユーザーの位置 remoteUsers.forEach((user) => { const movement = getMovement(user.socketId) if (movement) { console.log(`${user.displayName}:`, movement.position) } }) }) return null }
interface User { id: string // 認証ユーザーID socketId: string // ソケット接続ID displayName: string // 表示名 avatarUrl: string | null isGuest: boolean }
interface PlayerMovement { position: { x: number; y: number; z: number } direction: { x: number; z: number } // 移動方向(正規化) horizontalSpeed: number // XZ平面速度 verticalSpeed: number // Y軸速度 rotation: { yaw: number; pitch: number } isGrounded: boolean isJumping: boolean isInVR?: boolean vrTracking?: VRTrackingData // VRモード時のみ }
interface VRTrackingData { head: { yaw: number; pitch: number } leftHand: { position: Position3D; rotation: Rotation3D } rightHand: { position: Position3D; rotation: Rotation3D } hipsPositionDelta: Position3D movementDirection: 'forward' | 'backward' | 'left' | 'right' | 'idle' isHandTracking?: boolean }
xrift-world-template/ ├── public/ # アセットファイル(GLB, テクスチャ, 画像) │ ├── models/ # 3Dモデル (.glb, .gltf) │ ├── textures/ # テクスチャ画像 │ └── *.jpg, *.png # Skybox等 ├── src/ │ ├── components/ # 3Dコンポーネント │ ├── World.tsx # メインワールドコンポーネント │ ├── dev.tsx # 開発用エントリーポイント │ ├── index.tsx # 本番用エクスポート │ └── constants.ts # 定数定義 ├── .triplex/ # Triplex(3Dエディタ)設定 ├── xrift.json # XRift CLI設定 ├── vite.config.ts # ビルド設定(Module Federation) └── package.json
| 項目 | 型 | デフォルト値 | 説明 |
|---|---|---|---|
| number | 9.81 | 重力の強さ(正の値、地球=9.81、月=1.62、木星=24.79) |
| boolean | true | 無限ジャンプを許可するか |
{ "physics": { "gravity": 9.81, "allowInfiniteJump": true } }
設定例:
"allowInfiniteJump": false で落下のリスクを追加"gravity": 1.62(月の重力)でふわふわした動き"gravity": 24.79(木星の重力)で重厚な動き# 開発 npm run dev # 開発サーバー起動 (http://localhost:5173) npm run build # 本番ビルド npm run typecheck # 型チェック # XRift CLI xrift login # 認証 xrift create # 新規プロジェクト作成 xrift upload world # ワールドをアップロード xrift whoami # ログインユーザー確認 xrift logout # ログアウト
npm run dev で開発サーバーを起動すると、一人称視点でワールドを操作・テストできる。
| 操作 | キー |
|---|---|
| 視点操作 | 画面クリックでマウスロック → マウス移動 |
| 移動 | W / A / S / D |
| 上昇 / 下降 | E・Space / Q |
| インタラクト | 照準を合わせてクリック |
| マウスロック解除 | ESC |
Interactable コンポーネントのクリック動作も開発環境で確認可能(画面中央の Raycaster が LAYERS.INTERACTABLE レイヤーを検出)。
src/dev.tsx は開発専用のエントリーポイント。本番ビルドには含まれない。
注意: 本番環境では
XRiftProvider は不要(フロントエンド側が自動でラップ)
react / react-dom ^19.0.0three ^0.182.0@react-three/fiber ^9.3.0@react-three/drei ^10.7.3@react-three/rapier ^2.1.0@xrift/world-components - XRiftのフック・コンポーネント原因:
XRiftProvider でラップされていない
解決方法:
src/dev.tsx で XRiftProvider を使用しているか確認.triplex/provider.tsx を確認原因:
baseUrl を使用していない、またはパス結合が間違っている
解決方法:
// ✅ 正しい const { baseUrl } = useXRift() const model = useGLTF(`${baseUrl}models/robot.glb`) // ❌ 間違い const model = useGLTF('/models/robot.glb') const model = useGLTF(`${baseUrl}/models/robot.glb`)
原因:
Physics コンポーネントでラップされていない、または RigidBody がない
解決方法:
<Physics> <RigidBody type="fixed"> {/* または "dynamic" */} <mesh>...</mesh> </RigidBody> </Physics>
src/components/Duck/index.tsxsrc/components/Skybox/index.tsxsrc/components/RotatingObject/index.tsxsrc/components/InteractableButton/index.tsxsrc/components/RemoteUserHUDs/index.tsxsrc/components/SpawnPoint/index.tsxsrc/World.tsx