Skip to content

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();

動作:

  1. Flutter WebView / iframe 環境に応じてメッセージ受信口を登録
  2. { type: '__ps_ready' } メッセージをホストへ送信
  3. 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 !== windowtrue (iframe)
  • それ以外 → false

型定義

GameConfig<S>

run() に渡す設定オブジェクト。

キー必須説明
logicGameLogic<S>Yesゲームロジック定義
onState(state: S, myPlayerId: string) => voidYesstate 更新時のコールバック
inputs(sendAction: (action: string, payload: any) => void) => voidYes入力ハンドラ登録
eventsRecord<string, (data: any) => void>Noゲームイベントハンドラ
playerCountnumberYesプレイヤー数

GameLogic<S>

ゲームロジック定義。run() で使用する。

キー必須説明
setup(seats: Seat[], random: SeededRandom) => SYes初期 state を生成。seats には kind !== 'player' の席 (spectator / admin) も含まれるため、配役は kind === 'player' だけを対象にする
actionsRecord<string, ActionHandler<S> | ServerOnlyAction<S>>Yesアクションハンドラ (state を直接変更)。serverOnly() で wrap すると client 先行実行をスキップしてサーバーだけで実行
update(state: S, ctx: UpdateContext) => voidYes毎 tick 実行 (state を直接変更)
tickRatenumberNo秒間 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[]) => SYes初期 state を生成
onState(state: S, myPlayerId: string, serverTime: number) => voidYesstate 更新時のコールバック
inputs(patch: PatchFn, set: SetFn) => voidYes入力ハンドラ (patch: 複数操作、set: 単一値変更)
eventsRecord<string, (data: Record<string, unknown>) => void>Noゲームイベントハンドラ
connectionConnectionCallbacksNo接続状態・エラーコールバック
playerCountnumberYesプレイヤー数

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 付き乱数生成器。GameLogicsetupupdate の引数で提供される。

メソッド説明
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 パターンで使用する。

プロパティ

プロパティ説明
myIdstring (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=NDev ハーネスモード (N 画面)。詳細は はじめに を参照