@unboxy/phaser-sdk 0.2.29 → 0.2.31
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/dist/index.d.ts +1 -1
- package/dist/scene/HudRuntime.js +177 -0
- package/dist/scene/types.d.ts +53 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -28,5 +28,5 @@ export { setupEditorBridge } from './editor/EditorBridge.js';
|
|
|
28
28
|
export { EditorOverlayScene, EDITOR_OVERLAY_KEY } from './editor/EditorOverlayScene.js';
|
|
29
29
|
export type { EditorEntityPatch, EditorEnterMessage, EditorExitMessage, EditorGetSceneMessage, EditorApplyEditMessage, EditorSetSelectionMessage, EditorPanZoomMessage, EditorHostToSdkMessage, EditorSceneLoadedMessage, EditorSelectionPickedMessage, EditorDragEndMessage, EditorShortcutMessage, EditorCreateEntityMessage, EditorDeleteEntityMessage, EditorSetEditModeMessage, EditorSelectionRectMessage, EditorSdkToHostMessage, } from './protocol.js';
|
|
30
30
|
export { SCHEMA_VERSION, } from './scene/types.js';
|
|
31
|
-
export type { Manifest, SceneRef, HudRef, AssetRecord, AssetKind, SceneType, SceneFile, WorldScene, HudScene, WorldSceneConfig, CameraConfig, WorldEntity, WorldEntityKind, NonGroupWorldEntity, SpriteEntity, PrimitiveEntity, CodeRenderedEntity, GroupEntity, TilemapEntity, TriggerEntity, TriggerShape, WorldVisual, SpriteVisual, PrimitiveVisual, PrimitiveRectVisual, PrimitiveCircleVisual, CodeRenderedVisual, HudEntity, HudEntityKind, HudEntityBase, HudTextEntity, HudImageEntity, HudIconButtonEntity, HudVisual, HudTextVisual, HudImageVisual, HudIconButtonVisual, HudTextSource, HudLayer, Transform, Anchor, AnchorSide, } from './scene/types.js';
|
|
31
|
+
export type { Manifest, SceneRef, HudRef, AssetRecord, AssetKind, SceneType, SceneFile, WorldScene, HudScene, WorldSceneConfig, CameraConfig, WorldEntity, WorldEntityKind, NonGroupWorldEntity, SpriteEntity, PrimitiveEntity, CodeRenderedEntity, GroupEntity, TilemapEntity, TriggerEntity, TriggerShape, WorldVisual, SpriteVisual, PrimitiveVisual, PrimitiveRectVisual, PrimitiveCircleVisual, CodeRenderedVisual, HudEntity, HudEntityKind, HudEntityBase, HudTextEntity, HudImageEntity, HudIconButtonEntity, HudProgressBarEntity, HudPanelEntity, HudVisual, HudTextVisual, HudImageVisual, HudIconButtonVisual, HudProgressBarVisual, HudPanelVisual, HudTextSource, HudNumberSource, HudLayer, Transform, Anchor, AnchorSide, } from './scene/types.js';
|
|
32
32
|
export { PROTOCOL_VERSION, type HelloMessage, type InitMessage, type RpcRequestMessage, type RpcResultOk, type RpcResultError, type HostToSdkMessage, type SdkToHostMessage, type RpcErrorPayload, type RpcMethod, type SavesGetParams, type SavesGetResult, type SavesSetParams, type SavesSetResult, type SavesDeleteParams, type SavesDeleteResult, type SavesListResult, type GameDataGetParams, type GameDataGetResult, type GameDataSetParams, type GameDataSetResult, type GameDataDeleteParams, type GameDataDeleteResult, type GameDataListResult, type RealtimeGetTokenParams, type RealtimeGetTokenResult, } from './protocol.js';
|
package/dist/scene/HudRuntime.js
CHANGED
|
@@ -96,6 +96,12 @@ export function spawnHudEntity(ctx, entity) {
|
|
|
96
96
|
case 'icon-button':
|
|
97
97
|
go = createIconButton(ctx, entity, pos);
|
|
98
98
|
break;
|
|
99
|
+
case 'progress-bar':
|
|
100
|
+
go = createProgressBar(ctx, entity, pos);
|
|
101
|
+
break;
|
|
102
|
+
case 'panel':
|
|
103
|
+
go = createPanel(ctx, entity, pos);
|
|
104
|
+
break;
|
|
99
105
|
default: {
|
|
100
106
|
const exhaustive = entity;
|
|
101
107
|
throw new Error(`[unboxy/hud] unknown HUD entity kind: ${JSON.stringify(exhaustive)}`);
|
|
@@ -227,6 +233,144 @@ function createIconButton(ctx, entity, pos) {
|
|
|
227
233
|
container.setData('editorHitHeight', h);
|
|
228
234
|
return container;
|
|
229
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Progress bar — Graphics-rendered (no Phaser primitive matches "fill that
|
|
238
|
+
* shrinks/grows from one edge"). Subscribes to value + max bindings if
|
|
239
|
+
* either is dynamic; redraws whenever a relevant registry key changes.
|
|
240
|
+
*/
|
|
241
|
+
function createProgressBar(ctx, entity, pos) {
|
|
242
|
+
const v = entity.visual;
|
|
243
|
+
const w = v.width ?? 200;
|
|
244
|
+
const h = v.height ?? 16;
|
|
245
|
+
const container = ctx.scene.add.container(pos.x, pos.y);
|
|
246
|
+
// Background + fill drawn into a Graphics so width-stretching doesn't
|
|
247
|
+
// distort corners (no setDisplaySize stretching, no extra dependencies).
|
|
248
|
+
const g = ctx.scene.add.graphics();
|
|
249
|
+
container.add(g);
|
|
250
|
+
const draw = (frac) => {
|
|
251
|
+
const f = Math.max(0, Math.min(1, frac));
|
|
252
|
+
g.clear();
|
|
253
|
+
const radius = v.borderRadius ?? 0;
|
|
254
|
+
// Centered draw: the container is positioned by applyContainerOriginShift
|
|
255
|
+
// so child visuals at (0,0) sit at the resolved anchor; drawing the rect
|
|
256
|
+
// from (-w/2, -h/2) keeps the visual aligned with the editor's
|
|
257
|
+
// editorHit{Width,Height} bounds (which are computed center-based).
|
|
258
|
+
const x0 = -w / 2;
|
|
259
|
+
const y0 = -h / 2;
|
|
260
|
+
// Background.
|
|
261
|
+
if (v.backgroundColor) {
|
|
262
|
+
g.fillStyle(parseColor(v.backgroundColor), 1);
|
|
263
|
+
if (radius > 0)
|
|
264
|
+
g.fillRoundedRect(x0, y0, w, h, radius);
|
|
265
|
+
else
|
|
266
|
+
g.fillRect(x0, y0, w, h);
|
|
267
|
+
}
|
|
268
|
+
// Fill.
|
|
269
|
+
const fillColor = v.fillColor ?? '#22c55e';
|
|
270
|
+
g.fillStyle(parseColor(fillColor), 1);
|
|
271
|
+
const fillW = w * f;
|
|
272
|
+
if (fillW > 0) {
|
|
273
|
+
if (radius > 0)
|
|
274
|
+
g.fillRoundedRect(x0, y0, fillW, h, Math.min(radius, fillW / 2));
|
|
275
|
+
else
|
|
276
|
+
g.fillRect(x0, y0, fillW, h);
|
|
277
|
+
}
|
|
278
|
+
// Border.
|
|
279
|
+
if (v.borderColor && (v.borderWidth ?? 0) > 0) {
|
|
280
|
+
g.lineStyle(v.borderWidth ?? 1, parseColor(v.borderColor), 1);
|
|
281
|
+
if (radius > 0)
|
|
282
|
+
g.strokeRoundedRect(x0, y0, w, h, radius);
|
|
283
|
+
else
|
|
284
|
+
g.strokeRect(x0, y0, w, h);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const computeFrac = () => {
|
|
288
|
+
const value = readNumberSource(v.value, ctx.scene);
|
|
289
|
+
const max = readNumberSource(v.max, ctx.scene);
|
|
290
|
+
if (max <= 0)
|
|
291
|
+
return 0;
|
|
292
|
+
return value / max;
|
|
293
|
+
};
|
|
294
|
+
draw(computeFrac());
|
|
295
|
+
// Subscribe to dynamic bindings. Either value or max can be dynamic;
|
|
296
|
+
// each pushes a redraw on registry change.
|
|
297
|
+
const cleanups = [];
|
|
298
|
+
if (v.value.mode === 'dynamic') {
|
|
299
|
+
const key = `changedata-${v.value.binding}`;
|
|
300
|
+
const handler = () => draw(computeFrac());
|
|
301
|
+
ctx.scene.game.registry.events.on(key, handler);
|
|
302
|
+
cleanups.push(() => ctx.scene.game.registry.events.off(key, handler));
|
|
303
|
+
}
|
|
304
|
+
if (v.max.mode === 'dynamic') {
|
|
305
|
+
const key = `changedata-${v.max.binding}`;
|
|
306
|
+
const handler = () => draw(computeFrac());
|
|
307
|
+
ctx.scene.game.registry.events.on(key, handler);
|
|
308
|
+
cleanups.push(() => ctx.scene.game.registry.events.off(key, handler));
|
|
309
|
+
}
|
|
310
|
+
// setSize so the editor's bounds-based hit-test works on the container.
|
|
311
|
+
container.setSize(w, h);
|
|
312
|
+
container.setData('editorHitWidth', w);
|
|
313
|
+
container.setData('editorHitHeight', h);
|
|
314
|
+
applyContainerOriginShift(container, entity.anchor.side, w, h);
|
|
315
|
+
// Persist redraw + cleanup on the GameObject for applyHudPatch to reach.
|
|
316
|
+
container.setData('hudRedraw', () => draw(computeFrac()));
|
|
317
|
+
container.setData('hudCleanup', () => cleanups.forEach((fn) => fn()));
|
|
318
|
+
container.once(Phaser.GameObjects.Events.DESTROY, () => {
|
|
319
|
+
const fn = container.getData('hudCleanup');
|
|
320
|
+
fn?.();
|
|
321
|
+
});
|
|
322
|
+
return container;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Panel — a colored rectangle with optional border + rounded corners. Used
|
|
326
|
+
* as a visual frame behind other HUD widgets (no children layout in v1;
|
|
327
|
+
* children live alongside the panel as separate entities).
|
|
328
|
+
*/
|
|
329
|
+
function createPanel(ctx, entity, pos) {
|
|
330
|
+
const v = entity.visual;
|
|
331
|
+
const w = v.width ?? 200;
|
|
332
|
+
const h = v.height ?? 100;
|
|
333
|
+
const container = ctx.scene.add.container(pos.x, pos.y);
|
|
334
|
+
const g = ctx.scene.add.graphics();
|
|
335
|
+
container.add(g);
|
|
336
|
+
const draw = () => {
|
|
337
|
+
g.clear();
|
|
338
|
+
const radius = v.borderRadius ?? 0;
|
|
339
|
+
// Centered draw — see createProgressBar for the rationale.
|
|
340
|
+
const x0 = -w / 2;
|
|
341
|
+
const y0 = -h / 2;
|
|
342
|
+
if (v.backgroundColor) {
|
|
343
|
+
g.fillStyle(parseColor(v.backgroundColor), v.backgroundAlpha ?? 1);
|
|
344
|
+
if (radius > 0)
|
|
345
|
+
g.fillRoundedRect(x0, y0, w, h, radius);
|
|
346
|
+
else
|
|
347
|
+
g.fillRect(x0, y0, w, h);
|
|
348
|
+
}
|
|
349
|
+
if (v.borderColor && (v.borderWidth ?? 0) > 0) {
|
|
350
|
+
g.lineStyle(v.borderWidth ?? 1, parseColor(v.borderColor), 1);
|
|
351
|
+
if (radius > 0)
|
|
352
|
+
g.strokeRoundedRect(x0, y0, w, h, radius);
|
|
353
|
+
else
|
|
354
|
+
g.strokeRect(x0, y0, w, h);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
draw();
|
|
358
|
+
container.setSize(w, h);
|
|
359
|
+
container.setData('editorHitWidth', w);
|
|
360
|
+
container.setData('editorHitHeight', h);
|
|
361
|
+
applyContainerOriginShift(container, entity.anchor.side, w, h);
|
|
362
|
+
container.setData('hudRedraw', draw);
|
|
363
|
+
return container;
|
|
364
|
+
}
|
|
365
|
+
/** Read a numeric source (static or dynamic from registry). */
|
|
366
|
+
function readNumberSource(source, scene) {
|
|
367
|
+
if (source.mode === 'static')
|
|
368
|
+
return source.value;
|
|
369
|
+
const raw = scene.game.registry.get(source.binding);
|
|
370
|
+
if (typeof raw === 'number' && Number.isFinite(raw))
|
|
371
|
+
return raw;
|
|
372
|
+
return source.fallback ?? 0;
|
|
373
|
+
}
|
|
230
374
|
/**
|
|
231
375
|
* Map a 9-grid anchor side to a Phaser origin pair so the rendered widget
|
|
232
376
|
* is positioned correctly relative to the resolved anchor coordinate.
|
|
@@ -596,6 +740,39 @@ export function applyHudPatch(game, entityId, patch) {
|
|
|
596
740
|
image.setDisplaySize(p.width, p.height);
|
|
597
741
|
}
|
|
598
742
|
}
|
|
743
|
+
if (patch.visual && (entity.kind === 'progress-bar' || entity.kind === 'panel')) {
|
|
744
|
+
// Both kinds are container + Graphics. Patches that change geometry
|
|
745
|
+
// (width/height) or styling are handled by mutating the entity record
|
|
746
|
+
// then re-spawning the widget in place — same pattern as icon-button.
|
|
747
|
+
// For binding-source changes on progress-bar, also re-spawn so the
|
|
748
|
+
// registry subscriptions get rewired cleanly.
|
|
749
|
+
const v = entity.visual;
|
|
750
|
+
for (const [k, val] of Object.entries(patch.visual)) {
|
|
751
|
+
if (val !== undefined)
|
|
752
|
+
v[k] = val;
|
|
753
|
+
}
|
|
754
|
+
const reg2 = reg;
|
|
755
|
+
const safeArea2 = safeArea;
|
|
756
|
+
const oldDepth = go.depth;
|
|
757
|
+
go.destroy();
|
|
758
|
+
reg2.unregister(entityId);
|
|
759
|
+
const ctx = {
|
|
760
|
+
scene: hud,
|
|
761
|
+
registry: reg2,
|
|
762
|
+
safeArea: safeArea2,
|
|
763
|
+
resolveAsset: (id) => {
|
|
764
|
+
const m = getManifest(hud);
|
|
765
|
+
const a = m.assets?.find((x) => x.id === id);
|
|
766
|
+
if (!a)
|
|
767
|
+
throw new Error(`[unboxy/hud] manifest has no asset with id '${id}'`);
|
|
768
|
+
return a;
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
const fresh = spawnHudEntity(ctx, entity);
|
|
772
|
+
if (typeof oldDepth === 'number') {
|
|
773
|
+
fresh.setDepth?.(oldDepth);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
599
776
|
if (patch.visual && entity.kind === 'icon-button') {
|
|
600
777
|
// Icon-button is a Container — visual patches require a small refresh.
|
|
601
778
|
// For v1 we just patch the entity record + label/colour fields cheaply;
|
package/dist/scene/types.d.ts
CHANGED
|
@@ -318,8 +318,50 @@ export interface HudIconButtonVisual {
|
|
|
318
318
|
textColor?: string;
|
|
319
319
|
fontSize?: number;
|
|
320
320
|
}
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
/**
|
|
322
|
+
* A progress bar's numeric input. Static = literal number; dynamic = read
|
|
323
|
+
* from `unboxy.gameData` / Phaser's game registry by key. Mirrors
|
|
324
|
+
* `HudTextSource` but for numbers (no prefix/suffix — bar is visual).
|
|
325
|
+
*/
|
|
326
|
+
export type HudNumberSource = {
|
|
327
|
+
mode: 'static';
|
|
328
|
+
value: number;
|
|
329
|
+
} | {
|
|
330
|
+
mode: 'dynamic';
|
|
331
|
+
binding: string;
|
|
332
|
+
/** Used when the binding is unset at runtime. Defaults to 0. */
|
|
333
|
+
fallback?: number;
|
|
334
|
+
};
|
|
335
|
+
export interface HudProgressBarVisual {
|
|
336
|
+
kind: 'progress-bar';
|
|
337
|
+
/** Current value source (e.g. player's HP). */
|
|
338
|
+
value: HudNumberSource;
|
|
339
|
+
/** Max value source (e.g. player's max HP). */
|
|
340
|
+
max: HudNumberSource;
|
|
341
|
+
width?: number;
|
|
342
|
+
height?: number;
|
|
343
|
+
fillColor?: string;
|
|
344
|
+
backgroundColor?: string;
|
|
345
|
+
borderColor?: string | null;
|
|
346
|
+
borderWidth?: number;
|
|
347
|
+
/** Border radius for rounded rectangles. Defaults to 0 (sharp corners). */
|
|
348
|
+
borderRadius?: number;
|
|
349
|
+
}
|
|
350
|
+
export interface HudPanelVisual {
|
|
351
|
+
kind: 'panel';
|
|
352
|
+
width?: number;
|
|
353
|
+
height?: number;
|
|
354
|
+
/** Solid fill colour. Falls back to a transparent panel if omitted. */
|
|
355
|
+
backgroundColor?: string;
|
|
356
|
+
/** Background alpha (independent of widget-level alpha). */
|
|
357
|
+
backgroundAlpha?: number;
|
|
358
|
+
borderColor?: string | null;
|
|
359
|
+
borderWidth?: number;
|
|
360
|
+
/** Border radius for the rect. Defaults to 0. */
|
|
361
|
+
borderRadius?: number;
|
|
362
|
+
}
|
|
363
|
+
export type HudVisual = HudTextVisual | HudImageVisual | HudIconButtonVisual | HudProgressBarVisual | HudPanelVisual;
|
|
364
|
+
export type HudEntityKind = 'text' | 'image' | 'icon-button' | 'progress-bar' | 'panel';
|
|
323
365
|
export interface HudEntityBase {
|
|
324
366
|
id: string;
|
|
325
367
|
/** Optional semantic tag. Behavior code keys off this. */
|
|
@@ -358,7 +400,15 @@ export interface HudIconButtonEntity extends HudEntityBase {
|
|
|
358
400
|
kind: 'icon-button';
|
|
359
401
|
visual: HudIconButtonVisual;
|
|
360
402
|
}
|
|
361
|
-
export
|
|
403
|
+
export interface HudProgressBarEntity extends HudEntityBase {
|
|
404
|
+
kind: 'progress-bar';
|
|
405
|
+
visual: HudProgressBarVisual;
|
|
406
|
+
}
|
|
407
|
+
export interface HudPanelEntity extends HudEntityBase {
|
|
408
|
+
kind: 'panel';
|
|
409
|
+
visual: HudPanelVisual;
|
|
410
|
+
}
|
|
411
|
+
export type HudEntity = HudTextEntity | HudImageEntity | HudIconButtonEntity | HudProgressBarEntity | HudPanelEntity;
|
|
362
412
|
export interface HudScene {
|
|
363
413
|
schemaVersion: number;
|
|
364
414
|
id: string;
|