@energy8platform/game-engine 0.2.1 → 0.3.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.
Files changed (59) hide show
  1. package/README.md +298 -28
  2. package/dist/animation.cjs.js +191 -1
  3. package/dist/animation.cjs.js.map +1 -1
  4. package/dist/animation.d.ts +117 -1
  5. package/dist/animation.esm.js +192 -3
  6. package/dist/animation.esm.js.map +1 -1
  7. package/dist/audio.cjs.js +66 -16
  8. package/dist/audio.cjs.js.map +1 -1
  9. package/dist/audio.d.ts +4 -0
  10. package/dist/audio.esm.js +66 -16
  11. package/dist/audio.esm.js.map +1 -1
  12. package/dist/core.cjs.js +306 -85
  13. package/dist/core.cjs.js.map +1 -1
  14. package/dist/core.d.ts +60 -1
  15. package/dist/core.esm.js +307 -86
  16. package/dist/core.esm.js.map +1 -1
  17. package/dist/debug.cjs.js +36 -68
  18. package/dist/debug.cjs.js.map +1 -1
  19. package/dist/debug.d.ts +4 -6
  20. package/dist/debug.esm.js +36 -68
  21. package/dist/debug.esm.js.map +1 -1
  22. package/dist/index.cjs.js +1247 -253
  23. package/dist/index.cjs.js.map +1 -1
  24. package/dist/index.d.ts +386 -41
  25. package/dist/index.esm.js +1247 -256
  26. package/dist/index.esm.js.map +1 -1
  27. package/dist/ui.cjs.js +757 -1
  28. package/dist/ui.cjs.js.map +1 -1
  29. package/dist/ui.d.ts +208 -2
  30. package/dist/ui.esm.js +756 -2
  31. package/dist/ui.esm.js.map +1 -1
  32. package/dist/vite.cjs.js +65 -68
  33. package/dist/vite.cjs.js.map +1 -1
  34. package/dist/vite.d.ts +17 -23
  35. package/dist/vite.esm.js +66 -68
  36. package/dist/vite.esm.js.map +1 -1
  37. package/package.json +4 -5
  38. package/src/animation/SpriteAnimation.ts +210 -0
  39. package/src/animation/Tween.ts +27 -1
  40. package/src/animation/index.ts +2 -0
  41. package/src/audio/AudioManager.ts +64 -15
  42. package/src/core/EventEmitter.ts +7 -1
  43. package/src/core/GameApplication.ts +18 -7
  44. package/src/core/SceneManager.ts +3 -1
  45. package/src/debug/DevBridge.ts +49 -80
  46. package/src/index.ts +6 -0
  47. package/src/input/InputManager.ts +26 -0
  48. package/src/loading/CSSPreloader.ts +7 -33
  49. package/src/loading/LoadingScene.ts +17 -41
  50. package/src/loading/index.ts +1 -0
  51. package/src/loading/logo.ts +95 -0
  52. package/src/types.ts +4 -0
  53. package/src/ui/BalanceDisplay.ts +14 -0
  54. package/src/ui/Button.ts +1 -1
  55. package/src/ui/Layout.ts +364 -0
  56. package/src/ui/ScrollContainer.ts +557 -0
  57. package/src/ui/index.ts +4 -0
  58. package/src/viewport/ViewportManager.ts +2 -0
  59. package/src/vite/index.ts +83 -83
package/src/ui/Button.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Container, Graphics, Sprite, Texture, FederatedPointerEvent } from 'pixi.js';
1
+ import { Container, Graphics, Sprite, Texture } from 'pixi.js';
2
2
  import { Tween } from '../animation/Tween';
3
3
  import { Easing } from '../animation/Easing';
4
4
 
