@uzuhq/code-sdk 0.8.5 → 0.8.7
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 +61 -7
- package/dist/dev-globals.d.ts +1 -1
- package/dist/{dev-hooks-DmOV2p0j.d.ts → dev-hooks-B9mpXwaP.d.ts} +0 -3
- package/dist/index.d.ts +57 -2
- package/dist/index.js +236 -36
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -313,6 +313,58 @@ onPlayersChanged((players) => {
|
|
|
313
313
|
|
|
314
314
|
---
|
|
315
315
|
|
|
316
|
+
## SafeArea / HUD 回避
|
|
317
|
+
|
|
318
|
+
ゲームは常に全画面へ描かれる。ノッチや UZU の HUD を避けるのはゲーム側の仕事で、避けるべき
|
|
319
|
+
領域はホストが配る。**素の `env(safe-area-inset-*)` は使わない** (iframe / WebView の境界を
|
|
320
|
+
越えないのでホストによっては 0 になる)。
|
|
321
|
+
|
|
322
|
+
`init()` が以下の CSS カスタムプロパティを起動時に設定する。
|
|
323
|
+
|
|
324
|
+
| 変数 | 意味 |
|
|
325
|
+
| ----------------------------------------------- | ------------------------------------------------------------------------- |
|
|
326
|
+
| `--uzu-safe-area-inset-{top,right,bottom,left}` | デバイスの safe area |
|
|
327
|
+
| `--uzu-hud-inset-x` / `--uzu-hud-inset-y` | HUD 矩形の右下座標(safe area 内側の左上が原点) |
|
|
328
|
+
| `--uzu-content-inset-{top,right,bottom,left}` | 上 2 つを合成済み (`top` にだけ HUD の高さを足す)。**迷ったらこれを使う** |
|
|
329
|
+
|
|
330
|
+
```css
|
|
331
|
+
.game-ui {
|
|
332
|
+
padding-top: var(--uzu-content-inset-top, env(safe-area-inset-top, 0px));
|
|
333
|
+
padding-left: var(--uzu-content-inset-left, env(safe-area-inset-left, 0px));
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### `getInsets(): UzuInsets`
|
|
338
|
+
|
|
339
|
+
canvas / WebGL のように CSS で避けられない描画向けに、同じ値を数値で読む。
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
import { getInsets } from '@uzuhq/code-sdk';
|
|
343
|
+
|
|
344
|
+
const { safeArea, content, hud } = getInsets();
|
|
345
|
+
// safeArea / content: { top, right, bottom, left } (CSS px)
|
|
346
|
+
// hud: { x, y }
|
|
347
|
+
layout(content);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
値は初回ペイント前に確定しているので setup の中で読んでよい。
|
|
351
|
+
|
|
352
|
+
### `onInsetsChange(cb: (insets: UzuInsets) => void): () => void`
|
|
353
|
+
|
|
354
|
+
回転 / PiP で値が変わったときに呼ばれる。戻り値を呼ぶと解除できる。
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
import { onInsetsChange } from '@uzuhq/code-sdk';
|
|
358
|
+
|
|
359
|
+
const off = onInsetsChange((insets) => layout(insets.content));
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**CSS 変数の変化は `resize` イベントを発火させない。** `window.addEventListener('resize', …)`
|
|
363
|
+
だけを見ている実装は追従できないので、canvas を持つシナリオは必ずこちらを使う。
|
|
364
|
+
登録時の即時呼び出しはしないので、初期値は `getInsets()` で読む。
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
316
368
|
## メッセージング
|
|
317
369
|
|
|
318
370
|
### `send(type: string, payload?: object): void`
|
|
@@ -436,13 +488,15 @@ interface PlayerVoiceState {
|
|
|
436
488
|
|
|
437
489
|
`init()` / `run()` は以下の URL パラメータを読み取る。
|
|
438
490
|
|
|
439
|
-
| パラメータ
|
|
440
|
-
|
|
|
441
|
-
| `?server=`
|
|
442
|
-
| `?roomId=`
|
|
443
|
-
| `?seatId=`
|
|
444
|
-
| `?players=`
|
|
445
|
-
| `?__dev=N`
|
|
491
|
+
| パラメータ | 説明 |
|
|
492
|
+
| ------------------------------------------- | --------------------------------------------- |
|
|
493
|
+
| `?server=` | WebSocket サーバーの URL |
|
|
494
|
+
| `?roomId=` | ルーム ID。指定するとオンラインモードになる |
|
|
495
|
+
| `?seatId=` | 自分の席 ID |
|
|
496
|
+
| `?players=` | JSON エンコードされたプレイヤーリスト |
|
|
497
|
+
| `?__dev=N` | Dev ハーネスモード (N 画面の iframe 並列表示) |
|
|
498
|
+
| `?uzuSafeAreaInset{Top,Right,Bottom,Left}=` | デバイスの safe area。ホストが配る |
|
|
499
|
+
| `?uzuHudInsetX=` / `?uzuHudInsetY=` | HUD 矩形の右下座標。ホストが配る |
|
|
446
500
|
|
|
447
501
|
### モードの自動判定
|
|
448
502
|
|
package/dist/dev-globals.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
//#region src/dev-state-patch.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* @docs
|
|
4
|
-
* - 外部 automation API: docs/docs/uzu_code/sdk-guide/dev-hooks.md
|
|
5
|
-
*
|
|
6
3
|
* `__uzu_dev.mergeRawState` / `__uzu_dev.patchRawState` の中身。
|
|
7
4
|
* scenario state を「object 階層の部分更新 (RFC 7396)」と「array 要素単体の書換
|
|
8
5
|
* 含む path-based ops (RFC 6902)」で書き換えるための utility。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as applyJsonMergePatch, A as GameLogic, B as ServerOnlyActionHandlerFn, C as ActionHandler, D as DeadlineArgs, E as Deadline, F as ServerActionHandler, G as Duration, H as SetupContext, I as ServerActionMap, J as minus, K as GAME_START, L as ServerEvent, M as SeededRandom, N as ServerActionArgs, O as DeadlineContext, P as ServerActionContext, Q as JsonPatchOp, R as ServerOnlyAction, S as ActionContext, T as DEFAULT_ICON_URLS, U as UpdateArgs, V as SetupArgs, W as UpdateContext, X as sub, Y as plus, Z as JsonMergePatch, _ as PlayerVoiceState, a as createDevHooks, b as SendAction, c as BridgeChannel, d as ConnectionState, et as applyJsonPatch, f as EventHandler, g as PlayScreenMessage, h as PayloadMap, i as attachDevHooks, j as Seat, k as Emit, l as BridgeMessage, m as GameConfig, n as RunHandle, o as PredictionWarning, p as EventSubscription, q as GameTime, r as UzuDevHooks, s as getPredictionWarnings, t as DevHooksCtx, u as ConnectionCallbacks, v as PlayersChangedMessage, w as ActionMap, x as ActionArgs, y as SeatKind, z as ServerOnlyActionContext } from "./dev-hooks-
|
|
1
|
+
import { $ as applyJsonMergePatch, A as GameLogic, B as ServerOnlyActionHandlerFn, C as ActionHandler, D as DeadlineArgs, E as Deadline, F as ServerActionHandler, G as Duration, H as SetupContext, I as ServerActionMap, J as minus, K as GAME_START, L as ServerEvent, M as SeededRandom, N as ServerActionArgs, O as DeadlineContext, P as ServerActionContext, Q as JsonPatchOp, R as ServerOnlyAction, S as ActionContext, T as DEFAULT_ICON_URLS, U as UpdateArgs, V as SetupArgs, W as UpdateContext, X as sub, Y as plus, Z as JsonMergePatch, _ as PlayerVoiceState, a as createDevHooks, b as SendAction, c as BridgeChannel, d as ConnectionState, et as applyJsonPatch, f as EventHandler, g as PlayScreenMessage, h as PayloadMap, i as attachDevHooks, j as Seat, k as Emit, l as BridgeMessage, m as GameConfig, n as RunHandle, o as PredictionWarning, p as EventSubscription, q as GameTime, r as UzuDevHooks, s as getPredictionWarnings, t as DevHooksCtx, u as ConnectionCallbacks, v as PlayersChangedMessage, w as ActionMap, x as ActionArgs, y as SeatKind, z as ServerOnlyActionContext } from "./dev-hooks-B9mpXwaP.js";
|
|
2
2
|
//#region ../engine-core/src/json-patch.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* @docs
|
|
@@ -121,6 +121,61 @@ declare class ReconnectableWebSocket {
|
|
|
121
121
|
private handlePong;
|
|
122
122
|
}
|
|
123
123
|
//#endregion
|
|
124
|
+
//#region src/insets.d.ts
|
|
125
|
+
/**
|
|
126
|
+
* @docs
|
|
127
|
+
* - SafeArea 対応ガイド: javascript/uzu_code/docs/docs/guides/safe-area.md
|
|
128
|
+
* - Flutterライブラリ仕様: docs/docs/uzu_code/flutter-host.md
|
|
129
|
+
*
|
|
130
|
+
* ホストが配る inset を CSS 変数と JS API の両方へ流し込む層。
|
|
131
|
+
*
|
|
132
|
+
* `env(safe-area-inset-*)` は iframe / WebView の境界を越えない。GamePlayScreen を
|
|
133
|
+
* 開くホスト 4 つのうち 3 つはゲームを iframe に載せるので、デバイスの safe area は
|
|
134
|
+
* ホストが値として配る以外に伝える手が無い ([csswg-drafts#4670] は未解決のままで、
|
|
135
|
+
* `env()` は UA 定義なので CSS からも JS からも上書きできない)。Discord Activities /
|
|
136
|
+
* Telegram Mini Apps / Capacitor も例外なく同じ形を採っている。
|
|
137
|
+
*
|
|
138
|
+
* [csswg-drafts#4670]: https://github.com/w3c/csswg-drafts/issues/4670
|
|
139
|
+
*/
|
|
140
|
+
/** 4 辺の inset (CSS px)。 */
|
|
141
|
+
interface Insets {
|
|
142
|
+
top: number;
|
|
143
|
+
right: number;
|
|
144
|
+
bottom: number;
|
|
145
|
+
left: number;
|
|
146
|
+
}
|
|
147
|
+
/** ホストが配る inset の全層。 */
|
|
148
|
+
interface UzuInsets {
|
|
149
|
+
/** デバイスの safe area (ノッチ / 角丸 / ホームインジケータ)。 */
|
|
150
|
+
safeArea: Insets;
|
|
151
|
+
/**
|
|
152
|
+
* safeArea と HUD を合成済みの、そのまま避ければ安全な領域。
|
|
153
|
+
* 迷ったらこれを使う。
|
|
154
|
+
*/
|
|
155
|
+
content: Insets;
|
|
156
|
+
/** UZU HUD 矩形の右下座標 (safe area 内側の左上が原点)。 */
|
|
157
|
+
hud: {
|
|
158
|
+
x: number;
|
|
159
|
+
y: number;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 現在の inset を返す。
|
|
164
|
+
*
|
|
165
|
+
* canvas / WebGL は CSS 変数で避けられないので、そこから読む口。値は初回ペイント前に
|
|
166
|
+
* 確定しているので、setup の中で読んでよい。
|
|
167
|
+
*/
|
|
168
|
+
declare function getInsets(): UzuInsets;
|
|
169
|
+
/**
|
|
170
|
+
* inset の変化を購読する。戻り値を呼ぶと解除できる。
|
|
171
|
+
*
|
|
172
|
+
* **CSS 変数の変化は `resize` を発火させない**ので、`window.addEventListener('resize')`
|
|
173
|
+
* だけを見ている実装は回転 / PiP に追従できない。canvas を持つシナリオはこちらを使う。
|
|
174
|
+
*
|
|
175
|
+
* 登録時に即時呼び出しはしない。初期値は [getInsets] で読むこと。
|
|
176
|
+
*/
|
|
177
|
+
declare function onInsetsChange(cb: (insets: UzuInsets) => void): () => void;
|
|
178
|
+
//#endregion
|
|
124
179
|
//#region src/index.d.ts
|
|
125
180
|
type GameMessageHandler = (payload: Record<string, unknown>) => void;
|
|
126
181
|
type PlayersChangedHandler = (players: Record<string, PlayerVoiceState>) => void;
|
|
@@ -190,4 +245,4 @@ declare function onPlayersChanged(handler: PlayersChangedHandler): void;
|
|
|
190
245
|
*/
|
|
191
246
|
declare function run<S, A extends ActionMap<S> = ActionMap<S>, SA extends ServerActionMap<S> = Record<never, never>>(config: GameConfig<S, A, SA>): void;
|
|
192
247
|
//#endregion
|
|
193
|
-
export { type ActionArgs, type ActionContext, type ActionHandler, type ActionMap, type BridgeChannel, type BridgeMessage, type ConnectionCallbacks, type ConnectionState, DEFAULT_ICON_URLS, type Deadline, type DeadlineArgs, type DeadlineContext, type DevHooksCtx, type Duration, type Emit, type EventHandler, type EventSubscription, GAME_START, type GameConfig, type GameLogic, type GameTime, type JsonMergePatch, type JsonPatchOp, type Operation, type PayloadMap, type PlayScreenMessage, type PlayerVoiceState, type PlayersChangedMessage, type PredictionWarning, type ReconnectableWSOptions, ReconnectableWebSocket, type RunHandle, type Seat, type SeatKind, type SeededRandom, SeededRandomImpl, type SendAction, type ServerActionArgs, type ServerActionContext, type ServerActionHandler, type ServerActionMap, type ServerEvent, type ServerOnlyAction, type ServerOnlyActionContext, type ServerOnlyActionHandlerFn, type SetupArgs, type SetupContext, type UpdateArgs, type UpdateContext, type UzuDevHooks, applyJsonMergePatch, applyJsonPatch, attachDevHooks, changeRoom, createDevHooks, firstFrameReady, gameReady, gameTime, getPredictionWarnings, init, isHosted, isPaused, isServerOnlyAction, minus, on, onPauseChange, onPlayersChanged, playBgm, playSound, plus, run, send, serverOnly, setMicEnabled, stopBgm, sub };
|
|
248
|
+
export { type ActionArgs, type ActionContext, type ActionHandler, type ActionMap, type BridgeChannel, type BridgeMessage, type ConnectionCallbacks, type ConnectionState, DEFAULT_ICON_URLS, type Deadline, type DeadlineArgs, type DeadlineContext, type DevHooksCtx, type Duration, type Emit, type EventHandler, type EventSubscription, GAME_START, type GameConfig, type GameLogic, type GameTime, type Insets, type JsonMergePatch, type JsonPatchOp, type Operation, type PayloadMap, type PlayScreenMessage, type PlayerVoiceState, type PlayersChangedMessage, type PredictionWarning, type ReconnectableWSOptions, ReconnectableWebSocket, type RunHandle, type Seat, type SeatKind, type SeededRandom, SeededRandomImpl, type SendAction, type ServerActionArgs, type ServerActionContext, type ServerActionHandler, type ServerActionMap, type ServerEvent, type ServerOnlyAction, type ServerOnlyActionContext, type ServerOnlyActionHandlerFn, type SetupArgs, type SetupContext, type UpdateArgs, type UpdateContext, type UzuDevHooks, type UzuInsets, applyJsonMergePatch, applyJsonPatch, attachDevHooks, changeRoom, createDevHooks, firstFrameReady, gameReady, gameTime, getInsets, getPredictionWarnings, init, isHosted, isPaused, isServerOnlyAction, minus, on, onInsetsChange, onPauseChange, onPlayersChanged, playBgm, playSound, plus, run, send, serverOnly, setMicEnabled, stopBgm, sub };
|
package/dist/index.js
CHANGED
|
@@ -353,7 +353,7 @@ function unfreezeGameTime() {
|
|
|
353
353
|
*/
|
|
354
354
|
let paused = false;
|
|
355
355
|
let pausedBy = null;
|
|
356
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
356
|
+
const listeners$1 = /* @__PURE__ */ new Set();
|
|
357
357
|
/** transport から呼ぶ内部関数。停止状態が変わったときだけ通知する。 */
|
|
358
358
|
function setPauseState(next, by) {
|
|
359
359
|
pausedBy = next ? by : null;
|
|
@@ -361,7 +361,7 @@ function setPauseState(next, by) {
|
|
|
361
361
|
paused = next;
|
|
362
362
|
if (paused) freezeGameTime();
|
|
363
363
|
else unfreezeGameTime();
|
|
364
|
-
for (const cb of listeners) try {
|
|
364
|
+
for (const cb of listeners$1) try {
|
|
365
365
|
cb(paused);
|
|
366
366
|
} catch (err) {
|
|
367
367
|
console.warn("[uzu] onPauseChange listener threw:", err);
|
|
@@ -377,7 +377,7 @@ function getPausedBy() {
|
|
|
377
377
|
}
|
|
378
378
|
/** 停止状態の変化を購読する。 */
|
|
379
379
|
function onPauseChange(cb) {
|
|
380
|
-
listeners.add(cb);
|
|
380
|
+
listeners$1.add(cb);
|
|
381
381
|
}
|
|
382
382
|
let requester = null;
|
|
383
383
|
function setPauseRequester(fn) {
|
|
@@ -1073,6 +1073,234 @@ function attachDevHooks(ctx) {
|
|
|
1073
1073
|
window.__uzu_dev = createDevHooks(ctx);
|
|
1074
1074
|
}
|
|
1075
1075
|
//#endregion
|
|
1076
|
+
//#region src/insets.ts
|
|
1077
|
+
const SIDES = [
|
|
1078
|
+
"top",
|
|
1079
|
+
"right",
|
|
1080
|
+
"bottom",
|
|
1081
|
+
"left"
|
|
1082
|
+
];
|
|
1083
|
+
const SAFE_AREA_PARAM = {
|
|
1084
|
+
top: "uzuSafeAreaInsetTop",
|
|
1085
|
+
right: "uzuSafeAreaInsetRight",
|
|
1086
|
+
bottom: "uzuSafeAreaInsetBottom",
|
|
1087
|
+
left: "uzuSafeAreaInsetLeft"
|
|
1088
|
+
};
|
|
1089
|
+
/** ホストが値を配らない経路 (素のブラウザで直接開く等) の逃げ道。 */
|
|
1090
|
+
const envExpr = (side) => `env(safe-area-inset-${side}, 0px)`;
|
|
1091
|
+
const HUD_GAP = 6;
|
|
1092
|
+
const HUD_ACTION_ITEM_WIDTH = 38;
|
|
1093
|
+
/**
|
|
1094
|
+
* ホストが配った safe area。`null` は「配られていない」で、`env()` に委ねる合図。
|
|
1095
|
+
* 値で持っているあいだは計測が要らないので、回転のたびにレイアウトを読み直さずに済む。
|
|
1096
|
+
*/
|
|
1097
|
+
let hostSafeArea = null;
|
|
1098
|
+
let hud = {
|
|
1099
|
+
x: 0,
|
|
1100
|
+
y: 0
|
|
1101
|
+
};
|
|
1102
|
+
let current = null;
|
|
1103
|
+
let envProbe = null;
|
|
1104
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1105
|
+
const zero = () => ({
|
|
1106
|
+
top: 0,
|
|
1107
|
+
right: 0,
|
|
1108
|
+
bottom: 0,
|
|
1109
|
+
left: 0
|
|
1110
|
+
});
|
|
1111
|
+
const toFinite = (value) => {
|
|
1112
|
+
const n = typeof value === "string" ? Number.parseFloat(value) : value;
|
|
1113
|
+
return typeof n === "number" && Number.isFinite(n) ? n : null;
|
|
1114
|
+
};
|
|
1115
|
+
/**
|
|
1116
|
+
* `env()` の実値を読む計測用の要素。
|
|
1117
|
+
*
|
|
1118
|
+
* `env()` は JS から直接読めず、CSS 変数へ入れても `getComputedStyle` は代入前の
|
|
1119
|
+
* トークン列を返す。padding に置いて算出値を読むのが唯一の手。
|
|
1120
|
+
*/
|
|
1121
|
+
function measureEnvInsets() {
|
|
1122
|
+
if (typeof document === "undefined") return zero();
|
|
1123
|
+
if (!envProbe?.isConnected) {
|
|
1124
|
+
const el = document.createElement("div");
|
|
1125
|
+
el.setAttribute("aria-hidden", "true");
|
|
1126
|
+
el.style.cssText = [
|
|
1127
|
+
"position:fixed;top:0;left:0;width:0;height:0",
|
|
1128
|
+
"visibility:hidden;pointer-events:none",
|
|
1129
|
+
...SIDES.map((side) => `padding-${side}:${envExpr(side)}`)
|
|
1130
|
+
].join(";");
|
|
1131
|
+
(document.body ?? document.documentElement).appendChild(el);
|
|
1132
|
+
envProbe = el;
|
|
1133
|
+
}
|
|
1134
|
+
const style = getComputedStyle(envProbe);
|
|
1135
|
+
return {
|
|
1136
|
+
top: toFinite(style.paddingTop) ?? 0,
|
|
1137
|
+
right: toFinite(style.paddingRight) ?? 0,
|
|
1138
|
+
bottom: toFinite(style.paddingBottom) ?? 0,
|
|
1139
|
+
left: toFinite(style.paddingLeft) ?? 0
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
function setVar(name, value) {
|
|
1143
|
+
if (typeof document === "undefined") return;
|
|
1144
|
+
document.documentElement.style.setProperty(name, value);
|
|
1145
|
+
}
|
|
1146
|
+
function writeSafeAreaVars() {
|
|
1147
|
+
for (const side of SIDES) setVar(`--uzu-safe-area-inset-${side}`, hostSafeArea ? `${hostSafeArea[side]}px` : envExpr(side));
|
|
1148
|
+
}
|
|
1149
|
+
function writeHudVars() {
|
|
1150
|
+
setVar("--uzu-hud-inset-x", `${hud.x}px`);
|
|
1151
|
+
setVar("--uzu-hud-inset-y", `${hud.y}px`);
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* 合成済みの層。下 2 層を参照する式で書くので、safe area が実数でも `env()` でも
|
|
1155
|
+
* 同じ 1 本で成立する。
|
|
1156
|
+
*
|
|
1157
|
+
* `left` に `--uzu-hud-inset-x` を足さないのは、HUD が左上の**矩形**だから。上辺を
|
|
1158
|
+
* HUD の高さ分だけ空ければその下は全域が安全で、`left` にも足すと L 字全体を避ける
|
|
1159
|
+
* ことになり過剰に削れる。精密に避けたいシナリオは下 2 層を自分で合成する。
|
|
1160
|
+
*/
|
|
1161
|
+
function writeContentVars() {
|
|
1162
|
+
setVar("--uzu-content-inset-top", "calc(var(--uzu-safe-area-inset-top, 0px) + var(--uzu-hud-inset-y, 0px))");
|
|
1163
|
+
setVar("--uzu-content-inset-right", "var(--uzu-safe-area-inset-right, 0px)");
|
|
1164
|
+
setVar("--uzu-content-inset-bottom", "var(--uzu-safe-area-inset-bottom, 0px)");
|
|
1165
|
+
setVar("--uzu-content-inset-left", "var(--uzu-safe-area-inset-left, 0px)");
|
|
1166
|
+
}
|
|
1167
|
+
function compute() {
|
|
1168
|
+
const safeArea = hostSafeArea ?? measureEnvInsets();
|
|
1169
|
+
return {
|
|
1170
|
+
safeArea,
|
|
1171
|
+
content: {
|
|
1172
|
+
top: safeArea.top + hud.y,
|
|
1173
|
+
right: safeArea.right,
|
|
1174
|
+
bottom: safeArea.bottom,
|
|
1175
|
+
left: safeArea.left
|
|
1176
|
+
},
|
|
1177
|
+
hud: { ...hud }
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
const sameInsets = (a, b) => SIDES.every((side) => a[side] === b[side]);
|
|
1181
|
+
const same = (a, b) => sameInsets(a.safeArea, b.safeArea) && a.hud.x === b.hud.x && a.hud.y === b.hud.y;
|
|
1182
|
+
/** 値が動いたときだけ購読者へ流す。 */
|
|
1183
|
+
function refresh() {
|
|
1184
|
+
const next = compute();
|
|
1185
|
+
if (current && same(current, next)) return;
|
|
1186
|
+
current = next;
|
|
1187
|
+
for (const cb of listeners) try {
|
|
1188
|
+
cb(next);
|
|
1189
|
+
} catch (err) {
|
|
1190
|
+
console.warn("[UZU SDK] onInsetsChange listener threw:", err);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* HUD 回避領域の右下座標を算出する。
|
|
1195
|
+
* クエリパラメータに uzuHudInsetX/Y がない場合のフォールバック値として使用。
|
|
1196
|
+
*
|
|
1197
|
+
* 前提: UZU ボタンは常に表示される (onMenuPressed は常に非 null)。
|
|
1198
|
+
* Dart 側の HudInsets.x() は hasMenu=false を扱えるが、
|
|
1199
|
+
* フォールバック計算では安全側に倒して常に UZU ボタンありで算出する。
|
|
1200
|
+
*
|
|
1201
|
+
* - players が取得できる場合: 2人以上→アクション数2、1人→アクション数0
|
|
1202
|
+
* - players が取得できない場合: アクション数2 (安全側に倒す)
|
|
1203
|
+
*/
|
|
1204
|
+
function fallbackHudInsets(params) {
|
|
1205
|
+
let actionCount = 2;
|
|
1206
|
+
const json = params.get("players");
|
|
1207
|
+
if (json) try {
|
|
1208
|
+
actionCount = JSON.parse(json).length >= 2 ? 2 : 0;
|
|
1209
|
+
} catch {}
|
|
1210
|
+
let x = 56;
|
|
1211
|
+
if (actionCount > 0) x += HUD_GAP + HUD_ACTION_ITEM_WIDTH * actionCount;
|
|
1212
|
+
return {
|
|
1213
|
+
x,
|
|
1214
|
+
y: 52
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
function readSafeAreaParams(params) {
|
|
1218
|
+
const values = zero();
|
|
1219
|
+
let supplied = false;
|
|
1220
|
+
for (const side of SIDES) {
|
|
1221
|
+
const raw = params.get(SAFE_AREA_PARAM[side]);
|
|
1222
|
+
if (raw === null) continue;
|
|
1223
|
+
const n = toFinite(raw);
|
|
1224
|
+
if (n === null) continue;
|
|
1225
|
+
values[side] = n;
|
|
1226
|
+
supplied = true;
|
|
1227
|
+
}
|
|
1228
|
+
return supplied ? values : null;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* URL パラメータから初期値を確定させる。`init()` が 1 度だけ呼ぶ。
|
|
1232
|
+
*
|
|
1233
|
+
* 初期値を URL で受けるのは、**初回ペイント前に確定させる**ため。起動後に push で
|
|
1234
|
+
* 配る形にすると、最初の 1 フレームだけ inset が空のまま描かれる
|
|
1235
|
+
* ([discord/embedded-app-sdk#304] と同じ踏み方) 。
|
|
1236
|
+
*
|
|
1237
|
+
* [discord/embedded-app-sdk#304]: https://github.com/discord/embedded-app-sdk/issues/304
|
|
1238
|
+
*/
|
|
1239
|
+
function applyInsets(params) {
|
|
1240
|
+
const hudX = toFinite(params.get("uzuHudInsetX"));
|
|
1241
|
+
const hudY = toFinite(params.get("uzuHudInsetY"));
|
|
1242
|
+
const fallback = fallbackHudInsets(params);
|
|
1243
|
+
hud = {
|
|
1244
|
+
x: hudX ?? fallback.x,
|
|
1245
|
+
y: hudY ?? fallback.y
|
|
1246
|
+
};
|
|
1247
|
+
hostSafeArea = readSafeAreaParams(params);
|
|
1248
|
+
writeSafeAreaVars();
|
|
1249
|
+
writeHudVars();
|
|
1250
|
+
writeContentVars();
|
|
1251
|
+
refresh();
|
|
1252
|
+
if (!hostSafeArea && typeof window !== "undefined") {
|
|
1253
|
+
window.addEventListener("resize", refresh);
|
|
1254
|
+
window.addEventListener("orientationchange", refresh);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* ホストからの `insetsChanged` を反映する。回転 / PiP で値が変わったときに届く。
|
|
1259
|
+
* 内部用 (bridge の受信口から呼ばれる)。
|
|
1260
|
+
*/
|
|
1261
|
+
function handleInsetsChanged(payload) {
|
|
1262
|
+
const safeArea = payload.safeArea;
|
|
1263
|
+
if (safeArea) {
|
|
1264
|
+
const next = zero();
|
|
1265
|
+
for (const side of SIDES) next[side] = toFinite(safeArea[side]) ?? 0;
|
|
1266
|
+
hostSafeArea = next;
|
|
1267
|
+
writeSafeAreaVars();
|
|
1268
|
+
}
|
|
1269
|
+
const nextHud = payload.hud;
|
|
1270
|
+
if (nextHud) {
|
|
1271
|
+
hud = {
|
|
1272
|
+
x: toFinite(nextHud.x) ?? hud.x,
|
|
1273
|
+
y: toFinite(nextHud.y) ?? hud.y
|
|
1274
|
+
};
|
|
1275
|
+
writeHudVars();
|
|
1276
|
+
}
|
|
1277
|
+
refresh();
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* 現在の inset を返す。
|
|
1281
|
+
*
|
|
1282
|
+
* canvas / WebGL は CSS 変数で避けられないので、そこから読む口。値は初回ペイント前に
|
|
1283
|
+
* 確定しているので、setup の中で読んでよい。
|
|
1284
|
+
*/
|
|
1285
|
+
function getInsets() {
|
|
1286
|
+
if (!current) refresh();
|
|
1287
|
+
return current ?? compute();
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* inset の変化を購読する。戻り値を呼ぶと解除できる。
|
|
1291
|
+
*
|
|
1292
|
+
* **CSS 変数の変化は `resize` を発火させない**ので、`window.addEventListener('resize')`
|
|
1293
|
+
* だけを見ている実装は回転 / PiP に追従できない。canvas を持つシナリオはこちらを使う。
|
|
1294
|
+
*
|
|
1295
|
+
* 登録時に即時呼び出しはしない。初期値は [getInsets] で読むこと。
|
|
1296
|
+
*/
|
|
1297
|
+
function onInsetsChange(cb) {
|
|
1298
|
+
listeners.add(cb);
|
|
1299
|
+
return () => {
|
|
1300
|
+
listeners.delete(cb);
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
//#endregion
|
|
1076
1304
|
//#region src/run/optimistic-action-client.ts
|
|
1077
1305
|
function createOptimisticActionClient(config) {
|
|
1078
1306
|
const { logic, playerId, onState, events, sendAction } = config;
|
|
@@ -1720,11 +1948,7 @@ function init(opts) {
|
|
|
1720
1948
|
}
|
|
1721
1949
|
} catch {}
|
|
1722
1950
|
});
|
|
1723
|
-
|
|
1724
|
-
const hudY = params.get("uzuHudInsetY");
|
|
1725
|
-
const { x: fallbackX, y: fallbackY } = calcHudInsets(params);
|
|
1726
|
-
document.documentElement.style.setProperty("--uzu-hud-inset-x", `${hudX ?? fallbackX}px`);
|
|
1727
|
-
document.documentElement.style.setProperty("--uzu-hud-inset-y", `${hudY ?? fallbackY}px`);
|
|
1951
|
+
applyInsets(params);
|
|
1728
1952
|
onPauseChange((paused) => {
|
|
1729
1953
|
sendRaw("sdk", "pauseState", {
|
|
1730
1954
|
paused,
|
|
@@ -1905,6 +2129,9 @@ function handleMessage(msg) {
|
|
|
1905
2129
|
console.log(`[SDK] ▶️ handleMessage sdk/requestResume`);
|
|
1906
2130
|
requestResume();
|
|
1907
2131
|
return;
|
|
2132
|
+
case "insetsChanged":
|
|
2133
|
+
handleInsetsChanged(payload ?? {});
|
|
2134
|
+
return;
|
|
1908
2135
|
}
|
|
1909
2136
|
return;
|
|
1910
2137
|
}
|
|
@@ -1915,32 +2142,5 @@ function handleMessage(msg) {
|
|
|
1915
2142
|
return;
|
|
1916
2143
|
}
|
|
1917
2144
|
}
|
|
1918
|
-
const GAP = 6;
|
|
1919
|
-
const ACTION_ITEM_WIDTH = 38;
|
|
1920
|
-
/**
|
|
1921
|
-
* HUD 回避領域の右下座標を算出する。
|
|
1922
|
-
* クエリパラメータに uzuHudInsetX/Y がない場合のフォールバック値として使用。
|
|
1923
|
-
*
|
|
1924
|
-
* 前提: UZU ボタンは常に表示される (onMenuPressed は常に非 null)。
|
|
1925
|
-
* Dart 側の HudInsets.x() は hasMenu=false を扱えるが、
|
|
1926
|
-
* フォールバック計算では安全側に倒して常に UZU ボタンありで算出する。
|
|
1927
|
-
*
|
|
1928
|
-
* - players が取得できる場合: 2人以上→アクション数2、1人→アクション数0
|
|
1929
|
-
* - players が取得できない場合: アクション数2 (安全側に倒す)
|
|
1930
|
-
*/
|
|
1931
|
-
function calcHudInsets(params) {
|
|
1932
|
-
const y = 52;
|
|
1933
|
-
let actionCount = 2;
|
|
1934
|
-
const json = params.get("players");
|
|
1935
|
-
if (json) try {
|
|
1936
|
-
actionCount = JSON.parse(json).length >= 2 ? 2 : 0;
|
|
1937
|
-
} catch {}
|
|
1938
|
-
let x = 56;
|
|
1939
|
-
if (actionCount > 0) x += GAP + ACTION_ITEM_WIDTH * actionCount;
|
|
1940
|
-
return {
|
|
1941
|
-
x,
|
|
1942
|
-
y
|
|
1943
|
-
};
|
|
1944
|
-
}
|
|
1945
2145
|
//#endregion
|
|
1946
|
-
export { DEFAULT_ICON_URLS, GAME_START, ReconnectableWebSocket, SeededRandomImpl, applyJsonMergePatch, applyJsonPatch, attachDevHooks, changeRoom, createDevHooks, firstFrameReady, gameReady, gameTime, getPredictionWarnings, init, isHosted, isPaused, isServerOnlyAction, minus, on, onPauseChange, onPlayersChanged, playBgm, playSound, plus, run, send, serverOnly, setMicEnabled, stopBgm, sub };
|
|
2146
|
+
export { DEFAULT_ICON_URLS, GAME_START, ReconnectableWebSocket, SeededRandomImpl, applyJsonMergePatch, applyJsonPatch, attachDevHooks, changeRoom, createDevHooks, firstFrameReady, gameReady, gameTime, getInsets, getPredictionWarnings, init, isHosted, isPaused, isServerOnlyAction, minus, on, onInsetsChange, onPauseChange, onPlayersChanged, playBgm, playSound, plus, run, send, serverOnly, setMicEnabled, stopBgm, sub };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uzuhq/code-sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "UZU PlayScreen SDK - Flutter ↔ JS ゲーム通信ライブラリ",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"happy-dom": "^20.10.6",
|
|
26
26
|
"tsdown": "^0.22.14",
|
|
27
27
|
"typescript": "^6.0.0",
|
|
28
|
-
"vitest": "^4.1.
|
|
28
|
+
"vitest": "^4.1.11",
|
|
29
29
|
"@uzuhq/code-engine-core": "0.0.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|