@unboxy/phaser-sdk 0.2.38 → 0.2.40
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/scene/HudRuntime.js +118 -33
- package/package.json +1 -1
package/dist/scene/HudRuntime.js
CHANGED
|
@@ -101,30 +101,8 @@ const LAYER_DEPTH = {
|
|
|
101
101
|
function createImageOrNinePatch(scene, x, y, width, height, asset, frame) {
|
|
102
102
|
if (asset.ninePatch) {
|
|
103
103
|
const np = asset.ninePatch;
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const factory = scene.add.rexNinePatch;
|
|
107
|
-
if (factory) {
|
|
108
|
-
const columns = [cuts.leftWidth, undefined, cuts.rightWidth];
|
|
109
|
-
const rows = [cuts.topHeight, undefined, cuts.bottomHeight];
|
|
110
|
-
// Per-frame uses rex's 9-arg `(scene, x, y, w, h, key, baseFrame, cols, rows)`
|
|
111
|
-
// form — baseFrame scopes the slicing to that one cell of the spritesheet.
|
|
112
|
-
// rex types baseFrame as string; Phaser accepts either string or number
|
|
113
|
-
// for spritesheet frame access, but passing String() satisfies the typed
|
|
114
|
-
// overload on rex's side.
|
|
115
|
-
if (isPerFrame) {
|
|
116
|
-
return factory.call(scene.add, x, y, width, height, asset.textureKey, String(frame ?? 0), columns, rows);
|
|
117
|
-
}
|
|
118
|
-
// Middle column/row left undefined → that segment stretches to fill
|
|
119
|
-
// the remaining space between the two fixed-width edges.
|
|
120
|
-
return factory.call(scene.add, x, y, width, height, asset.textureKey, columns, rows);
|
|
121
|
-
}
|
|
122
|
-
// Plugin not registered — degrade to a stretched Image rather than crash
|
|
123
|
-
// an entire HUD render. Symptom is "corners distort on resize", not a
|
|
124
|
-
// missing widget. Likely cause: a host that bypassed createUnboxyGame's
|
|
125
|
-
// auto-registration (e.g. an external Phaser.Game instance).
|
|
126
|
-
// eslint-disable-next-line no-console
|
|
127
|
-
console.warn(`[unboxy/hud] rexNinePatch plugin not registered; falling back to stretched Image for asset '${asset.id}'`);
|
|
104
|
+
const cuts = isPerFrameNinePatch(np) ? np.perFrame : np;
|
|
105
|
+
return createCustomNinePatch(scene, x, y, width, height, asset, cuts, frame);
|
|
128
106
|
}
|
|
129
107
|
// Fallback: plain Image. Frame index threaded through for spritesheet
|
|
130
108
|
// assets so per-cell rendering still works without 9-slice.
|
|
@@ -134,6 +112,105 @@ function createImageOrNinePatch(scene, x, y, width, height, asset, frame) {
|
|
|
134
112
|
image.setDisplaySize(width, height);
|
|
135
113
|
return image;
|
|
136
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* 9-slice renderer built directly on Phaser primitives. We tried
|
|
117
|
+
* `phaser3-rex-plugins`' NinePatch first (slice 7.5/7.6) but ran into a
|
|
118
|
+
* RenderTexture sizing bug in Phaser 3.60+ that's hard to work around
|
|
119
|
+
* without monkey-patching: rex's constructor calls `super(scene)` with no
|
|
120
|
+
* dims (defaulting the DynamicTexture to 32×32), then `setSize(w, h)` —
|
|
121
|
+
* which in Phaser 3.60+ only sets display dimensions, not the underlying
|
|
122
|
+
* texture. The 9-slice gets drawn into the small default canvas and
|
|
123
|
+
* displayed stretched. Reproducing this without rex is faster than the
|
|
124
|
+
* monkey-patch dance.
|
|
125
|
+
*
|
|
126
|
+
* <p>Algorithm: split the source texture into 9 sub-frames via
|
|
127
|
+
* `texture.add()` (idempotent — Phaser ignores re-adds), spawn one Phaser
|
|
128
|
+
* Image per sub-frame, position + stretch them inside a Container. Corners
|
|
129
|
+
* keep their native dimensions; edges stretch in one axis; the middle
|
|
130
|
+
* stretches in both. Returns the container, sized to the target dims with
|
|
131
|
+
* an explicit hit area so `setInteractive` works as expected on the
|
|
132
|
+
* icon-button widget.
|
|
133
|
+
*
|
|
134
|
+
* <p>Source layout: `(sx, sy)` is the source-image origin within the
|
|
135
|
+
* texture; for plain Images it's (0,0). For per-frame on a uniform-grid
|
|
136
|
+
* sheet, `frame` selects which cell — the corresponding frame's `cutX/Y`
|
|
137
|
+
* gives the origin.
|
|
138
|
+
*/
|
|
139
|
+
function createCustomNinePatch(scene, x, y, width, height, asset, cuts, frame) {
|
|
140
|
+
const texKey = asset.textureKey;
|
|
141
|
+
const texture = scene.textures.get(texKey);
|
|
142
|
+
// Per-frame variant: look up the specified cell's source rect. Single
|
|
143
|
+
// image: the texture's `__BASE` frame covers the entire image.
|
|
144
|
+
const baseFrame = frame !== undefined
|
|
145
|
+
? texture.get(frame)
|
|
146
|
+
: texture.get('__BASE');
|
|
147
|
+
const sx = baseFrame.cutX;
|
|
148
|
+
const sy = baseFrame.cutY;
|
|
149
|
+
const sw = baseFrame.width;
|
|
150
|
+
const sh = baseFrame.height;
|
|
151
|
+
const L = cuts.leftWidth;
|
|
152
|
+
const R = cuts.rightWidth;
|
|
153
|
+
const T = cuts.topHeight;
|
|
154
|
+
const B = cuts.bottomHeight;
|
|
155
|
+
// Source middle widths/heights (the part that stretches).
|
|
156
|
+
const midSrcW = Math.max(0, sw - L - R);
|
|
157
|
+
const midSrcH = Math.max(0, sh - T - B);
|
|
158
|
+
// Target middle widths/heights — what remains after the four corners are
|
|
159
|
+
// placed at their fixed sizes. When the widget is sized smaller than
|
|
160
|
+
// L+R / T+B, the corners overlap; rare but handled by clamping to 0
|
|
161
|
+
// rather than producing negative-size sprites.
|
|
162
|
+
const midDstW = Math.max(0, width - L - R);
|
|
163
|
+
const midDstH = Math.max(0, height - T - B);
|
|
164
|
+
const container = scene.add.container(x, y);
|
|
165
|
+
// Register a sub-frame on the source texture (idempotent) and add a
|
|
166
|
+
// Phaser Image rendering that frame at the target position + size.
|
|
167
|
+
// Position is relative to container's origin; Image origin (0, 0) makes
|
|
168
|
+
// (dx, dy) the top-left of the sub-region.
|
|
169
|
+
const frameSuffix = frame !== undefined ? `__f${frame}` : '';
|
|
170
|
+
const addSubFrame = (suffix, fx, fy, fw, fh, dx, dy, dw, dh) => {
|
|
171
|
+
if (fw <= 0 || fh <= 0 || dw <= 0 || dh <= 0)
|
|
172
|
+
return;
|
|
173
|
+
const subKey = `${texKey}${frameSuffix}_${suffix}`;
|
|
174
|
+
if (!texture.has(subKey)) {
|
|
175
|
+
texture.add(subKey, 0, sx + fx, sy + fy, fw, fh);
|
|
176
|
+
}
|
|
177
|
+
const img = scene.add.image(dx, dy, texKey, subKey);
|
|
178
|
+
img.setOrigin(0, 0);
|
|
179
|
+
img.setDisplaySize(dw, dh);
|
|
180
|
+
container.add(img);
|
|
181
|
+
};
|
|
182
|
+
// Layout offsets — container's local origin is at its center, so the
|
|
183
|
+
// 9-slice patch spans (-width/2, -height/2) to (width/2, height/2).
|
|
184
|
+
const x0 = -width / 2;
|
|
185
|
+
const y0 = -height / 2;
|
|
186
|
+
const xL = x0 + L;
|
|
187
|
+
const xR = x0 + width - R;
|
|
188
|
+
const yT = y0 + T;
|
|
189
|
+
const yB = y0 + height - B;
|
|
190
|
+
// Source frame offsets within the base frame.
|
|
191
|
+
const fxL = 0;
|
|
192
|
+
const fxM = L;
|
|
193
|
+
const fxR = sw - R;
|
|
194
|
+
const fyT = 0;
|
|
195
|
+
const fyM = T;
|
|
196
|
+
const fyB = sh - B;
|
|
197
|
+
// Top row
|
|
198
|
+
addSubFrame('tl', fxL, fyT, L, T, x0, y0, L, T);
|
|
199
|
+
addSubFrame('tm', fxM, fyT, midSrcW, T, xL, y0, midDstW, T);
|
|
200
|
+
addSubFrame('tr', fxR, fyT, R, T, xR, y0, R, T);
|
|
201
|
+
// Middle row
|
|
202
|
+
addSubFrame('ml', fxL, fyM, L, midSrcH, x0, yT, L, midDstH);
|
|
203
|
+
addSubFrame('mm', fxM, fyM, midSrcW, midSrcH, xL, yT, midDstW, midDstH);
|
|
204
|
+
addSubFrame('mr', fxR, fyM, R, midSrcH, xR, yT, R, midDstH);
|
|
205
|
+
// Bottom row
|
|
206
|
+
addSubFrame('bl', fxL, fyB, L, B, x0, yB, L, B);
|
|
207
|
+
addSubFrame('bm', fxM, fyB, midSrcW, B, xL, yB, midDstW, B);
|
|
208
|
+
addSubFrame('br', fxR, fyB, R, B, xR, yB, R, B);
|
|
209
|
+
// Explicit hit area so setInteractive works (Phaser containers have no
|
|
210
|
+
// intrinsic bounds; the icon-button widget calls setInteractive on this).
|
|
211
|
+
container.setSize(width, height);
|
|
212
|
+
return container;
|
|
213
|
+
}
|
|
137
214
|
/**
|
|
138
215
|
* Spawn a HUD widget into the scene. The returned GameObject is recorded in
|
|
139
216
|
* the entity registry so the editor + behavior code can find it by id.
|
|
@@ -216,21 +293,29 @@ function createImage(ctx, entity, pos) {
|
|
|
216
293
|
container.setSize(w, h);
|
|
217
294
|
return container;
|
|
218
295
|
}
|
|
219
|
-
// 9-slice path. When the asset has `ninePatch` metadata the SDK renders
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
296
|
+
// 9-slice path. When the asset has `ninePatch` metadata, the SDK renders
|
|
297
|
+
// via our custom Container-based NinePatch (corners fixed; edges + center
|
|
298
|
+
// stretch). Missing width/height falls back to the texture's native source
|
|
299
|
+
// dims so existing image-without-dims data still works.
|
|
223
300
|
if (asset.ninePatch) {
|
|
224
301
|
const tex = ctx.scene.textures.get(asset.textureKey);
|
|
225
302
|
const src = tex?.getSourceImage();
|
|
226
303
|
const w = entity.visual.width ?? src?.width ?? 64;
|
|
227
304
|
const h = entity.visual.height ?? src?.height ?? 64;
|
|
228
305
|
const np = createImageOrNinePatch(ctx.scene, pos.x, pos.y, w, h, asset);
|
|
229
|
-
if (entity.visual.
|
|
230
|
-
np.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
306
|
+
if (typeof entity.visual.alpha === 'number') {
|
|
307
|
+
np.setAlpha?.(entity.visual.alpha);
|
|
308
|
+
}
|
|
309
|
+
// The custom NinePatch is a Container with children centered around
|
|
310
|
+
// (0, 0). Use the same origin-shift trick the container widgets use so
|
|
311
|
+
// the anchor side resolves correctly. Tint isn't supported on
|
|
312
|
+
// Container; image widgets needing tint should bypass ninePatch.
|
|
313
|
+
if (np instanceof Phaser.GameObjects.Container) {
|
|
314
|
+
applyContainerOriginShift(np, entity.anchor.side, w, h);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
applyOriginFromAnchor(np, entity.anchor.side);
|
|
318
|
+
}
|
|
234
319
|
return np;
|
|
235
320
|
}
|
|
236
321
|
const image = entity.visual.frame !== undefined
|