@@ -0,0 +1,364 @@
1
+ import { Container } from 'pixi.js';
2
+
3
+ // ─── Types ───────────────────────────────────────────────
4
+
5
+ export type LayoutDirection = 'horizontal' | 'vertical' | 'grid' | 'wrap';
6
+ export type LayoutAlignment = 'start' | 'center' | 'end' | 'stretch';
7
+ export type LayoutAnchor =
8
+ | 'top-left' | 'top-center' | 'top-right'
9
+ | 'center-left' | 'center' | 'center-right'
10
+ | 'bottom-left' | 'bottom-center' | 'bottom-right';
11
+
12
+ export interface LayoutConfig {
13
+ /** Layout direction (default: 'vertical') */
14
+ direction?: LayoutDirection;
15
+
16
+ /** Gap between children in pixels (default: 0) */
17
+ gap?: number;
18
+
19
+ /** Padding inside the layout container [top, right, bottom, left] or a single number */
20
+ padding?: number | [number, number, number, number];
21
+
22
+ /** Alignment of children on the cross axis (default: 'start') */
23
+ alignment?: LayoutAlignment;
24
+
25
+ /** Anchor point determining where the layout is positioned relative to its coordinates */
26
+ anchor?: LayoutAnchor;
27
+
28
+ /** Number of columns (only for 'grid' direction) */
29
+ columns?: number;
30
+
31
+ /** Maximum width for 'wrap' direction before wrapping to next line */
32
+ maxWidth?: number;
33
+
34
+ /** Whether to auto-layout when children change (default: true) */
35
+ autoLayout?: boolean;
36
+
37
+ /** Breakpoints: map of max viewport widths to override configs */
38
+ breakpoints?: Record<number, Partial<LayoutConfig>>;
39
+ }
40
+
41
+ /**
42
+ * Responsive layout container that automatically arranges its children.
43
+ *
44
+ * Supports horizontal, vertical, grid, and wrap layout modes with
45
+ * alignment, padding, gap, and viewport-anchor positioning.
46
+ * Breakpoints allow different layouts for different screen sizes.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const toolbar = new Layout({
51
+ * direction: 'horizontal',
52
+ * gap: 20,
53
+ * alignment: 'center',
54
+ * anchor: 'bottom-center',
55
+ * padding: 16,
56
+ * breakpoints: {
57
+ * 768: { direction: 'vertical', gap: 10 },
58
+ * },
59
+ * });
60
+ *
61
+ * toolbar.addItem(spinButton);
62
+ * toolbar.addItem(betLabel);
63
+ * scene.container.addChild(toolbar);
64
+ *
65
+ * // On resize, update layout position relative to viewport
66
+ * toolbar.updateViewport(width, height);
67
+ * ```
68
+ */
69
+ export class Layout extends Container {
70
+ private _config: Required<Pick<LayoutConfig, 'direction' | 'gap' | 'alignment' | 'autoLayout' | 'columns'>>;
71
+ private _padding: [number, number, number, number];
72
+ private _anchor: LayoutAnchor;
73
+ private _maxWidth: number;
74
+ private _breakpoints: [number, Partial<LayoutConfig>][];
75
+ private _content: Container;
76
+ private _items: Container[] = [];
77
+ private _viewportWidth = 0;
78
+ private _viewportHeight = 0;
79
+
80
+ constructor(config: LayoutConfig = {}) {
81
+ super();
82
+
83
+ this._config = {
84
+ direction: config.direction ?? 'vertical',
85
+ gap: config.gap ?? 0,
86
+ alignment: config.alignment ?? 'start',
87
+ autoLayout: config.autoLayout ?? true,
88
+ columns: config.columns ?? 2,
89
+ };
90
+
91
+ this._padding = Layout.normalizePadding(config.padding ?? 0);
92
+ this._anchor = config.anchor ?? 'top-left';
93
+ this._maxWidth = config.maxWidth ?? Infinity;
94
+
95
+ // Sort breakpoints by width ascending for correct resolution
96
+ this._breakpoints = config.breakpoints
97
+ ? Object.entries(config.breakpoints)
98
+ .map(([w, cfg]) => [Number(w), cfg] as [number, Partial<LayoutConfig>])
99
+ .sort((a, b) => a[0] - b[0])
100
+ : [];
101
+
102
+ this._content = new Container();
103
+ this.addChild(this._content);
104
+ }
105
+
106
+ /** Add an item to the layout */
107
+ addItem(child: Container): this {
108
+ this._items.push(child);
109
+ this._content.addChild(child);
110
+ if (this._config.autoLayout) this.layout();
111
+ return this;
112
+ }
113
+
114
+ /** Remove an item from the layout */
115
+ removeItem(child: Container): this {
116
+ const idx = this._items.indexOf(child);
117
+ if (idx !== -1) {
118
+ this._items.splice(idx, 1);
119
+ this._content.removeChild(child);
120
+ if (this._config.autoLayout) this.layout();
121
+ }
122
+ return this;
123
+ }
124
+
125
+ /** Remove all items */
126
+ clearItems(): this {
127
+ for (const item of this._items) {
128
+ this._content.removeChild(item);
129
+ }
130
+ this._items.length = 0;
131
+ if (this._config.autoLayout) this.layout();
132
+ return this;
133
+ }
134
+
135
+ /** Get all layout items */
136
+ get items(): readonly Container[] {
137
+ return this._items;
138
+ }
139
+
140
+ /**
141
+ * Update the viewport size and recalculate layout.
142
+ * Should be called from `Scene.onResize()`.
143
+ */
144
+ updateViewport(width: number, height: number): void {
145
+ this._viewportWidth = width;
146
+ this._viewportHeight = height;
147
+ this.layout();
148
+ }
149
+
150
+ /**
151
+ * Recalculate layout positions of all children.
152
+ */
153
+ layout(): void {
154
+ if (this._items.length === 0) return;
155
+
156
+ // Resolve effective config (apply breakpoint overrides)
157
+ const effective = this.resolveConfig();
158
+ const gap = effective.gap ?? this._config.gap;
159
+ const direction = effective.direction ?? this._config.direction;
160
+ const alignment = effective.alignment ?? this._config.alignment;
161
+ const columns = effective.columns ?? this._config.columns;
162
+ const padding = effective.padding !== undefined
163
+ ? Layout.normalizePadding(effective.padding)
164
+ : this._padding;
165
+ const maxWidth = effective.maxWidth ?? this._maxWidth;
166
+
167
+ const [pt, pr, pb, pl] = padding;
168
+
169
+ switch (direction) {
170
+ case 'horizontal':
171
+ this.layoutLinear('x', 'y', gap, alignment, pl, pt);
172
+ break;
173
+ case 'vertical':
174
+ this.layoutLinear('y', 'x', gap, alignment, pt, pl);
175
+ break;
176
+ case 'grid':
177
+ this.layoutGrid(columns, gap, alignment, pl, pt);
178
+ break;
179
+ case 'wrap':
180
+ this.layoutWrap(maxWidth - pl - pr, gap, alignment, pl, pt);
181
+ break;
182
+ }
183
+
184
+ // Apply anchor positioning relative to viewport
185
+ this.applyAnchor(effective.anchor ?? this._anchor);
186
+ }
187
+
188
+ // ─── Private layout helpers ────────────────────────────
189
+
190
+ private layoutLinear(
191
+ mainAxis: 'x' | 'y',
192
+ crossAxis: 'x' | 'y',
193
+ gap: number,
194
+ alignment: LayoutAlignment,
195
+ mainOffset: number,
196
+ crossOffset: number,
197
+ ): void {
198
+ let pos = mainOffset;
199
+ const sizes = this._items.map(item => this.getItemSize(item));
200
+ const maxCross = Math.max(...sizes.map(s => (crossAxis === 'x' ? s.width : s.height)));
201
+
202
+ for (let i = 0; i < this._items.length; i++) {
203
+ const item = this._items[i];
204
+ const size = sizes[i];
205
+
206
+ item[mainAxis] = pos;
207
+
208
+ // Cross-axis alignment
209
+ const itemCross = crossAxis === 'x' ? size.width : size.height;
210
+ switch (alignment) {
211
+ case 'start':
212
+ item[crossAxis] = crossOffset;
213
+ break;
214
+ case 'center':
215
+ item[crossAxis] = crossOffset + (maxCross - itemCross) / 2;
216
+ break;
217
+ case 'end':
218
+ item[crossAxis] = crossOffset + maxCross - itemCross;
219
+ break;
220
+ case 'stretch':
221
+ item[crossAxis] = crossOffset;
222
+ // Note: stretch doesn't resize children — that's up to the item
223
+ break;
224
+ }
225
+
226
+ const mainSize = mainAxis === 'x' ? size.width : size.height;
227
+ pos += mainSize + gap;
228
+ }
229
+ }
230
+
231
+ private layoutGrid(
232
+ columns: number,
233
+ gap: number,
234
+ alignment: LayoutAlignment,
235
+ offsetX: number,
236
+ offsetY: number,
237
+ ): void {
238
+ const sizes = this._items.map(item => this.getItemSize(item));
239
+ const maxItemWidth = Math.max(...sizes.map(s => s.width));
240
+ const maxItemHeight = Math.max(...sizes.map(s => s.height));
241
+ const cellW = maxItemWidth + gap;
242
+ const cellH = maxItemHeight + gap;
243
+
244
+ for (let i = 0; i < this._items.length; i++) {
245
+ const item = this._items[i];
246
+ const col = i % columns;
247
+ const row = Math.floor(i / columns);
248
+ const size = sizes[i];
249
+
250
+ // X alignment within cell
251
+ switch (alignment) {
252
+ case 'center':
253
+ item.x = offsetX + col * cellW + (maxItemWidth - size.width) / 2;
254
+ break;
255
+ case 'end':
256
+ item.x = offsetX + col * cellW + maxItemWidth - size.width;
257
+ break;
258
+ default:
259
+ item.x = offsetX + col * cellW;
260
+ }
261
+
262
+ item.y = offsetY + row * cellH;
263
+ }
264
+ }
265
+
266
+ private layoutWrap(
267
+ maxWidth: number,
268
+ gap: number,
269
+ alignment: LayoutAlignment,
270
+ offsetX: number,
271
+ offsetY: number,
272
+ ): void {
273
+ let x = offsetX;
274
+ let y = offsetY;
275
+ let rowHeight = 0;
276
+ const sizes = this._items.map(item => this.getItemSize(item));
277
+
278
+ for (let i = 0; i < this._items.length; i++) {
279
+ const item = this._items[i];
280
+ const size = sizes[i];
281
+
282
+ // Check if item fits in current row
283
+ if (x + size.width > maxWidth + offsetX && x > offsetX) {
284
+ // Wrap to next row
285
+ x = offsetX;
286
+ y += rowHeight + gap;
287
+ rowHeight = 0;
288
+ }
289
+
290
+ item.x = x;
291
+ item.y = y;
292
+
293
+ x += size.width + gap;
294
+ rowHeight = Math.max(rowHeight, size.height);
295
+ }
296
+ }
297
+
298
+ private applyAnchor(anchor: LayoutAnchor): void {
299
+ if (this._viewportWidth === 0 || this._viewportHeight === 0) return;
300
+
301
+ const bounds = this._content.getBounds();
302
+ const contentW = bounds.width;
303
+ const contentH = bounds.height;
304
+ const vw = this._viewportWidth;
305
+ const vh = this._viewportHeight;
306
+
307
+ let anchorX = 0;
308
+ let anchorY = 0;
309
+
310
+ // Horizontal
311
+ if (anchor.includes('left')) {
312
+ anchorX = 0;
313
+ } else if (anchor.includes('right')) {
314
+ anchorX = vw - contentW;
315
+ } else {
316
+ // center
317
+ anchorX = (vw - contentW) / 2;
318
+ }
319
+
320
+ // Vertical
321
+ if (anchor.startsWith('top')) {
322
+ anchorY = 0;
323
+ } else if (anchor.startsWith('bottom')) {
324
+ anchorY = vh - contentH;
325
+ } else {
326
+ // center
327
+ anchorY = (vh - contentH) / 2;
328
+ }
329
+
330
+ // Compensate for content's local bounds offset
331
+ this.x = anchorX - bounds.x;
332
+ this.y = anchorY - bounds.y;
333
+ }
334
+
335
+ private resolveConfig(): Partial<LayoutConfig> {
336
+ if (this._breakpoints.length === 0 || this._viewportWidth === 0) {
337
+ return {};
338
+ }
339
+
340
+ // Find the largest breakpoint that's ≤ current viewport width
341
+ let resolved: Partial<LayoutConfig> = {};
342
+ for (const [maxWidth, overrides] of this._breakpoints) {
343
+ if (this._viewportWidth <= maxWidth) {
344
+ resolved = overrides;
345
+ break;
346
+ }
347
+ }
348
+ return resolved;
349
+ }
350
+
351
+ private getItemSize(item: Container): { width: number; height: number } {
352
+ const bounds = item.getBounds();
353
+ return { width: bounds.width, height: bounds.height };
354
+ }
355
+
356
+ private static normalizePadding(
357
+ padding: number | [number, number, number, number],
358
+ ): [number, number, number, number] {
359
+ if (typeof padding === 'number') {
360
+ return [padding, padding, padding, padding];
361
+ }
362
+ return padding;
363
+ }
364
+ }