@uzuhq/code-sdk 0.8.12 → 0.9.0
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 +184 -76
- package/dist/dev-globals.d.ts +1 -1
- package/dist/{dev-hooks-7UDN8Xac.d.ts → dev-hooks-D30dnLtR.d.ts} +75 -17
- package/dist/index.d.ts +161 -12
- package/dist/index.js +573 -101
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,14 +14,15 @@ npm install @uzuhq/code-sdk
|
|
|
14
14
|
|
|
15
15
|
すべてのブリッジメッセージは `{ channel, type, payload }` の共通構造を持つ。
|
|
16
16
|
|
|
17
|
-
| チャネル
|
|
18
|
-
|
|
|
19
|
-
| `sdk`
|
|
20
|
-
| `game`
|
|
17
|
+
| チャネル | 用途 | 説明 |
|
|
18
|
+
| ---------- | ---------------------------- | -------------------------------------------------------------------------------- |
|
|
19
|
+
| `sdk` | SDK/プラットフォームコマンド | サウンド再生、マイク操作、通話の状態など SDK 内部のメッセージ |
|
|
20
|
+
| `game` | ゲーム独自メッセージ | `send()` / `on()` で送受信するカスタムメッセージ |
|
|
21
|
+
| `platform` | アプリとサーバーの伝言 | 通話の申告・一時停止の要求・presence。SDK が中身を見ずに運ぶ。シナリオは触らない |
|
|
21
22
|
|
|
22
23
|
`send()` は `channel: 'game'` のメッセージのみを送信する。`on()` は `channel: 'game'` のメッセージのみを受信し、handler には `payload` が直接渡される。
|
|
23
24
|
|
|
24
|
-
SDK チャネルのメッセージは `playSound()`, `
|
|
25
|
+
SDK チャネルのメッセージは `playSound()`, `muteMic()` などの専用関数から自動送信される。
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
@@ -75,14 +76,15 @@ run({
|
|
|
75
76
|
|
|
76
77
|
### GameConfig
|
|
77
78
|
|
|
78
|
-
| キー
|
|
79
|
-
|
|
|
80
|
-
| `logic`
|
|
81
|
-
| `onState`
|
|
82
|
-
| `inputs`
|
|
83
|
-
| `events`
|
|
84
|
-
| `playerCount`
|
|
85
|
-
|
|
79
|
+
| キー | 型 | 必須 | 説明 |
|
|
80
|
+
| ------------- | -------------------------------------------------------------- | ---- | -------------------------- |
|
|
81
|
+
| `logic` | `GameLogic<S>` | Yes | ゲームロジック定義 |
|
|
82
|
+
| `onState` | `(state: S, myPlayerId: string) => void` | Yes | state 更新時のコールバック |
|
|
83
|
+
| `inputs` | `(sendAction: (action: string, payload: any) => void) => void` | Yes | 入力ハンドラ登録 |
|
|
84
|
+
| `events` | `Record<string, (data: any) => void>` | No | ゲームイベントハンドラ |
|
|
85
|
+
| `playerCount` | `number` | Yes | プレイヤー数 |
|
|
86
|
+
|
|
87
|
+
接続の状態は `run()` の設定ではなく [`onPresenceChange()`](#接続と通話の状態) で読む。
|
|
86
88
|
|
|
87
89
|
### GameLogic
|
|
88
90
|
|
|
@@ -127,13 +129,14 @@ const logic: GameLogic<MyState> = {
|
|
|
127
129
|
};
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
| キー | 型 | 必須 | 説明
|
|
131
|
-
| --------------- | ---------------------------------------- | ---- |
|
|
132
|
-
| `setup` | `(args: SetupArgs) => S` | Yes | 初期 state を生成。`args.players` は配役を受け取る参加者だけ
|
|
133
|
-
| `actions` | `Record<string, ActionHandler<S>>` | Yes | クライアント先読み + サーバーの 2 回走る。決定的であること
|
|
134
|
-
| `serverActions` | `Record<string, ServerActionHandler<S>>` | No | サーバーでのみ走る。async 可。`ctx` に `tick` / `random` が入る
|
|
135
|
-
| `update` | `(args: UpdateArgs<S>) => void` | Yes | 毎 tick 実行 (`tickRate` が 0 なら呼ばれない)
|
|
136
|
-
| `tickRate` | `number` | No | 秒間 tick 数 (default: 0 = tick なし)
|
|
132
|
+
| キー | 型 | 必須 | 説明 |
|
|
133
|
+
| --------------- | ---------------------------------------- | ---- | -------------------------------------------------------------------- |
|
|
134
|
+
| `setup` | `(args: SetupArgs) => S` | Yes | 初期 state を生成。`args.players` は配役を受け取る参加者だけ |
|
|
135
|
+
| `actions` | `Record<string, ActionHandler<S>>` | Yes | クライアント先読み + サーバーの 2 回走る。決定的であること |
|
|
136
|
+
| `serverActions` | `Record<string, ServerActionHandler<S>>` | No | サーバーでのみ走る。async 可。`ctx` に `tick` / `random` が入る |
|
|
137
|
+
| `update` | `(args: UpdateArgs<S>) => void` | Yes | 毎 tick 実行 (`tickRate` が 0 なら呼ばれない) |
|
|
138
|
+
| `tickRate` | `number` | No | 秒間 tick 数 (default: 0 = tick なし) |
|
|
139
|
+
| `voicePlan` | `(args: VoicePlanArgs<S>) => VoicePlan` | No | 通話の部屋とマイクの方針 ([通話の部屋とマイク](#通話の部屋とマイク)) |
|
|
137
140
|
|
|
138
141
|
ハンドラの引数は 1 つのオブジェクトで、使うものだけ書けばよい。
|
|
139
142
|
`state` / `payload` / `playerId` はその呼び出しの事実、`ctx` は実行環境が与えるもの。
|
|
@@ -204,11 +207,23 @@ const remain = Math.ceil(minus(state.phaseEndsAt, gameTime()) / 1000);
|
|
|
204
207
|
演出だけを止めたいときに限り、読み取り専用で参照できる。
|
|
205
208
|
|
|
206
209
|
```ts
|
|
207
|
-
import {
|
|
210
|
+
import { getPause, onPauseChange } from '@uzuhq/code-sdk';
|
|
211
|
+
|
|
212
|
+
onPauseChange((pause) => (pause ? engine.stop() : engine.start()));
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`getPause()` / `onPauseChange` が渡すのは止まった理由 (`Pause`)。止まっていなければ `null`。
|
|
208
216
|
|
|
209
|
-
|
|
217
|
+
```ts
|
|
218
|
+
type Pause =
|
|
219
|
+
| null
|
|
220
|
+
| { reason: 'emergency'; by: SeatId } // 緊急一時停止。by は止めたプレイヤー
|
|
221
|
+
| { reason: 'waiting'; for: SeatId[]; since: number }; // 自動中断用。今のサーバーは出さない
|
|
210
222
|
```
|
|
211
223
|
|
|
224
|
+
理由は今後増えることがあるので、知らない `reason` でも `null` でなければ止まっているものとして扱う。
|
|
225
|
+
止まっている画面はアプリが描く。
|
|
226
|
+
|
|
212
227
|
`GameLogic` からは触れない。停止を state に持ち込むと「停止中は state が変わらない」という
|
|
213
228
|
前提が崩れる。
|
|
214
229
|
|
|
@@ -269,32 +284,99 @@ stopBgm(); // BGM 停止
|
|
|
269
284
|
|
|
270
285
|
---
|
|
271
286
|
|
|
272
|
-
##
|
|
287
|
+
## 通話の部屋とマイク
|
|
288
|
+
|
|
289
|
+
通話の部屋とマイクの方針は、ロジックの `voicePlan` が state から返す。画面から部屋を直接変える
|
|
290
|
+
API は無い。部屋を変えたいときは action で state を変え、`voicePlan` がそこから新しい部屋を返す。
|
|
273
291
|
|
|
274
|
-
|
|
292
|
+
```ts
|
|
293
|
+
import type { GameLogic } from '@uzuhq/code-sdk';
|
|
294
|
+
|
|
295
|
+
const logic: GameLogic<MyState> = {
|
|
296
|
+
// ...
|
|
297
|
+
voicePlan: ({ state, playerId }) => ({
|
|
298
|
+
room: state.rooms[playerId] ?? null, // null は全体の部屋
|
|
299
|
+
mic: state.phase === 'reading' ? 'locked' : 'free',
|
|
300
|
+
}),
|
|
301
|
+
};
|
|
302
|
+
```
|
|
275
303
|
|
|
276
|
-
|
|
304
|
+
- state が変わるたびに、サーバーがプレイヤー 1 人ずつについて呼ぶ。`deadlines` の `at` と同じく
|
|
305
|
+
**state だけから決まる軽い純関数にすること** (実時刻や乱数を読むと、呼ぶたびに答えが変わって部屋が暴れる)
|
|
306
|
+
- 書かなければ全員が全体の部屋、マイクは本人の自由
|
|
307
|
+
- 投げたり使えない値を返したりしたら、直前の結果を使い続ける
|
|
308
|
+
- 観測席 (観戦・進行管理) は対象外。全体の部屋にミュートで入り、聴く部屋は `setListeningRoom()` で選ぶ
|
|
309
|
+
|
|
310
|
+
| `mic` | 場面に入ったとき | 場面の中 | 場面を抜けたとき |
|
|
311
|
+
| -------- | ---------------- | -------------------------------------------------------------------------- | --------------------------------------- |
|
|
312
|
+
| `free` | 何もしない | 本人の自由 | 何もしない |
|
|
313
|
+
| `muted` | ミュートする | 本人は外せる | 何もしない (ミュートは本人が外す) |
|
|
314
|
+
| `locked` | ミュートする | 本人も外せない。押すと「この場面ではマイクを使えません」のダイアログが出る | ロックだけ外れる (ミュートは本人が外す) |
|
|
315
|
+
|
|
316
|
+
作品にできるのはマイクを制限することだけで、開くことはできない。場面を抜けても、ミュートは自動では外れない。勝手にマイクを開くと、本人の知らないうちに声が他の人へ届いてしまうため。
|
|
317
|
+
|
|
318
|
+
緊急一時停止の間は全員が全体の部屋に集まり、`locked` のロックも外れる (ミュートは切れたまま)。
|
|
319
|
+
再開すると部屋に戻り、またロックしてミュートする。
|
|
320
|
+
|
|
321
|
+
### `muteMic(): void`
|
|
322
|
+
|
|
323
|
+
本人のマイクをミュートする。
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { muteMic } from '@uzuhq/code-sdk';
|
|
327
|
+
|
|
328
|
+
muteMic();
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- 作品からできるのはミュートだけで、外すことはできない。外すのは本人が HUD のマイクボタンで行う (勝手にマイクを開くと、本人の知らないうちに声が他の人へ届くため)
|
|
332
|
+
- 場面ごとに決まったミュートは `voicePlan` の `mic` で宣言する
|
|
333
|
+
- 今のマイクの状態は `getPresence().me.voice` で読む
|
|
334
|
+
|
|
335
|
+
### `setListeningRoom(room: string | null): void`
|
|
336
|
+
|
|
337
|
+
観測席 (観戦・進行管理) が聴く部屋を選ぶ。`null` は全体の部屋。プレイヤー席で呼んでも何も起きない
|
|
338
|
+
(プレイヤーの部屋は `voicePlan` が決める)。
|
|
277
339
|
|
|
278
340
|
```ts
|
|
279
|
-
import {
|
|
341
|
+
import { setListeningRoom } from '@uzuhq/code-sdk';
|
|
280
342
|
|
|
281
|
-
|
|
282
|
-
setMicEnabled(true); // マイクをオン
|
|
343
|
+
setListeningRoom('secret'); // 密談室を聴く
|
|
283
344
|
```
|
|
284
345
|
|
|
285
|
-
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## 接続と通話の状態
|
|
286
349
|
|
|
287
|
-
|
|
350
|
+
### `getPresence(): Presence` / `onPresenceChange(listener): void`
|
|
351
|
+
|
|
352
|
+
自分の接続と、プレイヤー全員の通話の状態。他の人については声が届くかだけを出す (ゲームの接続は出さない。
|
|
353
|
+
アプリが裏に回るとゲームの画面は止まるが、通話は続くため)。
|
|
288
354
|
|
|
289
355
|
```ts
|
|
290
|
-
import {
|
|
356
|
+
import { getPresence, onPresenceChange } from '@uzuhq/code-sdk';
|
|
291
357
|
|
|
292
|
-
|
|
293
|
-
|
|
358
|
+
onPresenceChange((presence, changes) => {
|
|
359
|
+
for (const [id, other] of Object.entries(presence.others)) drawSeat(id, other);
|
|
360
|
+
for (const change of changes) {
|
|
361
|
+
if (change.kind === 'voice' && change.to === 'lost')
|
|
362
|
+
toast(`${change.seatId} の声が届いていません`);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
294
365
|
```
|
|
295
366
|
|
|
296
|
-
-
|
|
297
|
-
-
|
|
367
|
+
- 誰か (名前・キャラクター) は名簿 (`setup` の `players` / state) から、自分の席 ID と席種は `onState` の引数から引く
|
|
368
|
+
- 切れた側への変化は 5 秒続いたものだけが確定する。瞬断では変わらない
|
|
369
|
+
- `changes` は自分の接続の変化、全員の通話の状態の変化、観測席の人数の変化だけ。発話やミュートの変化では空で、値だけが新しくなる
|
|
370
|
+
- 発話は 1 秒に数回変わるので、描画のフレームごとにまとめて届く
|
|
371
|
+
- アプリも HUD の下に帯 (「〇〇の声が届いていません」など) を出す。シナリオが自前で出しても重ねて出る
|
|
372
|
+
- `roomId` なしのローカル実行では、自分は `online`・通話は `off` に固定される
|
|
373
|
+
- `onPresenceChange()` / `onPauseChange()` の戻り値の関数を呼ぶと、呼ばれなくなる (場面ごとに登録し直すときに使う)
|
|
374
|
+
|
|
375
|
+
型は [Presence](#presence) を参照。
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## デバイス連携
|
|
298
380
|
|
|
299
381
|
### `haptic(kind: 'light' | 'medium' | 'heavy'): void`
|
|
300
382
|
|
|
@@ -329,21 +411,6 @@ onState(state) {
|
|
|
329
411
|
- 再描画のたびに呼んでもよい (ホストは処理中の 2 回目以降を無視する)
|
|
330
412
|
- 対応していない古いアプリや `uzu dev` では何も起きないので、呼んだあとも画面を操作不能にしない
|
|
331
413
|
|
|
332
|
-
### `onPlayersChanged(handler: (players: Record<string, PlayerVoiceState>) => void): void`
|
|
333
|
-
|
|
334
|
-
プレイヤーのリアルタイム状態(音声状態)が変化したときのハンドラを登録する。
|
|
335
|
-
|
|
336
|
-
```ts
|
|
337
|
-
import { onPlayersChanged } from '@uzuhq/code-sdk';
|
|
338
|
-
|
|
339
|
-
onPlayersChanged((players) => {
|
|
340
|
-
for (const [id, state] of Object.entries(players)) {
|
|
341
|
-
// state.audioStatus: 'speaking' | 'listening' | 'muted' | 'unstable' | null
|
|
342
|
-
updatePlayerUI(id, state.audioStatus);
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
414
|
---
|
|
348
415
|
|
|
349
416
|
## SafeArea / HUD 回避
|
|
@@ -423,7 +490,7 @@ on('attack', (payload) => {
|
|
|
423
490
|
```
|
|
424
491
|
|
|
425
492
|
:::caution
|
|
426
|
-
`on()` は `channel: 'game'`
|
|
493
|
+
`on()` は `channel: 'game'` のメッセージのみを受信する。通話や接続の状態は `onPresenceChange()`、一時停止は `onPauseChange()` などの専用関数を使用すること。
|
|
427
494
|
:::
|
|
428
495
|
|
|
429
496
|
### `isHosted`
|
|
@@ -438,7 +505,7 @@ on('attack', (payload) => {
|
|
|
438
505
|
|
|
439
506
|
```ts
|
|
440
507
|
interface BridgeMessage {
|
|
441
|
-
channel: 'sdk' | 'game';
|
|
508
|
+
channel: 'sdk' | 'game' | 'platform';
|
|
442
509
|
type: string;
|
|
443
510
|
payload: Record<string, unknown>;
|
|
444
511
|
}
|
|
@@ -487,33 +554,73 @@ onState(state, myPlayerId, mySeatKind) {
|
|
|
487
554
|
|
|
488
555
|
### ConnectionState
|
|
489
556
|
|
|
557
|
+
`ReconnectableWebSocket` の接続状態。シナリオが見る自分の接続は `getPresence().me.connection`。
|
|
558
|
+
|
|
490
559
|
```ts
|
|
491
560
|
type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
492
561
|
```
|
|
493
562
|
|
|
494
|
-
###
|
|
563
|
+
### VoicePlan
|
|
564
|
+
|
|
565
|
+
`voicePlan` が返す値。
|
|
495
566
|
|
|
496
567
|
```ts
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
568
|
+
type VoicePlan = {
|
|
569
|
+
room: string | null; // 入る部屋。null は全体の部屋
|
|
570
|
+
mic: 'free' | 'muted' | 'locked';
|
|
571
|
+
};
|
|
500
572
|
```
|
|
501
573
|
|
|
502
|
-
###
|
|
574
|
+
### Presence
|
|
575
|
+
|
|
576
|
+
`getPresence()` / `onPresenceChange` が渡す値。`since` は端末の時刻 (ms)。
|
|
503
577
|
|
|
504
578
|
```ts
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
579
|
+
type Presence = {
|
|
580
|
+
me: Me; // 観測席でも必ずある
|
|
581
|
+
others: Record<SeatId, Other>; // 自分以外のプレイヤー
|
|
582
|
+
observers: { count: number; speaking: { id: SeatId; nickname: string }[] }; // 観測席は人数と、今話している人だけ
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
type Me = {
|
|
586
|
+
connection:
|
|
587
|
+
{ status: 'online' } | { status: 'connecting' | 'reconnecting' | 'offline'; since: number };
|
|
588
|
+
voice:
|
|
589
|
+
| { status: 'off'; reason: 'table' | 'this-device' } // 卓が通話を使わない / この端末は通話に入らない
|
|
590
|
+
| { status: 'connecting' | 'reconnecting'; since: number }
|
|
591
|
+
| { status: 'failed'; reason: 'mic-permission' | 'network'; since: number }
|
|
592
|
+
| {
|
|
593
|
+
status: 'here';
|
|
594
|
+
room: string | null;
|
|
595
|
+
mic: Mic;
|
|
596
|
+
speaking: boolean;
|
|
597
|
+
quality: 'good' | 'poor';
|
|
598
|
+
};
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
type Other = {
|
|
602
|
+
voice:
|
|
603
|
+
| { status: 'off' }
|
|
604
|
+
| { status: 'here'; mic: Mic; speaking: boolean; quality: 'good' | 'poor' } // 自分と同じ部屋で聞こえている
|
|
605
|
+
| { status: 'elsewhere'; room: string | null } // 別の部屋にいる
|
|
606
|
+
| { status: 'lost'; since: number } // 声が届かない (通話が切れた / 同じ部屋のはずなのに聞こえなくなった)
|
|
607
|
+
| { status: 'unknown'; reason: 'me-not-connected' | 'no-report' }; // 自分が通話にいない / 申告が届かず聞こえてもいない
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
type Mic =
|
|
611
|
+
| { status: 'open' }
|
|
612
|
+
| { status: 'muted'; by: 'self' | 'scenario' } // scenario = 場面の頭で切られたまま。本人は戻せる
|
|
613
|
+
| { status: 'locked' } // 場面の間は本人も解除できない
|
|
614
|
+
| { status: 'listen-only' }; // ライブ配信の観戦者。聴くだけ
|
|
615
|
+
|
|
616
|
+
type PresenceChange =
|
|
617
|
+
| { kind: 'connection'; seatId: SeatId; from: ConnectionStatus; to: ConnectionStatus } // 自分の接続だけ
|
|
618
|
+
| { kind: 'voice'; seatId: SeatId; from: VoiceStatus; to: VoiceStatus }
|
|
619
|
+
| { kind: 'observers'; from: number; to: number };
|
|
508
620
|
```
|
|
509
621
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
| `speaking` | 発話中 |
|
|
513
|
-
| `listening` | 音声接続済み・聞いている |
|
|
514
|
-
| `muted` | ミュート中 |
|
|
515
|
-
| `unstable` | 接続に問題あり |
|
|
516
|
-
| `null` | 音声通話に未接続 |
|
|
622
|
+
`others` の通話は本人の申告に自分の端末の見え方 (自分の部屋、声が聞こえるか) を重ねたものなので、
|
|
623
|
+
端末ごとに違いうる。本人の申告が届かなくても (アプリが裏に回った等)、自分の端末で聞こえていれば `here` になる。
|
|
517
624
|
|
|
518
625
|
---
|
|
519
626
|
|
|
@@ -521,15 +628,16 @@ interface PlayerVoiceState {
|
|
|
521
628
|
|
|
522
629
|
`init()` / `run()` は以下の URL パラメータを読み取る。
|
|
523
630
|
|
|
524
|
-
| パラメータ | 説明
|
|
525
|
-
| ------------------------------------------- |
|
|
526
|
-
| `?server=` | WebSocket サーバーの URL
|
|
527
|
-
| `?roomId=` | ルーム ID。指定するとオンラインモードになる
|
|
528
|
-
| `?seatId=` | 自分の席 ID
|
|
529
|
-
| `?players=` | JSON エンコードされたプレイヤーリスト
|
|
530
|
-
| `?
|
|
531
|
-
| `?
|
|
532
|
-
| `?
|
|
631
|
+
| パラメータ | 説明 |
|
|
632
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
633
|
+
| `?server=` | WebSocket サーバーの URL |
|
|
634
|
+
| `?roomId=` | ルーム ID。指定するとオンラインモードになる |
|
|
635
|
+
| `?seatId=` | 自分の席 ID |
|
|
636
|
+
| `?players=` | JSON エンコードされたプレイヤーリスト |
|
|
637
|
+
| `?monitor=1` | 運営や作者が監視用に開いた画面。ホストが付ける。サーバーはこの接続を数えず、止める要求も受けない |
|
|
638
|
+
| `?__dev=N` | Dev ハーネスモード (N 画面の iframe 並列表示) |
|
|
639
|
+
| `?uzuSafeAreaInset{Top,Right,Bottom,Left}=` | デバイスの safe area。ホストが配る |
|
|
640
|
+
| `?uzuHudInsetX=` / `?uzuHudInsetY=` | HUD 矩形の右下座標。ホストが配る |
|
|
533
641
|
|
|
534
642
|
### モードの自動判定
|
|
535
643
|
|
package/dist/dev-globals.d.ts
CHANGED
|
@@ -55,6 +55,64 @@ declare const minus: (a: GameTime, b: GameTime) => Duration;
|
|
|
55
55
|
*/
|
|
56
56
|
declare const GAME_START: GameTime;
|
|
57
57
|
//#endregion
|
|
58
|
+
//#region ../engine-core/src/presence.d.ts
|
|
59
|
+
/**
|
|
60
|
+
* @docs
|
|
61
|
+
* - 接続と通話の状態 (Presence): docs/docs/uzu_code/presence.md
|
|
62
|
+
* - ブリッジ仕様: docs/docs/uzu_code/bridge.md
|
|
63
|
+
*
|
|
64
|
+
* 接続と通話の状態に関わる型。ロジックに渡す型と、play-server が全員へ配る
|
|
65
|
+
* presence の型をここに置き、room-core と SDK が同じ定義を読む。
|
|
66
|
+
*
|
|
67
|
+
* 判断はサーバー (room-core) に集める。落ちたか、どの部屋にいるべきか、全員を止めるかは
|
|
68
|
+
* サーバーが決めて配る。SDK はシナリオに焼き込まれて二度と変わらないので、ここにある
|
|
69
|
+
* 値を表どおりに並べるだけにする。
|
|
70
|
+
*/
|
|
71
|
+
/** 席 ID。`playerId`・setup の `players` (Seat) の `id` と同じ文字列。 */
|
|
72
|
+
type SeatId = string;
|
|
73
|
+
/**
|
|
74
|
+
* マイクの方針。作品にできるのはマイクを制限することだけで、開くことはできない。
|
|
75
|
+
*
|
|
76
|
+
* - `free`: 何もしない (ミュートも、ミュートの解除もしない)
|
|
77
|
+
* - `muted`: 場面の頭でミュートする。本人は外せる
|
|
78
|
+
* - `locked`: 場面の間は本人も外せない。一時停止の間だけロックが外れる
|
|
79
|
+
*
|
|
80
|
+
* `muted` / `locked` の場面を抜けても、ミュートは自動では外れない。外すのは本人だけ
|
|
81
|
+
* (勝手にマイクを開くと、本人の知らないうちに声が他の人へ届くため)。
|
|
82
|
+
*/
|
|
83
|
+
type MicPolicy = 'free' | 'muted' | 'locked';
|
|
84
|
+
/** プレイヤー 1 人の通話の部屋とマイクの方針。 */
|
|
85
|
+
type VoicePlan = {
|
|
86
|
+
/** 入る部屋。null は全体の部屋。 */
|
|
87
|
+
room: string | null;
|
|
88
|
+
mic: MicPolicy;
|
|
89
|
+
};
|
|
90
|
+
/** `voicePlan` を書かない作品の既定。全員が全体の部屋、マイクは本人の自由。 */
|
|
91
|
+
declare const DEFAULT_VOICE_PLAN: VoicePlan;
|
|
92
|
+
/**
|
|
93
|
+
* `voicePlan` の引数。呼ばれるのはプレイヤーだけで、観測席はプラットフォームが一律に扱う
|
|
94
|
+
* (全体の部屋に入り、入ったらミュート。聴く部屋は本人が選ぶ)。
|
|
95
|
+
*/
|
|
96
|
+
type VoicePlanArgs<S> = {
|
|
97
|
+
state: S;
|
|
98
|
+
playerId: SeatId;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* 一時停止。state ではない。
|
|
102
|
+
*
|
|
103
|
+
* 理由は 1 つだけ持つ。`waiting` (戻らない人を待つ自動中断) はまだ止める側が無いが、
|
|
104
|
+
* 読む側が先に知っておけるよう型に置く。理由は今後増えることがあるので、読む側は知らない
|
|
105
|
+
* `reason` でも止まっているものとして扱う (止める画面はアプリが描く)。
|
|
106
|
+
*/
|
|
107
|
+
type Pause = null | {
|
|
108
|
+
reason: 'emergency';
|
|
109
|
+
by: SeatId;
|
|
110
|
+
} | {
|
|
111
|
+
reason: 'waiting';
|
|
112
|
+
for: SeatId[];
|
|
113
|
+
since: number;
|
|
114
|
+
};
|
|
115
|
+
//#endregion
|
|
58
116
|
//#region ../engine-core/src/types.d.ts
|
|
59
117
|
/**
|
|
60
118
|
* roster に載る席。
|
|
@@ -276,6 +334,15 @@ type GameLogic<S, A extends ActionMap<S> = ActionMap<S>, SA extends ServerAction
|
|
|
276
334
|
*/
|
|
277
335
|
deadlines?: Record<string, Deadline<S>>;
|
|
278
336
|
tickRate?: number;
|
|
337
|
+
/**
|
|
338
|
+
* 通話の部屋とマイクの方針。state が変わるたびに、サーバーがプレイヤー 1 人ずつについて呼ぶ。
|
|
339
|
+
* 結果は presence に載って全員に配られ、各アプリがそれに合わせる。
|
|
340
|
+
*
|
|
341
|
+
* `deadlines` の `at` と同じく、state だけから決まる軽い純関数にすること。実時刻や乱数を
|
|
342
|
+
* 読むと、呼ばれるたびに答えが変わって部屋が暴れる。書かなければ全員が全体の部屋、
|
|
343
|
+
* マイクは本人の自由。投げたら直前の結果を使い続ける。
|
|
344
|
+
*/
|
|
345
|
+
voicePlan?(args: VoicePlanArgs<S>): VoicePlan;
|
|
279
346
|
};
|
|
280
347
|
/** デフォルトのプレイヤーアイコン URL 一覧(dev / local モード用) */
|
|
281
348
|
declare const DEFAULT_ICON_URLS: readonly ["https://imagedelivery.net/htp-D7B2hJT5XtdWYN9e7Q/4d0da24d-bcf2-4f7b-d1a0-f1bb8c747300/original", "https://imagedelivery.net/htp-D7B2hJT5XtdWYN9e7Q/8c75fccb-41d6-429d-e943-06c728a72a00/original", "https://imagedelivery.net/htp-D7B2hJT5XtdWYN9e7Q/43f45d11-da38-4d6e-637d-3df78e583500/original"];
|
|
@@ -286,8 +353,11 @@ type PlayScreenMessage = {
|
|
|
286
353
|
type: string;
|
|
287
354
|
[key: string]: unknown;
|
|
288
355
|
};
|
|
289
|
-
/**
|
|
290
|
-
|
|
356
|
+
/**
|
|
357
|
+
* ブリッジメッセージのチャンネル。`platform` はアプリとサーバーのあいだの伝言で、
|
|
358
|
+
* SDK は中身を見ずに WebSocket の `__platform` と行き来させる。
|
|
359
|
+
*/
|
|
360
|
+
type BridgeChannel = 'sdk' | 'game' | 'platform';
|
|
291
361
|
/** ワイヤーフォーマット: { channel, type, payload, playerId? } */
|
|
292
362
|
type BridgeMessage = {
|
|
293
363
|
channel: BridgeChannel;
|
|
@@ -317,15 +387,6 @@ type BridgeMessage = {
|
|
|
317
387
|
* 自己申告なので権限の根拠にはならない。表示の分岐にだけ使うこと。
|
|
318
388
|
*/
|
|
319
389
|
type SeatKind = 'player' | 'spectator' | 'admin';
|
|
320
|
-
/** プレイヤーごとのリアルタイム状態 */
|
|
321
|
-
type PlayerVoiceState = {
|
|
322
|
-
/** 音声状態。音声通話に未接続の場合は null */
|
|
323
|
-
audioStatus: 'speaking' | 'listening' | 'muted' | 'unstable' | null;
|
|
324
|
-
};
|
|
325
|
-
/** @deprecated 旧形式。新コードでは onPlayersChanged() と PlayerVoiceState を使用 */
|
|
326
|
-
type PlayersChangedMessage = {
|
|
327
|
-
players: Record<string, PlayerVoiceState>;
|
|
328
|
-
};
|
|
329
390
|
/**
|
|
330
391
|
* events handler。
|
|
331
392
|
*
|
|
@@ -382,12 +443,9 @@ type GameConfig<S, A extends ActionMap<S> = ActionMap<S>, SA extends ServerActio
|
|
|
382
443
|
* iframe 内部の `window.innerWidth/Height` を保証する。
|
|
383
444
|
*/
|
|
384
445
|
devMinIframeShortEdge?: number;
|
|
385
|
-
} & ConnectionCallbacks;
|
|
386
|
-
type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
387
|
-
type ConnectionCallbacks = {
|
|
388
|
-
/** WebSocket 接続状態が変化した時に呼ばれる */
|
|
389
|
-
onConnectionStateChange?: (state: ConnectionState) => void;
|
|
390
446
|
};
|
|
447
|
+
/** `ReconnectableWebSocket` の接続状態。シナリオの接続の状態は `getPresence().me.connection`。 */
|
|
448
|
+
type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
|
|
391
449
|
//#endregion
|
|
392
450
|
//#region src/host-globals.d.ts
|
|
393
451
|
declare global {
|
|
@@ -692,4 +750,4 @@ type DevHooksCtx<S = unknown> = {
|
|
|
692
750
|
declare const createDevHooks: <S>(ctx: DevHooksCtx<S>) => UzuDevHooks<S>;
|
|
693
751
|
declare const attachDevHooks: <S>(ctx: DevHooksCtx<S>) => void;
|
|
694
752
|
//#endregion
|
|
695
|
-
export {
|
|
753
|
+
export { GAME_START as $, Emit as A, ServerOnlyActionContext as B, ActionContext as C, Deadline as D, DEFAULT_ICON_URLS as E, ServerActionContext as F, UpdateContext as G, SetupArgs as H, ServerActionHandler as I, Pause as J, DEFAULT_VOICE_PLAN as K, ServerActionMap as L, Seat as M, SeededRandom as N, DeadlineArgs as O, ServerActionArgs as P, Duration as Q, ServerEvent as R, ActionArgs as S, ActionMap as T, SetupContext as U, ServerOnlyActionHandlerFn as V, UpdateArgs as W, VoicePlan as X, SeatId as Y, VoicePlanArgs as Z, GameConfig as _, createDevHooks as a, SeatKind as b, applyJsonMergePatch as c, getPredictionWarnings as d, GameTime as et, BridgeChannel as f, EventSubscription as g, EventHandler as h, attachDevHooks as i, GameLogic as j, DeadlineContext as k, applyJsonPatch as l, ConnectionState as m, RunHandle as n, plus as nt, JsonMergePatch as o, BridgeMessage as p, MicPolicy as q, UzuDevHooks as r, sub as rt, JsonPatchOp as s, DevHooksCtx as t, minus as tt, PredictionWarning as u, PayloadMap as v, ActionHandler as w, SendAction as x, PlayScreenMessage as y, ServerOnlyAction as z };
|