@umicat/phaser-sdk 1.0.0 → 1.0.1
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/SDK-GUIDE.md +5 -4
- package/dist/core/UmicatGame.js +59 -0
- package/dist/core/phaser-global.d.ts +1 -0
- package/dist/core/phaser-global.js +10 -0
- package/package.json +1 -1
package/SDK-GUIDE.md
CHANGED
|
@@ -626,10 +626,11 @@ class GameScene extends Phaser.Scene {
|
|
|
626
626
|
// ...wire physics + input on `this.player`...
|
|
627
627
|
}
|
|
628
628
|
update() {
|
|
629
|
-
// Safe:
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
//
|
|
629
|
+
// Safe: an async create() suspends update() until it resolves. The SDK
|
|
630
|
+
// gates this in createUmicatGame (since 1.0.1) for EVERY registered
|
|
631
|
+
// scene, so any class field assigned after ANY `await` in create() —
|
|
632
|
+
// loadWorldScene, saves.get(), umicatReady, a fetch — is defined by the
|
|
633
|
+
// first time update() ticks. No `if (!this.player) return;` guard needed.
|
|
633
634
|
this.player.setVelocityX(0);
|
|
634
635
|
}
|
|
635
636
|
}
|
package/dist/core/UmicatGame.js
CHANGED
|
@@ -6,11 +6,70 @@ import { setupEditorModeListener } from '../scene/EditorMode.js';
|
|
|
6
6
|
import { ORIENTATION_DIMENSIONS } from '../orientation.js';
|
|
7
7
|
import { setRenderScriptRegistry, } from '../scene/renderScripts.js';
|
|
8
8
|
import { UmicatHudScene } from '../scene/HudRuntime.js';
|
|
9
|
+
/**
|
|
10
|
+
* Make a Promise-returning `create()` safe against Phaser's update loop.
|
|
11
|
+
*
|
|
12
|
+
* Phaser does NOT await an async `create()`. The moment `create()` suspends
|
|
13
|
+
* at its first `await`, Phaser flips the scene to RUNNING and starts calling
|
|
14
|
+
* `update()` every frame — so any object the game builds *after* that await
|
|
15
|
+
* (a HUD text, the player sprite) is `undefined` for those frames. An
|
|
16
|
+
* `update()` that reads it throws on every frame and the game appears frozen
|
|
17
|
+
* on the loading screen. This is a very easy trap to fall into: the docs
|
|
18
|
+
* pattern `const hi = await saves.get('highScore')` at the top of `create()`
|
|
19
|
+
* is enough to trigger it.
|
|
20
|
+
*
|
|
21
|
+
* We give async `create()` the same guarantee `preload()` already has —
|
|
22
|
+
* "update() does not run until setup is finished" — by wrapping each scene so
|
|
23
|
+
* `update()` is a no-op until the `create()` promise settles. Synchronous
|
|
24
|
+
* `create()` is untouched (it never returns a thenable, so the gate is never
|
|
25
|
+
* armed). Re-runs cleanly across `scene.restart()` since the flag lives on
|
|
26
|
+
* the scene instance and is re-armed each create().
|
|
27
|
+
*/
|
|
28
|
+
function guardAsyncCreate(SceneClass) {
|
|
29
|
+
const proto = SceneClass.prototype;
|
|
30
|
+
// Patch the prototype at most once, even under HMR / repeated factory calls.
|
|
31
|
+
if (proto.__umicatGuarded)
|
|
32
|
+
return;
|
|
33
|
+
const origCreate = proto.create;
|
|
34
|
+
if (typeof origCreate !== 'function')
|
|
35
|
+
return; // no create() → nothing to gate
|
|
36
|
+
proto.__umicatGuarded = true;
|
|
37
|
+
const PENDING = '__umicatCreatePending';
|
|
38
|
+
proto.create = function (...args) {
|
|
39
|
+
const ret = origCreate.apply(this, args);
|
|
40
|
+
if (ret && typeof ret.then === 'function') {
|
|
41
|
+
this[PENDING] = true;
|
|
42
|
+
Promise.resolve(ret)
|
|
43
|
+
.catch((e) => {
|
|
44
|
+
// Surface the failure instead of hanging update() forever.
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.error('[umicat] async create() rejected:', e);
|
|
47
|
+
})
|
|
48
|
+
.finally(() => {
|
|
49
|
+
this[PENDING] = false;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return ret;
|
|
53
|
+
};
|
|
54
|
+
const origUpdate = proto.update;
|
|
55
|
+
if (typeof origUpdate === 'function') {
|
|
56
|
+
proto.update = function (...args) {
|
|
57
|
+
if (this[PENDING])
|
|
58
|
+
return undefined; // setup still running — skip this frame
|
|
59
|
+
return origUpdate.apply(this, args);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
9
63
|
/**
|
|
10
64
|
* Create an Umicat-enhanced Phaser game instance.
|
|
11
65
|
* Includes built-in integrations: screenshot capture, preserveDrawingBuffer, etc.
|
|
12
66
|
*/
|
|
13
67
|
export function createUmicatGame(options) {
|
|
68
|
+
// Harden every game scene against the async-create()/update() race before
|
|
69
|
+
// Phaser instantiates them (see guardAsyncCreate). The auto-registered
|
|
70
|
+
// UmicatHudScene is ours and uses a sync create(), so it's left alone.
|
|
71
|
+
for (const SceneClass of options.scenes)
|
|
72
|
+
guardAsyncCreate(SceneClass);
|
|
14
73
|
const { width, height } = 'orientation' in options && options.orientation
|
|
15
74
|
? ORIENTATION_DIMENSIONS[options.orientation]
|
|
16
75
|
: { width: options.width, height: options.height };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Phaser from 'phaser';
|
|
2
|
+
// phaser3-rex-plugins (e.g. the NinePatch plugin) were written for Phaser 3's
|
|
3
|
+
// UMD build, which set a global `window.Phaser`. Their module bodies reference a
|
|
4
|
+
// bare `Phaser` at eval time (e.g. `Phaser.Utils.Objects.GetValue`). Phaser 4 is
|
|
5
|
+
// pure ESM and does NOT expose a global, so those plugins throw
|
|
6
|
+
// "Phaser is not defined". Expose Phaser on globalThis here.
|
|
7
|
+
//
|
|
8
|
+
// IMPORTANT: this module must be imported BEFORE any `phaser3-rex-plugins/*`
|
|
9
|
+
// import so the global exists before the plugin's top-level code evaluates.
|
|
10
|
+
globalThis.Phaser = Phaser;
|