@unboxy/phaser-sdk 0.2.39 → 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 -40
- package/package.json +1 -1
package/dist/scene/HudRuntime.js
CHANGED
|
@@ -101,37 +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
|
-
// Construct at a placeholder 1×1 size, then resize() to target. Reason:
|
|
111
|
-
// Phaser 3.60+ refactored RenderTexture so that setSize() (which rex's
|
|
112
|
-
// constructor calls) only updates the gameobject's display dimensions,
|
|
113
|
-
// NOT the underlying DynamicTexture's canvas. The 9-slice draw pass
|
|
114
|
-
// then draws into the default 32×32 canvas and only the top-left
|
|
115
|
-
// corner of the patch is visible at 200×64 display. resize() — unlike
|
|
116
|
-
// setSize() — does call DynamicTexture.setSize, but rex's resize early-
|
|
117
|
-
// returns when current dims match target. Forcing initial dims of 1×1
|
|
118
|
-
// ensures resize() to (width, height) does real work.
|
|
119
|
-
let np;
|
|
120
|
-
if (isPerFrame) {
|
|
121
|
-
np = factory.call(scene.add, x, y, 1, 1, asset.textureKey, String(frame ?? 0), columns, rows);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
np = factory.call(scene.add, x, y, 1, 1, asset.textureKey, columns, rows);
|
|
125
|
-
}
|
|
126
|
-
np.resize(width, height);
|
|
127
|
-
return np;
|
|
128
|
-
}
|
|
129
|
-
// Plugin not registered — degrade to a stretched Image rather than crash
|
|
130
|
-
// an entire HUD render. Symptom is "corners distort on resize", not a
|
|
131
|
-
// missing widget. Likely cause: a host that bypassed createUnboxyGame's
|
|
132
|
-
// auto-registration (e.g. an external Phaser.Game instance).
|
|
133
|
-
// eslint-disable-next-line no-console
|
|
134
|
-
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);
|
|
135
106
|
}
|
|
136
107
|
// Fallback: plain Image. Frame index threaded through for spritesheet
|
|
137
108
|
// assets so per-cell rendering still works without 9-slice.
|
|
@@ -141,6 +112,105 @@ function createImageOrNinePatch(scene, x, y, width, height, asset, frame) {
|
|
|
141
112
|
image.setDisplaySize(width, height);
|
|
142
113
|
return image;
|
|
143
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
|
+
}
|
|
144
214
|
/**
|
|
145
215
|
* Spawn a HUD widget into the scene. The returned GameObject is recorded in
|
|
146
216
|
* the entity registry so the editor + behavior code can find it by id.
|
|
@@ -223,21 +293,29 @@ function createImage(ctx, entity, pos) {
|
|
|
223
293
|
container.setSize(w, h);
|
|
224
294
|
return container;
|
|
225
295
|
}
|
|
226
|
-
// 9-slice path. When the asset has `ninePatch` metadata the SDK renders
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
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.
|
|
230
300
|
if (asset.ninePatch) {
|
|
231
301
|
const tex = ctx.scene.textures.get(asset.textureKey);
|
|
232
302
|
const src = tex?.getSourceImage();
|
|
233
303
|
const w = entity.visual.width ?? src?.width ?? 64;
|
|
234
304
|
const h = entity.visual.height ?? src?.height ?? 64;
|
|
235
305
|
const np = createImageOrNinePatch(ctx.scene, pos.x, pos.y, w, h, asset);
|
|
236
|
-
if (entity.visual.
|
|
237
|
-
np.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
+
}
|
|
241
319
|
return np;
|
|
242
320
|
}
|
|
243
321
|
const image = entity.visual.frame !== undefined
|