テーマ
API リファレンス
@uzuhq/code-sdk パッケージの公開 API 一覧です。
パッケージ情報
| 項目 | 値 |
|---|---|
| パッケージ名 | @uzuhq/code-sdk |
| モジュール形式 | ES Modules |
| 依存関係 | なし (devDependencies: typescript ^5.7.0 のみ) |
関数
init(): void
SDK を初期化し、Flutter ホスト / 親ウィンドウに準備完了を通知する。
typescript
import { init } from '@uzuhq/code-sdk';
init();動作:
- Flutter WebView / iframe 環境に応じてメッセージ受信口を登録
{ type: '__ps_ready' }メッセージをホストへ送信- URL に
?roomId=xxxがあれば Relay WebSocket に自動接続
TIP
run() / sync() を使う場合は内部で init() を呼ぶため、直接呼ぶ必要はありません。 Relay ベース (onRoom()) のゲームでのみ、最初に呼び出してください。
run<S>(config: GameConfig<S>): void
Host-authoritative ゲームループを実行する。
typescript
import { run } from '@uzuhq/code-sdk';
run({
logic,
onState(state, myPlayerId) {
currentState = state;
myId = myPlayerId;
},
inputs(sendAction) {
canvas.addEventListener('click', () => {
sendAction('choose', { hand: 'rock' });
});
},
events: {
sound(data) {
new Audio(`/sounds/${data.sound}.mp3`).play().catch(() => {});
},
},
playerCount: 2,
});モードの自動判定:
- URL に
?roomId=xxxがない → ローカルモード (サーバー不要) - URL に
?roomId=xxx&server=xxxがある → オンラインモード - URL に
?__dev=Nがある → Dev ハーネスモード
sync<S>(config: SyncConfig<S>): void
Server-authoritative な JSON Patch ベースの状態同期を開始する。
typescript
import { sync, SERVER_TIME } from '@uzuhq/code-sdk';
import type { Seat } from '@uzuhq/code-sdk';
sync<GameState>({
playerCount: 2,
initialState(players: Seat[]) {
return { board: createBoard(8), currentPlayer: players[0].id };
},
onState(state, myPlayerId, serverTime) {
currentState = state;
render();
},
inputs(patch, set) {
canvas.addEventListener('click', (e) => {
const { row, col } = getCellFromClick(e);
patch([
{ op: 'replace', path: `/board/${row}/${col}`, value: myPlayerId },
{ op: 'replace', path: '/lastMoveAt', value: SERVER_TIME },
]);
});
},
connection: {
onConnectionStateChange(state) {
console.log('Connection:', state);
},
onPatchFailed(error) {
console.error('Patch failed:', error);
},
},
});モードの自動判定: run() と同じ。
send(message: PlayScreenMessage): void
Flutter ホスト / 親ウィンドウへ任意のメッセージを送信する。
typescript
import { send } from '@uzuhq/code-sdk';
send({ type: 'playSound', sound: 'sounds/clear.mp3' });- Flutter WebView:
window.FlutterHost.postMessage()経由 - iframe:
window.parent.postMessage()経由 - ブラウザ単体:
console.logにフォールバック
on(type: string, handler: MessageHandler): void
Flutter / ホストからのメッセージに対するハンドラを登録する。同一 type に複数登録可能。
typescript
import { on } from '@uzuhq/code-sdk';
import type { PlayersChangedMessage } from '@uzuhq/code-sdk';
on('playersChanged', (msg) => {
const { players } = msg as unknown as PlayersChangedMessage;
for (const [id, state] of Object.entries(players)) {
switch (state.audioStatus) {
case 'speaking':
highlightAvatar(id);
break;
case 'listening':
showNormalAvatar(id);
break;
case 'muted':
showMuteIcon(id);
break;
case 'unstable':
showUnstableBadge(id);
break;
case null:
showNoVoice(id);
break;
}
}
});firstFrameReady(): void
scenario が最初の絵 (splash / loading 画面) を画面に描画したことを Flutter ホストに通知する。Flutter は受信時に splash overlay を非表示にし WebView を可視化する。
呼び出しタイミング: scenario が DOM / canvas に最初の絵を描いた直後。paint commit を保証するため requestAnimationFrame を 1 段挟むのが定石。
呼ばないと splash overlay が出っぱなしになるので、Flutter ホスト環境では必ず呼ぶこと。
typescript
import { init, firstFrameReady } from '@uzuhq/code-sdk';
init();
drawSplash();
requestAnimationFrame(() => firstFrameReady());gameReady(): void
scenario が操作受付可能になったことを Flutter ホストに通知する。Flutter は受信時に入力受付を開始し、TTI (Time To Interactive) 計測を終了する。将来の広告タイマー / leaderboard / analytics の発火点としても使われる予定。
呼び出しタイミング: asset load 完了、ゲーム本体の setup 完了、入力受付可能になった直後。
typescript
import { gameReady } from '@uzuhq/code-sdk';
await loadAssets();
setupGame();
gameReady();YouTube Playables SDK の firstFrameReady() / gameReady() と思想的に整合する。
playSound(sound: string): void
効果音の再生リクエストを送信する。
typescript
import { playSound } from '@uzuhq/code-sdk';
playSound('sounds/clear.mp3');playBgm(sound: string): void / stopBgm(): void
BGM の再生・停止を制御する。
typescript
import { playBgm, stopBgm } from '@uzuhq/code-sdk';
playBgm('bgm/main.mp3'); // ループ再生
stopBgm(); // 停止isHosted
boolean (読み取り専用)。Flutter WebView または iframe 内で動作しているかを示す。
window.FlutterHostが存在 →true(Flutter WebView)window.parent !== window→true(iframe)- それ以外 →
false
型定義
GameConfig<S>
run() に渡す設定オブジェクト。
| キー | 型 | 必須 | 説明 |
|---|---|---|---|
logic | GameLogic<S> | Yes | ゲームロジック定義 |
onState | (state: S, myPlayerId: string) => void | Yes | state 更新時のコールバック |
inputs | (sendAction: (action: string, payload: any) => void) => void | Yes | 入力ハンドラ登録 |
events | Record<string, (data: any) => void> | No | ゲームイベントハンドラ |
playerCount | number | Yes | プレイヤー数 |
GameLogic<S>
ゲームロジック定義。run() で使用する。
| キー | 型 | 必須 | 説明 |
|---|---|---|---|
setup | (seats: Seat[], random: SeededRandom) => S | Yes | 初期 state を生成。seats には kind !== 'player' の席 (spectator / admin) も含まれるため、配役は kind === 'player' だけを対象にする |
actions | Record<string, ActionHandler<S> | ServerOnlyAction<S>> | Yes | アクションハンドラ (state を直接変更)。serverOnly() で wrap すると client 先行実行をスキップしてサーバーだけで実行 |
update | (state: S, ctx: UpdateContext) => void | Yes | 毎 tick 実行 (state を直接変更) |
tickRate | number | No | 秒間 tick 数 (default: 10) |
serverOnly(handler)
actions に渡す handler を「サーバーでだけ実行される」ものとして wrap する。fetch などの副作用付き処理を安全に書く用途。詳しくは パターン 3 → サーバー専用 action を参照。
ts
import { serverOnly } from "@uzuhq/code-sdk";
actions: {
notifyExternal: serverOnly(async (state, payload, playerId) => {
await fetch("https://example.com/notify", { ... });
state.notifiedAt = Date.now();
}),
}handler シグネチャ: (state: S, payload: any, playerId: string, emit: EmitFn, ctx: { tick: number }) => Promise<void> | void
SyncConfig<S>
sync() に渡す設定オブジェクト。
| キー | 型 | 必須 | 説明 |
|---|---|---|---|
initialState | (seats: Seat[]) => S | Yes | 初期 state を生成 |
onState | (state: S, myPlayerId: string, serverTime: number) => void | Yes | state 更新時のコールバック |
inputs | (patch: PatchFn, set: SetFn) => void | Yes | 入力ハンドラ (patch: 複数操作、set: 単一値変更) |
events | Record<string, (data: Record<string, unknown>) => void> | No | ゲームイベントハンドラ |
connection | ConnectionCallbacks | No | 接続状態・エラーコールバック |
playerCount | number | Yes | プレイヤー数 |
Seat
セッション参加者。ゲームの席を占める player のほか、観戦席 (spectator) と進行管理席 (admin、dev ハーネスのテストプレイ用) がある。
typescript
type SeatKind = 'player' | 'spectator' | 'admin';
interface Seat {
id: string;
nickname: string;
iconUrl: string;
/** ホスト(mobile / emulator)から渡される、選択済みキャラクターの ID。未選択時は undefined。 */
characterId?: string;
/** 席種。 */
kind: SeatKind;
}seats の server 側サポートは run() のみ
sync() でも initialState(seats) には kind 付きの席が届くため、client 側で観戦 view を組むことは可能。ただし SyncRoom は roster を保持せず権限制御も行わない (state 全体が全接続に見える sync の信頼モデルに準ずる)。server 側で席種が意味を持つ (spectators 登録・GM 権限ゲート) のは run() / GameRoom だけ。
PlayersChangedMessage
Flutter から送信されるプレイヤー状態更新メッセージ。on('playersChanged', handler) で受信する。
typescript
interface PlayersChangedMessage {
players: Record<string, PlayerVoiceState>;
}PlayerVoiceState
各プレイヤーのリアルタイム状態。
typescript
interface PlayerVoiceState {
audioStatus: 'speaking' | 'listening' | 'muted' | 'unstable' | null;
}| フィールド | 説明 |
|---|---|
audioStatus | 音声状態。音声通話に未接続の場合は null |
audioStatus の値:
| 値 | 説明 | 典型的な UI |
|---|---|---|
speaking | 発話中 | アバターをハイライト、波形表示 |
listening | 音声接続済み・聞いている | 通常表示 |
muted | 音声接続済み・ミュート中 | ミュートアイコン |
unstable | 音声接続に問題がある | 警告バッジ |
null | 音声通話に未接続 | 未接続アイコン |
Operation
JSON Patch 操作。
typescript
interface Operation {
op: 'replace' | 'add' | 'remove';
path: string; // JSON Pointer パス (例: '/players/alice/score')
value?: unknown; // 値 ('remove' の場合は不要)。SERVER_TIME sentinel 使用可
}PatchFn / SetFn
sync() の inputs コールバックで使用する状態変更関数。
typescript
type PatchFn = (ops: Operation[]) => void; // 複数の操作をまとめて送信
type SetFn = (path: string, value: unknown) => void; // 単一パスの replace ショートカットPatchFn の使用例:
typescript
patch([
{ op: 'replace', path: `/board/${row}/${col}`, value: myPlayerId },
{ op: 'replace', path: '/lastMoveAt', value: SERVER_TIME },
]);SetFn の使用例:
typescript
set('/players/myId/choice', 'rock');
// 内部的に patch([{ op: 'replace', path: '/players/myId/choice', value: 'rock' }]) と同等ConnectionState
接続状態を表す文字列リテラル型。
typescript
type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnected';ConnectionCallbacks
接続イベントのコールバック。
typescript
interface ConnectionCallbacks {
onConnectionStateChange?: (state: ConnectionState) => void;
onPatchFailed?: (error: string) => void;
}SeededRandom
seed 付き乱数生成器。GameLogic の setup や update の引数で提供される。
| メソッド | 説明 |
|---|---|
float() | 0.0〜1.0 の浮動小数点 |
int(max) | 0〜max-1 の整数 |
pick(array) | 配列からランダムに 1 つ選択 |
shuffle(array) | 配列をシャッフル (新しい配列を返す) |
SERVER_TIME
サーバー時刻 sentinel 定数 ('__SERVER_TIME__')。
Patch の value に指定すると、サーバー側で Date.now() に自動置換される。クライアント間の時刻差を吸収し、正確なタイムスタンプベースのロジックを実現する。
typescript
import { SERVER_TIME } from '@uzuhq/code-sdk';
set('/meta/phaseStartedAt', SERVER_TIME);
// サーバーで apply 時に Date.now() に置換されるRoom API (Relay 用)
onRoom() コールバックで受け取る Room インスタンスのメソッド。Relay パターンで使用する。
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
myId | string (readonly) | 自分の playerId |
broadcast(msg: Record<string, unknown>): void
自分以外の全メンバーにメッセージを送信する。
typescript
room.broadcast({ type: 'attack', lines: 2 });send(id: string, msg: Record<string, unknown>): void
特定のプレイヤーにメッセージを送信する。
typescript
room.send(targetId, { type: 'whisper', text: 'hello' });on(type: string, handler: (data: any) => void): void
メッセージタイプに対するハンドラを登録する。受信データには __from (送信者 playerId) が含まれる。
typescript
room.on('attack', (msg) => {
console.log(`${msg.__from} sent ${msg.lines} lines`);
});URL パラメータ
init() / run() / sync() は以下の URL パラメータを読み取る。
| パラメータ | 説明 |
|---|---|
?server= | WebSocket サーバーの URL (uzu dev 時は ws://localhost:<port>。本番ではホストが自動注入する) |
?roomId= | ルーム ID。指定するとオンラインモードになる |
?seatId= | 自分の席 ID |
?seats= | JSON エンコードされた席リスト (kind 付き) |
?__dev=N | Dev ハーネスモード (N 画面)。詳細は はじめに を参照 |