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,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class Aseprite
|
|
3
|
+
* @description Imports sprite-sheet metadata exported from Aseprite
|
|
4
|
+
* (File ▸ Export Sprite Sheet, with "JSON Data" on) and turns its frame tags
|
|
5
|
+
* into engine animation clips. Works with both the Hash and Array JSON layouts.
|
|
6
|
+
* Pure parsing — pass it the already-parsed JSON object.
|
|
7
|
+
*
|
|
8
|
+
* Assumes the sheet is a uniform grid in frame order (the common case), so the
|
|
9
|
+
* frame indices line up with the engine's Texture/Animator frame numbering.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const cfg = Aseprite.spriteConfig(sheetJson); // {frameWidth, framesPerRow, ...}
|
|
13
|
+
* const tex = new Texture("hero.png", cfg.frameWidth, cfg.frameHeight,
|
|
14
|
+
* cfg.framesPerRow, cfg.totalFrames, 0, false);
|
|
15
|
+
* obj.addComponent(tex);
|
|
16
|
+
* const anim = new Animator();
|
|
17
|
+
* obj.addComponent(anim);
|
|
18
|
+
* Aseprite.applyTo(anim, sheetJson); // registers "idle", "run", ... clips
|
|
19
|
+
* anim.play("run");
|
|
20
|
+
*/
|
|
21
|
+
class Aseprite {
|
|
22
|
+
/**
|
|
23
|
+
* @method frames
|
|
24
|
+
* @description Returns the frames as an ordered array regardless of whether
|
|
25
|
+
* the export used the Hash (object) or Array layout.
|
|
26
|
+
* @param {Object} sheet - Parsed Aseprite JSON
|
|
27
|
+
* @returns {Array<Object>}
|
|
28
|
+
*/
|
|
29
|
+
static frames(sheet) {
|
|
30
|
+
const f = sheet.frames;
|
|
31
|
+
if (Array.isArray(f)) return f;
|
|
32
|
+
return f ? Object.values(f) : [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @method spriteConfig
|
|
37
|
+
* @description Derives the uniform-grid sprite config (for Texture) from the
|
|
38
|
+
* first frame and the sheet size.
|
|
39
|
+
* @param {Object} sheet - Parsed Aseprite JSON
|
|
40
|
+
* @returns {{frameWidth:number, frameHeight:number, framesPerRow:number, totalFrames:number}}
|
|
41
|
+
*/
|
|
42
|
+
static spriteConfig(sheet) {
|
|
43
|
+
const frames = Aseprite.frames(sheet);
|
|
44
|
+
const first =
|
|
45
|
+
frames[0] && frames[0].frame ? frames[0].frame : { w: 0, h: 0 };
|
|
46
|
+
const frameWidth = first.w || 0;
|
|
47
|
+
const frameHeight = first.h || 0;
|
|
48
|
+
const sheetW =
|
|
49
|
+
(sheet.meta && sheet.meta.size && sheet.meta.size.w) || frameWidth;
|
|
50
|
+
return {
|
|
51
|
+
frameWidth,
|
|
52
|
+
frameHeight,
|
|
53
|
+
framesPerRow: frameWidth
|
|
54
|
+
? Math.max(1, Math.round(sheetW / frameWidth))
|
|
55
|
+
: 1,
|
|
56
|
+
totalFrames: frames.length,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @method durations
|
|
62
|
+
* @description Per-frame durations in milliseconds, in frame order.
|
|
63
|
+
* @param {Object} sheet - Parsed Aseprite JSON
|
|
64
|
+
* @returns {number[]}
|
|
65
|
+
*/
|
|
66
|
+
static durations(sheet) {
|
|
67
|
+
return Aseprite.frames(sheet).map((f) => f.duration || 100);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @private */
|
|
71
|
+
static _expandTag(tag) {
|
|
72
|
+
const from = tag.from;
|
|
73
|
+
const to = tag.to;
|
|
74
|
+
const forward = [];
|
|
75
|
+
for (let i = from; i <= to; i++) forward.push(i);
|
|
76
|
+
const dir = (tag.direction || "forward").toLowerCase();
|
|
77
|
+
if (dir === "reverse") return forward.slice().reverse();
|
|
78
|
+
if (dir === "pingpong" || dir === "ping-pong") {
|
|
79
|
+
const back = forward.slice(1, -1).reverse();
|
|
80
|
+
return forward.concat(back);
|
|
81
|
+
}
|
|
82
|
+
return forward;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @method toClips
|
|
87
|
+
* @description Builds clip descriptors from the sheet's frame tags. Each clip
|
|
88
|
+
* is `{ name, frames, speed }` where `frames` are frame indices and `speed`
|
|
89
|
+
* is the average frame duration (ms) — the per-clip speed the Animator uses.
|
|
90
|
+
* If the sheet has no tags, a single "default" clip spanning all frames is
|
|
91
|
+
* returned.
|
|
92
|
+
* @param {Object} sheet - Parsed Aseprite JSON
|
|
93
|
+
* @returns {Array<{name:string, frames:number[], speed:number}>}
|
|
94
|
+
*/
|
|
95
|
+
static toClips(sheet) {
|
|
96
|
+
const durations = Aseprite.durations(sheet);
|
|
97
|
+
const avg = (indices) => {
|
|
98
|
+
if (!indices.length) return 100;
|
|
99
|
+
let sum = 0;
|
|
100
|
+
let n = 0;
|
|
101
|
+
for (const i of indices) {
|
|
102
|
+
sum += durations[i] != null ? durations[i] : 100;
|
|
103
|
+
n++;
|
|
104
|
+
}
|
|
105
|
+
return Math.round(sum / n);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const tags = (sheet.meta && sheet.meta.frameTags) || [];
|
|
109
|
+
if (!tags.length) {
|
|
110
|
+
const all = durations.map((_, i) => i);
|
|
111
|
+
return [{ name: "default", frames: all, speed: avg(all) }];
|
|
112
|
+
}
|
|
113
|
+
return tags.map((tag) => {
|
|
114
|
+
const frames = Aseprite._expandTag(tag);
|
|
115
|
+
return { name: tag.name, frames, speed: avg(frames) };
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @method applyTo
|
|
121
|
+
* @description Registers every clip from the sheet on an Animator. Clips loop
|
|
122
|
+
* by default; pass `loop` as a boolean (applies to all) or an object mapping
|
|
123
|
+
* clip name -> boolean to override individual clips.
|
|
124
|
+
* @param {Animator} animator - Target animator
|
|
125
|
+
* @param {Object} sheet - Parsed Aseprite JSON
|
|
126
|
+
* @param {Object} [options] - { loop = true }
|
|
127
|
+
* @returns {Animator} - The animator (for chaining)
|
|
128
|
+
*/
|
|
129
|
+
static applyTo(animator, sheet, options = {}) {
|
|
130
|
+
const loopOpt = options.loop ?? true;
|
|
131
|
+
for (const clip of Aseprite.toClips(sheet)) {
|
|
132
|
+
const loop =
|
|
133
|
+
typeof loopOpt === "object" && loopOpt !== null
|
|
134
|
+
? (loopOpt[clip.name] ?? true)
|
|
135
|
+
: !!loopOpt;
|
|
136
|
+
animator.addClip(clip.name, clip.frames, { speed: clip.speed, loop });
|
|
137
|
+
}
|
|
138
|
+
return animator;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default Aseprite;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import Tilemap from "../Tilemap.js";
|
|
2
|
+
|
|
3
|
+
const FLIP_FLAGS = 0xe0000000;
|
|
4
|
+
const GID_MASK = 0x1fffffff;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @class TiledMap
|
|
8
|
+
* @description Imports orthogonal maps exported from the Tiled editor
|
|
9
|
+
* (https://www.mapeditor.org) in JSON format into the engine's Tilemap, plus
|
|
10
|
+
* helpers to pull object layers (spawn points, triggers, etc.) out as plain
|
|
11
|
+
* data. Pure parsing — pass it the already-parsed JSON object (load it with
|
|
12
|
+
* AssetManager.json or fetch).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const map = TiledMap.toTilemap(mapJson, "tiles.png");
|
|
16
|
+
* scene.add(map.gameObject);
|
|
17
|
+
* map.buildColliders(physics);
|
|
18
|
+
* const spawns = TiledMap.objects(mapJson, { layer: "spawns", flipY: true });
|
|
19
|
+
*/
|
|
20
|
+
class TiledMap {
|
|
21
|
+
/**
|
|
22
|
+
* @method tilesetFor
|
|
23
|
+
* @description Finds the tileset a (masked) global id belongs to.
|
|
24
|
+
* @param {Object} map - Parsed Tiled map JSON
|
|
25
|
+
* @param {number} gid - Masked global tile id (flip flags removed)
|
|
26
|
+
* @returns {Object|null}
|
|
27
|
+
*/
|
|
28
|
+
static tilesetFor(map, gid) {
|
|
29
|
+
const sets = map.tilesets || [];
|
|
30
|
+
let best = null;
|
|
31
|
+
for (const ts of sets) {
|
|
32
|
+
if (ts.firstgid <= gid && (!best || ts.firstgid > best.firstgid)) {
|
|
33
|
+
best = ts;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return best;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @private */
|
|
40
|
+
static _tileLayer(map, layer) {
|
|
41
|
+
const layers = (map.layers || []).filter((l) => l.type === "tilelayer");
|
|
42
|
+
if (layer == null) return layers[0] || null;
|
|
43
|
+
if (typeof layer === "number") return layers[layer] || null;
|
|
44
|
+
return layers.find((l) => l.name === layer) || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @method toFrameGrid
|
|
49
|
+
* @description Converts a tile layer into a 2D array of local frame indices
|
|
50
|
+
* (-1 for empty), ready for Tilemap.setMap. Global ids are converted to
|
|
51
|
+
* tileset-local indices and flip flags are stripped.
|
|
52
|
+
* @param {Object} map - Parsed Tiled map JSON
|
|
53
|
+
* @param {Object} [options] - { layer } (name, index, or first tile layer)
|
|
54
|
+
* @returns {number[][]}
|
|
55
|
+
*/
|
|
56
|
+
static toFrameGrid(map, options = {}) {
|
|
57
|
+
const layer = TiledMap._tileLayer(map, options.layer);
|
|
58
|
+
if (!layer) return [];
|
|
59
|
+
const cols = layer.width;
|
|
60
|
+
const rows = layer.height;
|
|
61
|
+
const data = layer.data || [];
|
|
62
|
+
const grid = [];
|
|
63
|
+
for (let r = 0; r < rows; r++) {
|
|
64
|
+
const row = [];
|
|
65
|
+
for (let c = 0; c < cols; c++) {
|
|
66
|
+
const raw = data[r * cols + c] || 0;
|
|
67
|
+
if (raw === 0) {
|
|
68
|
+
row.push(-1);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const gid = (raw & GID_MASK) >>> 0;
|
|
72
|
+
const ts = TiledMap.tilesetFor(map, gid);
|
|
73
|
+
row.push(ts ? gid - ts.firstgid : gid - 1);
|
|
74
|
+
}
|
|
75
|
+
grid.push(row);
|
|
76
|
+
}
|
|
77
|
+
return grid;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @method toTilemap
|
|
82
|
+
* @description Builds a ready-to-render Tilemap from a Tiled map. The tile
|
|
83
|
+
* sheet config (frame size, columns, count) is read from the map's first
|
|
84
|
+
* tileset; the render tile size comes from the map's tilewidth.
|
|
85
|
+
* @param {Object} map - Parsed Tiled map JSON
|
|
86
|
+
* @param {string} texturePath - Path to the tile sheet image
|
|
87
|
+
* @param {Object} [options] - { layer, pixelart = true, name, originX, originY }
|
|
88
|
+
* @returns {Tilemap}
|
|
89
|
+
*/
|
|
90
|
+
static toTilemap(map, texturePath, options = {}) {
|
|
91
|
+
const ts = (map.tilesets || [])[0] || {};
|
|
92
|
+
const tilemap = new Tilemap(options.name || "tiledmap", texturePath, {
|
|
93
|
+
tileSize: options.tileSize ?? map.tilewidth ?? ts.tilewidth ?? 16,
|
|
94
|
+
frameWidth: ts.tilewidth ?? map.tilewidth ?? 16,
|
|
95
|
+
frameHeight: ts.tileheight ?? map.tileheight ?? 16,
|
|
96
|
+
framesPerRow: ts.columns ?? 1,
|
|
97
|
+
totalFrames: ts.tilecount ?? 1,
|
|
98
|
+
pixelart: options.pixelart ?? true,
|
|
99
|
+
});
|
|
100
|
+
const grid = TiledMap.toFrameGrid(map, options);
|
|
101
|
+
tilemap.setMap(grid, {
|
|
102
|
+
originX: options.originX ?? 0,
|
|
103
|
+
originY: options.originY ?? 0,
|
|
104
|
+
flipY: options.flipY ?? true,
|
|
105
|
+
});
|
|
106
|
+
return tilemap;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @method objects
|
|
111
|
+
* @description Extracts the objects from an object layer as plain data
|
|
112
|
+
* (spawn points, triggers, regions). Tiled `properties` arrays are flattened
|
|
113
|
+
* into a `props` object. With `flipY` set, y is converted from Tiled's
|
|
114
|
+
* top-left pixel origin to a y-up world using the map's pixel height.
|
|
115
|
+
* @param {Object} map - Parsed Tiled map JSON
|
|
116
|
+
* @param {Object} [options] - { layer, flipY = false, originX = 0, originY = 0 }
|
|
117
|
+
* @returns {Array<Object>}
|
|
118
|
+
*/
|
|
119
|
+
static objects(map, options = {}) {
|
|
120
|
+
const groups = (map.layers || []).filter((l) => l.type === "objectgroup");
|
|
121
|
+
let group;
|
|
122
|
+
if (options.layer == null) group = groups[0];
|
|
123
|
+
else if (typeof options.layer === "number") group = groups[options.layer];
|
|
124
|
+
else group = groups.find((l) => l.name === options.layer);
|
|
125
|
+
if (!group) return [];
|
|
126
|
+
|
|
127
|
+
const flipY = options.flipY ?? false;
|
|
128
|
+
const originX = options.originX ?? 0;
|
|
129
|
+
const originY = options.originY ?? 0;
|
|
130
|
+
const mapPixelH = (map.height || 0) * (map.tileheight || 0);
|
|
131
|
+
|
|
132
|
+
return (group.objects || []).map((o) => {
|
|
133
|
+
const props = {};
|
|
134
|
+
if (Array.isArray(o.properties)) {
|
|
135
|
+
for (const p of o.properties) props[p.name] = p.value;
|
|
136
|
+
}
|
|
137
|
+
const worldY = flipY ? mapPixelH - o.y : o.y;
|
|
138
|
+
return {
|
|
139
|
+
id: o.id,
|
|
140
|
+
name: o.name || "",
|
|
141
|
+
type: o.type || o.class || "",
|
|
142
|
+
x: originX + o.x,
|
|
143
|
+
y: originY + worldY,
|
|
144
|
+
width: o.width || 0,
|
|
145
|
+
height: o.height || 0,
|
|
146
|
+
rotation: o.rotation || 0,
|
|
147
|
+
gid: o.gid != null ? (o.gid & GID_MASK) >>> 0 : null,
|
|
148
|
+
point: !!o.point,
|
|
149
|
+
ellipse: !!o.ellipse,
|
|
150
|
+
polygon: o.polygon || null,
|
|
151
|
+
props,
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export { FLIP_FLAGS, GID_MASK };
|
|
158
|
+
export default TiledMap;
|
|
@@ -10,15 +10,14 @@ import { Vector2 } from "../Physics.js";
|
|
|
10
10
|
*/
|
|
11
11
|
class DirectionalLight {
|
|
12
12
|
constructor(position, direction, color, intensity, width) {
|
|
13
|
-
this.position = position;
|
|
14
|
-
// Normalize the direction vector
|
|
13
|
+
this.position = position;
|
|
15
14
|
const length = Math.sqrt(
|
|
16
15
|
direction.x * direction.x + direction.y * direction.y
|
|
17
16
|
);
|
|
18
|
-
this.direction = new Vector2(direction.x / length, direction.y / length);
|
|
19
|
-
this.color = color;
|
|
20
|
-
this.intensity = intensity;
|
|
21
|
-
this.width = width;
|
|
17
|
+
this.direction = new Vector2(direction.x / length, direction.y / length);
|
|
18
|
+
this.color = color;
|
|
19
|
+
this.intensity = intensity;
|
|
20
|
+
this.width = width;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -27,38 +26,30 @@ class DirectionalLight {
|
|
|
27
26
|
* @returns {number} - Light intensity (0-1)
|
|
28
27
|
*/
|
|
29
28
|
getIntensityAtPoint(point) {
|
|
30
|
-
|
|
31
|
-
// Calculate distance from light position to point
|
|
32
29
|
const dx = point.x - this.position.x;
|
|
33
30
|
const dy = point.y - this.position.y;
|
|
34
31
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
35
32
|
|
|
36
|
-
// Calculate the projection of the point onto the light direction
|
|
37
33
|
const lightDirNormalized = new Vector2(this.direction.x, this.direction.y);
|
|
38
34
|
const toPoint = new Vector2(dx, dy);
|
|
39
35
|
const projection =
|
|
40
36
|
toPoint.x * lightDirNormalized.x + toPoint.y * lightDirNormalized.y;
|
|
41
37
|
|
|
42
|
-
// If point is behind the light, no illumination
|
|
43
38
|
if (projection < 0) {
|
|
44
39
|
return 0;
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
// Calculate perpendicular distance from the light beam center
|
|
48
42
|
const perpDistance = Math.abs(
|
|
49
43
|
toPoint.x * lightDirNormalized.y - toPoint.y * lightDirNormalized.x
|
|
50
44
|
);
|
|
51
45
|
|
|
52
|
-
// If point is outside the light beam width, no illumination
|
|
53
46
|
if (perpDistance > this.width / 2) {
|
|
54
47
|
return 0;
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
// Calculate intensity based on distance within the beam
|
|
58
50
|
const widthFactor = 1 - perpDistance / (this.width / 2);
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
const maxDistance = 1000; // Maximum effective distance
|
|
52
|
+
const maxDistance = 1000;
|
|
62
53
|
const distanceFactor = Math.max(0, 1 - distance / maxDistance);
|
|
63
54
|
|
|
64
55
|
return this.intensity * widthFactor * distanceFactor;
|
package/src/lights/PointLight.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
class PointLight {
|
|
9
9
|
constructor(position, color, intensity, radius) {
|
|
10
|
-
this.position = position;
|
|
11
|
-
this.color = color;
|
|
12
|
-
this.intensity = intensity;
|
|
13
|
-
this.radius = radius;
|
|
10
|
+
this.position = position;
|
|
11
|
+
this.color = color;
|
|
12
|
+
this.intensity = intensity;
|
|
13
|
+
this.radius = radius;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import TextureManager from "./TextureManager.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class AssetManager
|
|
5
|
+
* @description A general async loader for everything a game needs at startup:
|
|
6
|
+
* images/textures, audio, JSON, text, and web fonts. It deduplicates by key,
|
|
7
|
+
* reports aggregate progress (so you can drive a loading bar), and stores the
|
|
8
|
+
* resolved assets for synchronous lookup afterwards via `get(key)`.
|
|
9
|
+
*
|
|
10
|
+
* Textures are routed through TextureManager so the GL upload cache is shared
|
|
11
|
+
* with the rest of the engine.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const assets = new AssetManager();
|
|
15
|
+
* assets.image("player", "player.png", { pixelart: true });
|
|
16
|
+
* assets.audio("jump", "jump.wav");
|
|
17
|
+
* assets.json("level1", "levels/1.json");
|
|
18
|
+
* assets.font("Press Start 2P", "fonts/press-start.woff2");
|
|
19
|
+
* assets.onProgress((loaded, total) => bar.set(loaded / total));
|
|
20
|
+
* await assets.load();
|
|
21
|
+
* const img = assets.get("player"); // HTMLImageElement
|
|
22
|
+
* const data = assets.get("level1"); // parsed JSON
|
|
23
|
+
*/
|
|
24
|
+
class AssetManager {
|
|
25
|
+
constructor() {
|
|
26
|
+
/** @private */
|
|
27
|
+
this._queue = new Map();
|
|
28
|
+
this.assets = new Map();
|
|
29
|
+
/** @private */
|
|
30
|
+
this._paths = new Map();
|
|
31
|
+
/** @private */
|
|
32
|
+
this._progressHandlers = [];
|
|
33
|
+
/** @private */
|
|
34
|
+
this._loaded = 0;
|
|
35
|
+
/** @private */
|
|
36
|
+
this._total = 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @method image
|
|
41
|
+
* @description Queues an image/texture. The GL texture is created lazily by
|
|
42
|
+
* the renderer; `get(key)` returns the HTMLImageElement.
|
|
43
|
+
*/
|
|
44
|
+
image(key, path, options = {}) {
|
|
45
|
+
this._queue.set(key, { type: "image", path, pixelart: !!options.pixelart });
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @method audio
|
|
51
|
+
* @description Queues an audio file (returns an HTMLAudioElement on get()).
|
|
52
|
+
*/
|
|
53
|
+
audio(key, path) {
|
|
54
|
+
this._queue.set(key, { type: "audio", path });
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @method json
|
|
60
|
+
* @description Queues a JSON file (parsed on get()).
|
|
61
|
+
*/
|
|
62
|
+
json(key, path) {
|
|
63
|
+
this._queue.set(key, { type: "json", path });
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @method text
|
|
69
|
+
* @description Queues a plain-text file.
|
|
70
|
+
*/
|
|
71
|
+
text(key, path) {
|
|
72
|
+
this._queue.set(key, { type: "text", path });
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @method font
|
|
78
|
+
* @description Queues a web font. `key` is the font-family name you will use
|
|
79
|
+
* in CSS/canvas; `path` is the font file URL.
|
|
80
|
+
*/
|
|
81
|
+
font(key, path, descriptors = {}) {
|
|
82
|
+
this._queue.set(key, { type: "font", family: key, path, descriptors });
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @method onProgress
|
|
88
|
+
* @description Registers a callback invoked as (loaded, total) after each
|
|
89
|
+
* asset resolves.
|
|
90
|
+
*/
|
|
91
|
+
onProgress(handler) {
|
|
92
|
+
if (typeof handler === "function") this._progressHandlers.push(handler);
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @method progress
|
|
98
|
+
* @returns {number} - Fraction loaded in [0, 1] (1 when nothing is queued).
|
|
99
|
+
*/
|
|
100
|
+
progress() {
|
|
101
|
+
return this._total === 0 ? 1 : this._loaded / this._total;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @method load
|
|
106
|
+
* @description Loads every queued asset, resolving when all are done. Failed
|
|
107
|
+
* assets reject the returned promise unless `continueOnError` is true, in
|
|
108
|
+
* which case they resolve to null and loading continues.
|
|
109
|
+
* @param {Object} [options] - { continueOnError = false }
|
|
110
|
+
* @returns {Promise<Map>} - The resolved assets map
|
|
111
|
+
*/
|
|
112
|
+
async load(options = {}) {
|
|
113
|
+
const { continueOnError = false } = options;
|
|
114
|
+
const entries = Array.from(this._queue.entries());
|
|
115
|
+
this._total = entries.length;
|
|
116
|
+
this._loaded = 0;
|
|
117
|
+
|
|
118
|
+
await Promise.all(
|
|
119
|
+
entries.map(async ([key, spec]) => {
|
|
120
|
+
try {
|
|
121
|
+
const asset = await this._loadOne(spec);
|
|
122
|
+
this.assets.set(key, asset);
|
|
123
|
+
if (spec.type === "image") {
|
|
124
|
+
this._paths.set(key, { path: spec.path, pixelart: spec.pixelart });
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (!continueOnError) throw err;
|
|
128
|
+
console.warn(`[AssetManager] Failed to load "${key}":`, err);
|
|
129
|
+
this.assets.set(key, null);
|
|
130
|
+
} finally {
|
|
131
|
+
this._loaded++;
|
|
132
|
+
for (const handler of this._progressHandlers) {
|
|
133
|
+
handler(this._loaded, this._total);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
this._queue.clear();
|
|
140
|
+
return this.assets;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** @private */
|
|
144
|
+
_loadOne(spec) {
|
|
145
|
+
switch (spec.type) {
|
|
146
|
+
case "image":
|
|
147
|
+
return TextureManager.loadImage(spec.path);
|
|
148
|
+
case "audio":
|
|
149
|
+
return AssetManager._loadAudio(spec.path);
|
|
150
|
+
case "json":
|
|
151
|
+
return fetch(spec.path).then((r) => {
|
|
152
|
+
if (!r.ok) throw new Error(`HTTP ${r.status} for ${spec.path}`);
|
|
153
|
+
return r.json();
|
|
154
|
+
});
|
|
155
|
+
case "text":
|
|
156
|
+
return fetch(spec.path).then((r) => {
|
|
157
|
+
if (!r.ok) throw new Error(`HTTP ${r.status} for ${spec.path}`);
|
|
158
|
+
return r.text();
|
|
159
|
+
});
|
|
160
|
+
case "font":
|
|
161
|
+
return AssetManager._loadFont(spec);
|
|
162
|
+
default:
|
|
163
|
+
return Promise.reject(new Error(`Unknown asset type: ${spec.type}`));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @private */
|
|
168
|
+
static _loadAudio(path) {
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const audio = new Audio();
|
|
171
|
+
audio.addEventListener("canplaythrough", () => resolve(audio), {
|
|
172
|
+
once: true,
|
|
173
|
+
});
|
|
174
|
+
audio.addEventListener("error", () =>
|
|
175
|
+
reject(new Error(`Failed to load audio: ${path}`))
|
|
176
|
+
);
|
|
177
|
+
audio.src = path;
|
|
178
|
+
audio.load();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** @private */
|
|
183
|
+
static async _loadFont(spec) {
|
|
184
|
+
if (typeof FontFace === "undefined" || !document.fonts) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const face = new FontFace(
|
|
188
|
+
spec.family,
|
|
189
|
+
`url(${spec.path})`,
|
|
190
|
+
spec.descriptors
|
|
191
|
+
);
|
|
192
|
+
const loaded = await face.load();
|
|
193
|
+
document.fonts.add(loaded);
|
|
194
|
+
return loaded;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @method get
|
|
199
|
+
* @description Returns a previously loaded asset by key.
|
|
200
|
+
*/
|
|
201
|
+
get(key) {
|
|
202
|
+
return this.assets.get(key);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @method has
|
|
207
|
+
* @returns {boolean} - Whether an asset has been loaded for the key.
|
|
208
|
+
*/
|
|
209
|
+
has(key) {
|
|
210
|
+
return this.assets.has(key);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @method getTexture
|
|
215
|
+
* @description Convenience: returns the cached GL texture for an image asset
|
|
216
|
+
* that was queued with `image()`. Resolves once the upload completes.
|
|
217
|
+
* @param {string} key - The image asset key
|
|
218
|
+
* @returns {Promise<{texture: WebGLTexture, width: number, height: number}>}
|
|
219
|
+
*/
|
|
220
|
+
getTexture(key) {
|
|
221
|
+
const info = this._paths.get(key);
|
|
222
|
+
if (!info) return Promise.reject(new Error(`No image asset "${key}"`));
|
|
223
|
+
return TextureManager.getTexture(info.path, info.pixelart);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @method clear
|
|
228
|
+
* @description Forgets all loaded assets (does not free GL textures; use
|
|
229
|
+
* TextureManager.clear for that).
|
|
230
|
+
*/
|
|
231
|
+
clear() {
|
|
232
|
+
this.assets.clear();
|
|
233
|
+
this._queue.clear();
|
|
234
|
+
this._loaded = 0;
|
|
235
|
+
this._total = 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default AssetManager;
|