emeraldengine 2.2.1 → 3.0.0
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 +2359 -968
- package/dist/types/index.d.ts +72 -32
- package/dist/types/src/Animator.d.ts +50 -0
- package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
- package/dist/types/src/Camera.d.ts +122 -0
- package/dist/types/src/CameraController.d.ts +107 -0
- package/dist/types/src/CanvasText.d.ts +91 -0
- package/dist/types/src/CollisionLayers.d.ts +58 -0
- package/dist/types/{Color.d.ts → src/Color.d.ts} +5 -6
- package/dist/types/src/Coroutine.d.ts +111 -0
- package/dist/types/src/DebugOverlay.d.ts +86 -0
- package/dist/types/src/Drawable.d.ts +271 -0
- package/dist/types/src/Easing.d.ts +22 -0
- package/dist/types/src/Emerald.d.ts +420 -0
- package/dist/types/src/EmeraldDB.d.ts +159 -0
- package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
- package/dist/types/src/GLUtils.d.ts +4 -0
- package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
- package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +60 -23
- package/dist/types/src/Interpolator.d.ts +66 -0
- package/dist/types/src/Material.d.ts +87 -0
- package/dist/types/src/MathUtils.d.ts +80 -0
- package/dist/types/src/ParticleEmitter.d.ts +131 -0
- package/dist/types/{Physics.d.ts → src/Physics.d.ts} +88 -20
- package/dist/types/src/Pool.d.ts +53 -0
- package/dist/types/src/PostEffects.d.ts +68 -0
- package/dist/types/src/PostProcessor.d.ts +124 -0
- package/dist/types/src/RenderTarget.d.ts +56 -0
- package/dist/types/src/Scene.d.ts +62 -0
- package/dist/types/src/ScreenEffects.d.ts +111 -0
- package/dist/types/src/Serializer.d.ts +86 -0
- package/dist/types/src/Shaders.d.ts +2 -0
- package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
- package/dist/types/src/SpatialGrid.d.ts +50 -0
- package/dist/types/src/SpriteBatch.d.ts +89 -0
- package/dist/types/src/StateMachine.d.ts +59 -0
- package/dist/types/src/Storage.d.ts +89 -0
- package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
- package/dist/types/src/TextureAtlas.d.ts +55 -0
- package/dist/types/src/Tilemap.d.ts +91 -0
- package/dist/types/src/Time.d.ts +53 -0
- package/dist/types/src/Timer.d.ts +54 -0
- package/dist/types/src/Transform.d.ts +94 -0
- package/dist/types/src/Tween.d.ts +81 -0
- package/dist/types/src/UI.d.ts +121 -0
- package/dist/types/src/components/Behaviour.d.ts +71 -0
- package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
- package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
- package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
- package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
- package/dist/types/src/components/Collider.d.ts +96 -0
- package/dist/types/src/components/GameObject.d.ts +135 -0
- package/dist/types/src/components/RigidBody.d.ts +183 -0
- package/dist/types/src/importers/Aseprite.d.ts +79 -0
- package/dist/types/src/importers/TiledMap.d.ts +62 -0
- package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
- package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
- package/dist/types/src/managers/AssetManager.d.ts +116 -0
- package/dist/types/src/managers/AudioManager.d.ts +259 -0
- package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
- package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
- package/dist/types/src/managers/GLManager.d.ts +84 -0
- package/dist/types/src/managers/GLState.d.ts +37 -0
- package/dist/types/src/managers/IDManager.d.ts +31 -0
- package/dist/types/src/managers/InputManager.d.ts +290 -0
- package/dist/types/src/managers/NetworkManager.d.ts +93 -0
- package/dist/types/src/managers/RenderStats.d.ts +34 -0
- package/dist/types/src/managers/SceneManager.d.ts +44 -0
- package/dist/types/src/managers/ShaderManager.d.ts +55 -0
- package/dist/types/src/managers/TextureManager.d.ts +102 -0
- package/dist/types/src/particlesystem/Particle.d.ts +64 -0
- package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
- package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
- package/index.js +72 -0
- package/package.json +74 -60
- package/src/Animator.js +95 -0
- package/src/BitmapText.js +6 -5
- package/src/Camera.js +183 -0
- package/src/CameraController.js +192 -0
- package/src/CanvasText.js +281 -0
- package/src/CollisionLayers.js +86 -0
- package/src/Color.js +18 -18
- package/src/Coroutine.js +259 -0
- package/src/DebugOverlay.js +246 -0
- package/src/Drawable.js +842 -582
- package/src/Easing.js +57 -0
- package/src/Emerald.js +1150 -459
- package/src/EmeraldDB.js +328 -0
- package/src/FPSCounter.js +43 -43
- package/src/GLUtils.js +60 -67
- package/src/Instance.js +41 -5
- package/src/InstancedTexture.js +251 -118
- package/src/Interpolator.js +124 -0
- package/src/Material.js +202 -0
- package/src/MathUtils.js +133 -0
- package/src/ParticleEmitter.js +284 -0
- package/src/Physics.js +186 -5
- package/src/Pool.js +85 -0
- package/src/PostEffects.js +296 -0
- package/src/PostProcessor.js +304 -0
- package/src/RenderTarget.js +134 -0
- package/src/Scene.js +115 -83
- package/src/ScreenEffects.js +266 -0
- package/src/Serializer.js +131 -0
- package/src/Shaders.js +150 -165
- package/src/Shapes.js +118 -129
- package/src/SpatialGrid.js +111 -0
- package/src/SpriteBatch.js +299 -0
- package/src/StateMachine.js +82 -0
- package/src/Storage.js +175 -47
- package/src/Texture.js +58 -67
- package/src/TextureAtlas.js +96 -0
- package/src/Tilemap.js +274 -0
- package/src/Time.js +51 -6
- package/src/Timer.js +99 -0
- package/src/Transform.js +100 -7
- package/src/Tween.js +160 -0
- package/src/UI.js +394 -0
- package/src/components/Behaviour.js +90 -0
- package/src/components/BoxCollider.js +22 -3
- package/src/components/CircleCollider.js +19 -3
- package/src/components/CircleColliderDebug.js +24 -24
- package/src/components/Collider.js +140 -34
- package/src/components/GameObject.js +130 -21
- package/src/components/RigidBody.js +123 -2
- package/src/importers/Aseprite.js +142 -0
- package/src/importers/TiledMap.js +158 -0
- package/src/lights/DirectionalLight.js +6 -15
- package/src/lights/PointLight.js +4 -4
- package/src/managers/AssetManager.js +239 -0
- package/src/managers/AudioManager.js +565 -146
- package/src/managers/EventManager.js +488 -477
- package/src/managers/GLManager.js +57 -0
- package/src/managers/GLState.js +70 -0
- package/src/managers/IDManager.js +24 -2
- package/src/managers/InputManager.js +779 -0
- package/src/managers/NetworkManager.js +178 -0
- package/src/managers/RenderStats.js +34 -0
- package/src/managers/SceneManager.js +30 -0
- package/src/managers/ShaderManager.js +0 -2
- package/src/managers/TextureManager.js +218 -0
- package/src/particlesystem/Particle.js +82 -7
- package/src/particlesystem/ParticleSettings.js +21 -3
- package/src/particlesystem/Particles.js +80 -31
- package/dist/types/Drawable.d.ts +0 -157
- package/dist/types/Emerald.d.ts +0 -73
- package/dist/types/GLUtils.d.ts +0 -6
- package/dist/types/Scene.d.ts +0 -39
- package/dist/types/Shaders.d.ts +0 -4
- package/dist/types/Storage.d.ts +0 -46
- package/dist/types/Time.d.ts +0 -22
- package/dist/types/Transform.d.ts +0 -41
- package/dist/types/components/BoxColliderDebug.d.ts +0 -19
- package/dist/types/components/CircleColliderDebug.d.ts +0 -19
- package/dist/types/components/Collider.d.ts +0 -53
- package/dist/types/components/GameObject.d.ts +0 -72
- package/dist/types/components/RigidBody.d.ts +0 -104
- package/dist/types/managers/AudioManager.d.ts +0 -60
- package/dist/types/managers/GLManager.d.ts +0 -47
- package/dist/types/managers/IDManager.d.ts +0 -21
- package/dist/types/managers/SceneManager.d.ts +0 -22
- package/dist/types/particlesystem/Particle.d.ts +0 -42
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import Interpolator from "../Interpolator.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class NetworkManager
|
|
5
|
+
* @description A thin, optional multiplayer layer over Colyseus. `colyseus.js`
|
|
6
|
+
* is an optional peer dependency and is imported dynamically, so the engine has
|
|
7
|
+
* no hard dependency on it — games that don't use networking never load it.
|
|
8
|
+
*
|
|
9
|
+
* It wraps connection/room lifecycle, exposes a small event API
|
|
10
|
+
* (onStateChange / onMessage / onAdd / onRemove / onLeave), and bundles an
|
|
11
|
+
* {@link Interpolator} so remote entities can be rendered smoothly.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const net = new NetworkManager();
|
|
15
|
+
* await net.connect("wss://my-server");
|
|
16
|
+
* const room = await net.join("arena", { name: "P1" });
|
|
17
|
+
* net.onMessage("hit", (msg) => applyHit(msg));
|
|
18
|
+
* net.onStateChange((state) => {
|
|
19
|
+
* for (const [id, p] of state.players) net.interpolator.push(id, p, net.now());
|
|
20
|
+
* });
|
|
21
|
+
* // each frame:
|
|
22
|
+
* const pos = net.interpolator.sample(remoteId, net.now());
|
|
23
|
+
*/
|
|
24
|
+
class NetworkManager {
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.client = null;
|
|
27
|
+
this.room = null;
|
|
28
|
+
this.interpolator = new Interpolator(options.interpolation || {});
|
|
29
|
+
/** @private */
|
|
30
|
+
this._messageHandlers = new Map();
|
|
31
|
+
/** @private */
|
|
32
|
+
this._stateHandlers = [];
|
|
33
|
+
/** @private */
|
|
34
|
+
this._leaveHandlers = [];
|
|
35
|
+
/** @private */
|
|
36
|
+
this._startTime = NetworkManager._now();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @method connect
|
|
41
|
+
* @description Dynamically imports colyseus.js and creates a Client.
|
|
42
|
+
* @param {string} endpoint - The server endpoint (e.g. "wss://host:2567")
|
|
43
|
+
* @returns {Promise<NetworkManager>} - this
|
|
44
|
+
*/
|
|
45
|
+
async connect(endpoint) {
|
|
46
|
+
let colyseus;
|
|
47
|
+
try {
|
|
48
|
+
colyseus = await import("colyseus.js");
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"[NetworkManager] colyseus.js is not installed. Run `npm install colyseus.js` to use networking."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const Client =
|
|
55
|
+
colyseus.Client || (colyseus.default && colyseus.default.Client);
|
|
56
|
+
this.client = new Client(endpoint);
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @method join
|
|
62
|
+
* @description Joins (or creates) a room by name and wires up the event hooks.
|
|
63
|
+
* @param {string} roomName - The room name
|
|
64
|
+
* @param {Object} [options] - Join options forwarded to the server
|
|
65
|
+
* @returns {Promise<Object>} - The joined room
|
|
66
|
+
*/
|
|
67
|
+
async join(roomName, options = {}) {
|
|
68
|
+
if (!this.client) {
|
|
69
|
+
throw new Error("[NetworkManager] Call connect() before join().");
|
|
70
|
+
}
|
|
71
|
+
this.room = await this.client.joinOrCreate(roomName, options);
|
|
72
|
+
this._wireRoom(this.room);
|
|
73
|
+
return this.room;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @private */
|
|
77
|
+
_wireRoom(room) {
|
|
78
|
+
if (typeof room.onStateChange === "function") {
|
|
79
|
+
room.onStateChange((state) => {
|
|
80
|
+
for (const handler of this._stateHandlers) handler(state);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (typeof room.onLeave === "function") {
|
|
84
|
+
room.onLeave((code) => {
|
|
85
|
+
for (const handler of this._leaveHandlers) handler(code);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (typeof room.onMessage === "function") {
|
|
89
|
+
for (const [type, handlers] of this._messageHandlers) {
|
|
90
|
+
room.onMessage(type, (payload) => handlers.forEach((h) => h(payload)));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @method onStateChange
|
|
97
|
+
* @description Registers a handler for full room-state updates.
|
|
98
|
+
*/
|
|
99
|
+
onStateChange(handler) {
|
|
100
|
+
this._stateHandlers.push(handler);
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @method onMessage
|
|
106
|
+
* @description Registers a handler for a server message type.
|
|
107
|
+
*/
|
|
108
|
+
onMessage(type, handler) {
|
|
109
|
+
if (!this._messageHandlers.has(type)) {
|
|
110
|
+
this._messageHandlers.set(type, []);
|
|
111
|
+
if (this.room && typeof this.room.onMessage === "function") {
|
|
112
|
+
this.room.onMessage(type, (payload) =>
|
|
113
|
+
this._messageHandlers.get(type).forEach((h) => h(payload))
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this._messageHandlers.get(type).push(handler);
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @method onLeave
|
|
123
|
+
* @description Registers a handler invoked when the room is left.
|
|
124
|
+
*/
|
|
125
|
+
onLeave(handler) {
|
|
126
|
+
this._leaveHandlers.push(handler);
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @method send
|
|
132
|
+
* @description Sends a typed message to the server.
|
|
133
|
+
*/
|
|
134
|
+
send(type, payload) {
|
|
135
|
+
if (this.room && typeof this.room.send === "function") {
|
|
136
|
+
this.room.send(type, payload);
|
|
137
|
+
}
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @method now
|
|
143
|
+
* @description Seconds since this manager was created — a convenient clock for
|
|
144
|
+
* feeding the interpolator.
|
|
145
|
+
* @returns {number}
|
|
146
|
+
*/
|
|
147
|
+
now() {
|
|
148
|
+
return (NetworkManager._now() - this._startTime) / 1000;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @method leave
|
|
153
|
+
* @description Leaves the current room.
|
|
154
|
+
*/
|
|
155
|
+
async leave() {
|
|
156
|
+
if (this.room && typeof this.room.leave === "function") {
|
|
157
|
+
await this.room.leave();
|
|
158
|
+
}
|
|
159
|
+
this.room = null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @method sessionId
|
|
164
|
+
* @returns {string|null} - This client's session id, if joined.
|
|
165
|
+
*/
|
|
166
|
+
get sessionId() {
|
|
167
|
+
return this.room ? this.room.sessionId : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @private */
|
|
171
|
+
static _now() {
|
|
172
|
+
return typeof performance !== "undefined" && performance.now
|
|
173
|
+
? performance.now()
|
|
174
|
+
: Date.now();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export default NetworkManager;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class RenderStats
|
|
3
|
+
* @description Frame-level render counters, incremented by every draw site in
|
|
4
|
+
* the engine (Drawable, InstancedTexture, SpriteBatch, post-processing) and
|
|
5
|
+
* reset at the start of each drawScene. Read the previous completed frame via
|
|
6
|
+
* `RenderStats.frame` or `emerald.getRenderStats()` — DebugOverlay shows it
|
|
7
|
+
* automatically.
|
|
8
|
+
*
|
|
9
|
+
* - drawCalls: GPU draw commands issued (the batching win shows up here)
|
|
10
|
+
* - quads: sprites/shapes drawn, counting every instance in a batch
|
|
11
|
+
* - textureBinds: texture switches (high numbers = poor batching order)
|
|
12
|
+
*/
|
|
13
|
+
class RenderStats {
|
|
14
|
+
/**
|
|
15
|
+
* @method beginFrame
|
|
16
|
+
* @description Snapshots the counters gathered since the previous call into
|
|
17
|
+
* `frame` and zeroes the accumulators. Called by Emerald.drawScene.
|
|
18
|
+
*/
|
|
19
|
+
static beginFrame() {
|
|
20
|
+
RenderStats.frame.drawCalls = RenderStats.drawCalls;
|
|
21
|
+
RenderStats.frame.quads = RenderStats.quads;
|
|
22
|
+
RenderStats.frame.textureBinds = RenderStats.textureBinds;
|
|
23
|
+
RenderStats.drawCalls = 0;
|
|
24
|
+
RenderStats.quads = 0;
|
|
25
|
+
RenderStats.textureBinds = 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
RenderStats.drawCalls = 0;
|
|
30
|
+
RenderStats.quads = 0;
|
|
31
|
+
RenderStats.textureBinds = 0;
|
|
32
|
+
RenderStats.frame = { drawCalls: 0, quads: 0, textureBinds: 0 };
|
|
33
|
+
|
|
34
|
+
export default RenderStats;
|
|
@@ -20,6 +20,36 @@ class SceneManager {
|
|
|
20
20
|
static getScene() {
|
|
21
21
|
return SceneManager.scene;
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @method transitionTo
|
|
26
|
+
* @description Switches the active scene, optionally behind a ScreenEffects
|
|
27
|
+
* fade. With `screenEffects` provided the screen fades out, the scene is
|
|
28
|
+
* swapped (and your optional `onSwap` runs) while covered, then fades back in.
|
|
29
|
+
* Without it the swap happens immediately.
|
|
30
|
+
*
|
|
31
|
+
* @param {Scene} scene - The scene to make active
|
|
32
|
+
* @param {Object} [options]
|
|
33
|
+
* @param {ScreenEffects} [options.screenEffects] - Effects layer to fade with
|
|
34
|
+
* @param {Function} [options.onSwap] - `(scene) => void|Promise`, run during
|
|
35
|
+
* the covered swap (e.g. (re)build the new scene's contents)
|
|
36
|
+
* @param {number} [options.duration] - Fade duration (seconds)
|
|
37
|
+
* @param {Color} [options.color] - Fade color
|
|
38
|
+
* @returns {Promise<Scene>} - Resolves with the new scene
|
|
39
|
+
*/
|
|
40
|
+
static async transitionTo(scene, options = {}) {
|
|
41
|
+
const { screenEffects, onSwap, ...fadeOptions } = options;
|
|
42
|
+
const swap = async () => {
|
|
43
|
+
SceneManager.setScene(scene);
|
|
44
|
+
if (typeof onSwap === "function") await onSwap(scene);
|
|
45
|
+
};
|
|
46
|
+
if (screenEffects && typeof screenEffects.transition === "function") {
|
|
47
|
+
await screenEffects.transition(swap, fadeOptions);
|
|
48
|
+
} else {
|
|
49
|
+
await swap();
|
|
50
|
+
}
|
|
51
|
+
return scene;
|
|
52
|
+
}
|
|
23
53
|
}
|
|
24
54
|
|
|
25
55
|
SceneManager.scene = null;
|
|
@@ -109,7 +109,6 @@ export class ShaderManager {
|
|
|
109
109
|
return {
|
|
110
110
|
vertexPosition: this.gl.getAttribLocation(program, "aVertexPosition"),
|
|
111
111
|
textureCoord: this.gl.getAttribLocation(program, "aTextureCoord"),
|
|
112
|
-
// Add other attributes as needed
|
|
113
112
|
};
|
|
114
113
|
}
|
|
115
114
|
|
|
@@ -127,7 +126,6 @@ export class ShaderManager {
|
|
|
127
126
|
),
|
|
128
127
|
modelViewMatrix: this.gl.getUniformLocation(program, "uModelViewMatrix"),
|
|
129
128
|
sampler: this.gl.getUniformLocation(program, "uSampler"),
|
|
130
|
-
// Add other uniforms as needed
|
|
131
129
|
};
|
|
132
130
|
}
|
|
133
131
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import GLManager from "./GLManager.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class TextureManager
|
|
5
|
+
* @description Caches loaded images and GL textures so that many drawables
|
|
6
|
+
* sharing the same texture path download and upload the image only once.
|
|
7
|
+
*
|
|
8
|
+
* Textures are immutable after upload in this engine, so the same GL texture
|
|
9
|
+
* object can safely be bound by any number of objects. The GL-texture cache is
|
|
10
|
+
* keyed by path *and* filter mode, because pixel-art (NEAREST) and smooth
|
|
11
|
+
* (LINEAR) sampling require separate texture objects.
|
|
12
|
+
*/
|
|
13
|
+
class TextureManager {
|
|
14
|
+
/**
|
|
15
|
+
* @method loadImage
|
|
16
|
+
* @description Loads (and caches) an HTMLImageElement for a given path.
|
|
17
|
+
* Concurrent requests for the same path share a single in-flight load.
|
|
18
|
+
* @param {string} path - The path (or data URL) of the image
|
|
19
|
+
* @returns {Promise<HTMLImageElement>} - The loaded image
|
|
20
|
+
*/
|
|
21
|
+
static loadImage(path) {
|
|
22
|
+
if (TextureManager.images.has(path)) {
|
|
23
|
+
return TextureManager.images.get(path);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const promise = new Promise((resolve, reject) => {
|
|
27
|
+
const image = new Image();
|
|
28
|
+
image.crossOrigin = "Anonymous";
|
|
29
|
+
image.addEventListener("load", () => resolve(image));
|
|
30
|
+
image.addEventListener("error", () => {
|
|
31
|
+
TextureManager.images.delete(path);
|
|
32
|
+
reject(
|
|
33
|
+
new Error(
|
|
34
|
+
`[TextureManager.js] > Failed to load image: ${path}. Check that the file exists and the path is correct.`
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
image.src = path;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
TextureManager.images.set(path, promise);
|
|
42
|
+
return promise;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @method getTexture
|
|
47
|
+
* @description Returns a cached GL texture for the given path, creating and
|
|
48
|
+
* uploading it on first request. Subsequent calls reuse the same texture.
|
|
49
|
+
* @param {string} path - The path (or data URL) of the texture
|
|
50
|
+
* @param {boolean} pixelart - Whether to sample with NEAREST (pixel art) filtering
|
|
51
|
+
* @returns {Promise<{texture: WebGLTexture, width: number, height: number}>}
|
|
52
|
+
*/
|
|
53
|
+
static getTexture(path, pixelart = false) {
|
|
54
|
+
const key = `${path}|${pixelart ? "nearest" : "linear"}`;
|
|
55
|
+
if (TextureManager.textures.has(key)) {
|
|
56
|
+
return TextureManager.textures.get(key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const promise = TextureManager._upload(path, pixelart);
|
|
60
|
+
TextureManager.textures.set(key, promise);
|
|
61
|
+
return promise;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @method _upload
|
|
66
|
+
* @description Loads the image (via the shared image cache) and uploads it
|
|
67
|
+
* as a GL texture with the requested filtering.
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
static _upload(path, pixelart) {
|
|
71
|
+
return TextureManager.loadImage(path).then((image) => {
|
|
72
|
+
const gl = GLManager.getGL();
|
|
73
|
+
if (!gl) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"[TextureManager] > No GL context set. Create an Emerald instance before loading textures."
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const texture = gl.createTexture();
|
|
80
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
81
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
82
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
83
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
84
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
85
|
+
const filter = pixelart ? gl.NEAREST : gl.LINEAR;
|
|
86
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
|
|
87
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
88
|
+
gl.texImage2D(
|
|
89
|
+
gl.TEXTURE_2D,
|
|
90
|
+
0,
|
|
91
|
+
gl.RGBA,
|
|
92
|
+
gl.RGBA,
|
|
93
|
+
gl.UNSIGNED_BYTE,
|
|
94
|
+
image
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return { texture, width: image.width, height: image.height };
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @method restoreAll
|
|
103
|
+
* @description Re-uploads every cached texture after a WebGL context loss.
|
|
104
|
+
* The image cache survives the loss, so this is upload-only (no network).
|
|
105
|
+
* Reference counts are untouched — they track logical ownership by
|
|
106
|
+
* drawables, which still exist.
|
|
107
|
+
*/
|
|
108
|
+
static restoreAll() {
|
|
109
|
+
for (const key of Array.from(TextureManager.textures.keys())) {
|
|
110
|
+
const sep = key.lastIndexOf("|");
|
|
111
|
+
const path = key.slice(0, sep);
|
|
112
|
+
const pixelart = key.slice(sep + 1) === "nearest";
|
|
113
|
+
TextureManager.textures.set(key, TextureManager._upload(path, pixelart));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @method preload
|
|
119
|
+
* @description Eagerly loads a list of texture paths so they are ready (and
|
|
120
|
+
* cached) before the first frame that needs them.
|
|
121
|
+
* @param {string[]} paths - The texture paths to preload
|
|
122
|
+
* @param {boolean} pixelart - Whether to use pixel-art filtering
|
|
123
|
+
* @returns {Promise<void>}
|
|
124
|
+
*/
|
|
125
|
+
static async preload(paths, pixelart = false) {
|
|
126
|
+
await Promise.all(
|
|
127
|
+
paths.map((path) => TextureManager.getTexture(path, pixelart))
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @method has
|
|
133
|
+
* @description Checks whether a GL texture is already cached for a path
|
|
134
|
+
* @param {string} path - The texture path
|
|
135
|
+
* @param {boolean} pixelart - Whether the pixel-art variant is meant
|
|
136
|
+
* @returns {boolean}
|
|
137
|
+
*/
|
|
138
|
+
static has(path, pixelart = false) {
|
|
139
|
+
return TextureManager.textures.has(
|
|
140
|
+
`${path}|${pixelart ? "nearest" : "linear"}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @method retain
|
|
146
|
+
* @description Increments the reference count for a cached texture. Every
|
|
147
|
+
* Drawable that resolves a texture through getTexture() retains it; when the
|
|
148
|
+
* last user releases it, the GL texture is freed. Call retain/release in
|
|
149
|
+
* pairs (Drawable.dispose does the release for you).
|
|
150
|
+
* @param {string} path - The texture path
|
|
151
|
+
* @param {boolean} pixelart - Whether the pixel-art variant is meant
|
|
152
|
+
*/
|
|
153
|
+
static retain(path, pixelart = false) {
|
|
154
|
+
const key = `${path}|${pixelart ? "nearest" : "linear"}`;
|
|
155
|
+
TextureManager.refs.set(key, (TextureManager.refs.get(key) || 0) + 1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @method release
|
|
160
|
+
* @description Decrements a texture's reference count; when it reaches zero
|
|
161
|
+
* the GL texture is deleted and dropped from the cache (the CPU-side image
|
|
162
|
+
* cache is kept so a later reload is cheap).
|
|
163
|
+
* @param {string} path - The texture path
|
|
164
|
+
* @param {boolean} pixelart - Whether the pixel-art variant is meant
|
|
165
|
+
*/
|
|
166
|
+
static release(path, pixelart = false) {
|
|
167
|
+
const key = `${path}|${pixelart ? "nearest" : "linear"}`;
|
|
168
|
+
const count = TextureManager.refs.get(key);
|
|
169
|
+
if (count == null) return;
|
|
170
|
+
if (count > 1) {
|
|
171
|
+
TextureManager.refs.set(key, count - 1);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
TextureManager.refs.delete(key);
|
|
175
|
+
const promise = TextureManager.textures.get(key);
|
|
176
|
+
TextureManager.textures.delete(key);
|
|
177
|
+
const gl = GLManager.getGL();
|
|
178
|
+
if (promise && gl) {
|
|
179
|
+
promise.then(({ texture }) => gl.deleteTexture(texture)).catch(() => {});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @method refCount
|
|
185
|
+
* @description Current reference count for a texture (0 if uncached).
|
|
186
|
+
* @returns {number}
|
|
187
|
+
*/
|
|
188
|
+
static refCount(path, pixelart = false) {
|
|
189
|
+
return (
|
|
190
|
+
TextureManager.refs.get(`${path}|${pixelart ? "nearest" : "linear"}`) || 0
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @method clear
|
|
196
|
+
* @description Deletes all cached GL textures and clears the image cache.
|
|
197
|
+
* Useful when tearing down a context or freeing memory between levels.
|
|
198
|
+
*/
|
|
199
|
+
static clear() {
|
|
200
|
+
const gl = GLManager.getGL();
|
|
201
|
+
if (gl) {
|
|
202
|
+
TextureManager.textures.forEach((promise) => {
|
|
203
|
+
promise
|
|
204
|
+
.then(({ texture }) => gl.deleteTexture(texture))
|
|
205
|
+
.catch(() => {});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
TextureManager.textures.clear();
|
|
209
|
+
TextureManager.images.clear();
|
|
210
|
+
TextureManager.refs.clear();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
TextureManager.images = new Map();
|
|
215
|
+
TextureManager.textures = new Map();
|
|
216
|
+
TextureManager.refs = new Map();
|
|
217
|
+
|
|
218
|
+
export default TextureManager;
|
|
@@ -10,12 +10,24 @@ import Instance from "../Instance.js";
|
|
|
10
10
|
* @param {ParticleSettings} settings - The settings for the particle
|
|
11
11
|
*/
|
|
12
12
|
class Particle {
|
|
13
|
-
constructor(instancedTexture, position, rotation, scale, settings) {
|
|
13
|
+
constructor(instancedTexture, position, rotation, scale, settings, velocity) {
|
|
14
|
+
this.reset(instancedTexture, position, rotation, scale, settings, velocity);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @method reset
|
|
19
|
+
* @description (Re)initializes the particle. Used by the pool so particle
|
|
20
|
+
* objects can be reused without allocating. `velocity` overrides
|
|
21
|
+
* settings.velocity when provided (avoids a ParticleSettings allocation per
|
|
22
|
+
* particle).
|
|
23
|
+
*/
|
|
24
|
+
reset(instancedTexture, position, rotation, scale, settings, velocity) {
|
|
14
25
|
this.settings = settings;
|
|
15
26
|
this.lifetime = settings.lifetime;
|
|
16
27
|
this.age = 0;
|
|
17
|
-
|
|
18
|
-
this.
|
|
28
|
+
const v = velocity || settings.velocity;
|
|
29
|
+
this.velocity = { x: v.x, y: v.y };
|
|
30
|
+
this.gravity = { x: settings.gravity.x, y: settings.gravity.y };
|
|
19
31
|
this.instance = new Instance(
|
|
20
32
|
`Particle at ${position.x}, ${position.y}`,
|
|
21
33
|
position,
|
|
@@ -23,16 +35,68 @@ class Particle {
|
|
|
23
35
|
rotation,
|
|
24
36
|
settings.frame
|
|
25
37
|
);
|
|
38
|
+
/** @private */
|
|
39
|
+
this._baseScaleX = scale ? scale.x : 1;
|
|
40
|
+
/** @private */
|
|
41
|
+
this._baseScaleY = scale ? scale.y : 1;
|
|
42
|
+
|
|
43
|
+
/** @private */
|
|
44
|
+
this._colFrom = null;
|
|
45
|
+
/** @private */
|
|
46
|
+
this._colTo = null;
|
|
47
|
+
if (settings.colorOverLife) {
|
|
48
|
+
this._colFrom = Particle._normColor(settings.colorOverLife.from);
|
|
49
|
+
this._colTo = Particle._normColor(settings.colorOverLife.to);
|
|
50
|
+
}
|
|
51
|
+
|
|
26
52
|
this.instancedTexture = instancedTexture;
|
|
27
|
-
if(this.settings.animation) {
|
|
53
|
+
if (this.settings.animation) {
|
|
28
54
|
this.instance.playAnimation(
|
|
29
55
|
this.settings.animation.frames,
|
|
30
56
|
this.settings.animation.speed
|
|
31
57
|
);
|
|
32
58
|
}
|
|
59
|
+
this._applyOverLife(0);
|
|
33
60
|
instancedTexture.addInstance(this.instance);
|
|
34
61
|
}
|
|
35
62
|
|
|
63
|
+
/** @private */
|
|
64
|
+
static _normColor(c) {
|
|
65
|
+
if (!c) return [1, 1, 1];
|
|
66
|
+
return [(c.r ?? 255) / 255, (c.g ?? 255) / 255, (c.b ?? 255) / 255];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @private */
|
|
70
|
+
_applyOverLife(t) {
|
|
71
|
+
const s = this.settings;
|
|
72
|
+
const tr = this.instance.transform;
|
|
73
|
+
|
|
74
|
+
if (s.scaleOverLife) {
|
|
75
|
+
const m =
|
|
76
|
+
s.scaleOverLife.from + (s.scaleOverLife.to - s.scaleOverLife.from) * t;
|
|
77
|
+
tr.scale.x = this._baseScaleX * m;
|
|
78
|
+
tr.scale.y = this._baseScaleY * m;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (this._colFrom || s.alphaOverLife) {
|
|
82
|
+
let r = 1;
|
|
83
|
+
let g = 1;
|
|
84
|
+
let b = 1;
|
|
85
|
+
let a = 1;
|
|
86
|
+
if (this._colFrom) {
|
|
87
|
+
r = this._colFrom[0] + (this._colTo[0] - this._colFrom[0]) * t;
|
|
88
|
+
g = this._colFrom[1] + (this._colTo[1] - this._colFrom[1]) * t;
|
|
89
|
+
b = this._colFrom[2] + (this._colTo[2] - this._colFrom[2]) * t;
|
|
90
|
+
}
|
|
91
|
+
if (s.alphaOverLife) {
|
|
92
|
+
a =
|
|
93
|
+
s.alphaOverLife.from +
|
|
94
|
+
(s.alphaOverLife.to - s.alphaOverLife.from) * t;
|
|
95
|
+
}
|
|
96
|
+
this.instance.setColor(r, g, b, a);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
36
100
|
/**
|
|
37
101
|
* @method update
|
|
38
102
|
* @description Updates the particle
|
|
@@ -40,12 +104,23 @@ class Particle {
|
|
|
40
104
|
*/
|
|
41
105
|
update(dt) {
|
|
42
106
|
this.age += dt;
|
|
43
|
-
|
|
107
|
+
const s = this.settings;
|
|
108
|
+
|
|
44
109
|
this.velocity.x += this.gravity.x * dt;
|
|
45
110
|
this.velocity.y += this.gravity.y * dt;
|
|
46
|
-
|
|
111
|
+
if (s.drag) {
|
|
112
|
+
const damp = Math.max(0, 1 - s.drag * dt);
|
|
113
|
+
this.velocity.x *= damp;
|
|
114
|
+
this.velocity.y *= damp;
|
|
115
|
+
}
|
|
47
116
|
this.instance.transform.position.x += this.velocity.x * dt;
|
|
48
117
|
this.instance.transform.position.y += this.velocity.y * dt;
|
|
118
|
+
if (s.rotationSpeed) {
|
|
119
|
+
this.instance.transform.rotation += s.rotationSpeed * dt;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const t = this.lifetime > 0 ? Math.min(1, this.age / this.lifetime) : 1;
|
|
123
|
+
this._applyOverLife(t);
|
|
49
124
|
}
|
|
50
125
|
|
|
51
126
|
/**
|
|
@@ -64,7 +139,7 @@ class Particle {
|
|
|
64
139
|
destroy() {
|
|
65
140
|
if (this.instancedTexture && this.instance) {
|
|
66
141
|
if (this.instancedTexture.instances.length > 0) {
|
|
67
|
-
if(this.instancedTexture.getInstanceWithId(this.instance.id)) {
|
|
142
|
+
if (this.instancedTexture.getInstanceWithId(this.instance.id)) {
|
|
68
143
|
this.instancedTexture.removeInstance(this.instance.id);
|
|
69
144
|
}
|
|
70
145
|
}
|
|
@@ -24,12 +24,20 @@ class ParticleSettings {
|
|
|
24
24
|
amount = 10,
|
|
25
25
|
direction = new Vector2(1, 0),
|
|
26
26
|
spread = Math.PI / 4,
|
|
27
|
-
emissionRate = Infinity,
|
|
27
|
+
emissionRate = Infinity,
|
|
28
28
|
frame,
|
|
29
29
|
offset,
|
|
30
30
|
rotation,
|
|
31
31
|
scale,
|
|
32
|
-
animation
|
|
32
|
+
animation,
|
|
33
|
+
shape = "cone",
|
|
34
|
+
shapeRadius = 0,
|
|
35
|
+
shapeSize = new Vector2(0, 0),
|
|
36
|
+
scaleOverLife = null,
|
|
37
|
+
alphaOverLife = null,
|
|
38
|
+
colorOverLife = null,
|
|
39
|
+
rotationSpeed = 0,
|
|
40
|
+
drag = 0,
|
|
33
41
|
} = {}) {
|
|
34
42
|
this.lifetime = lifetime;
|
|
35
43
|
this.velocity = velocity;
|
|
@@ -43,7 +51,17 @@ class ParticleSettings {
|
|
|
43
51
|
this.rotation = rotation;
|
|
44
52
|
this.scale = scale;
|
|
45
53
|
this.animation = animation;
|
|
54
|
+
|
|
55
|
+
this.shape = shape;
|
|
56
|
+
this.shapeRadius = shapeRadius;
|
|
57
|
+
this.shapeSize = shapeSize;
|
|
58
|
+
|
|
59
|
+
this.scaleOverLife = scaleOverLife;
|
|
60
|
+
this.alphaOverLife = alphaOverLife;
|
|
61
|
+
this.colorOverLife = colorOverLife;
|
|
62
|
+
this.rotationSpeed = rotationSpeed;
|
|
63
|
+
this.drag = drag;
|
|
46
64
|
}
|
|
47
65
|
}
|
|
48
66
|
|
|
49
|
-
export default ParticleSettings;
|
|
67
|
+
export default ParticleSettings;
|