@energy8platform/game-engine 0.1.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 +1134 -0
- package/dist/animation.cjs.js +505 -0
- package/dist/animation.cjs.js.map +1 -0
- package/dist/animation.d.ts +235 -0
- package/dist/animation.esm.js +500 -0
- package/dist/animation.esm.js.map +1 -0
- package/dist/assets.cjs.js +148 -0
- package/dist/assets.cjs.js.map +1 -0
- package/dist/assets.d.ts +97 -0
- package/dist/assets.esm.js +146 -0
- package/dist/assets.esm.js.map +1 -0
- package/dist/audio.cjs.js +345 -0
- package/dist/audio.cjs.js.map +1 -0
- package/dist/audio.d.ts +135 -0
- package/dist/audio.esm.js +343 -0
- package/dist/audio.esm.js.map +1 -0
- package/dist/core.cjs.js +1832 -0
- package/dist/core.cjs.js.map +1 -0
- package/dist/core.d.ts +633 -0
- package/dist/core.esm.js +1827 -0
- package/dist/core.esm.js.map +1 -0
- package/dist/debug.cjs.js +298 -0
- package/dist/debug.cjs.js.map +1 -0
- package/dist/debug.d.ts +114 -0
- package/dist/debug.esm.js +295 -0
- package/dist/debug.esm.js.map +1 -0
- package/dist/index.cjs.js +3623 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +1607 -0
- package/dist/index.esm.js +3598 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/ui.cjs.js +1081 -0
- package/dist/ui.cjs.js.map +1 -0
- package/dist/ui.d.ts +387 -0
- package/dist/ui.esm.js +1072 -0
- package/dist/ui.esm.js.map +1 -0
- package/dist/vite.cjs.js +125 -0
- package/dist/vite.cjs.js.map +1 -0
- package/dist/vite.d.ts +42 -0
- package/dist/vite.esm.js +122 -0
- package/dist/vite.esm.js.map +1 -0
- package/package.json +107 -0
- package/src/animation/Easing.ts +116 -0
- package/src/animation/SpineHelper.ts +162 -0
- package/src/animation/Timeline.ts +138 -0
- package/src/animation/Tween.ts +225 -0
- package/src/animation/index.ts +4 -0
- package/src/assets/AssetManager.ts +174 -0
- package/src/assets/index.ts +2 -0
- package/src/audio/AudioManager.ts +366 -0
- package/src/audio/index.ts +1 -0
- package/src/core/EventEmitter.ts +47 -0
- package/src/core/GameApplication.ts +306 -0
- package/src/core/Scene.ts +48 -0
- package/src/core/SceneManager.ts +258 -0
- package/src/core/index.ts +4 -0
- package/src/debug/DevBridge.ts +259 -0
- package/src/debug/FPSOverlay.ts +102 -0
- package/src/debug/index.ts +3 -0
- package/src/index.ts +71 -0
- package/src/input/InputManager.ts +171 -0
- package/src/input/index.ts +1 -0
- package/src/loading/CSSPreloader.ts +155 -0
- package/src/loading/LoadingScene.ts +356 -0
- package/src/loading/index.ts +2 -0
- package/src/state/StateMachine.ts +228 -0
- package/src/state/index.ts +1 -0
- package/src/types.ts +218 -0
- package/src/ui/BalanceDisplay.ts +155 -0
- package/src/ui/Button.ts +199 -0
- package/src/ui/Label.ts +111 -0
- package/src/ui/Modal.ts +134 -0
- package/src/ui/Panel.ts +125 -0
- package/src/ui/ProgressBar.ts +121 -0
- package/src/ui/Toast.ts +124 -0
- package/src/ui/WinDisplay.ts +133 -0
- package/src/ui/index.ts +16 -0
- package/src/viewport/ViewportManager.ts +241 -0
- package/src/viewport/index.ts +1 -0
- package/src/vite/index.ts +153 -0
package/README.md
ADDED
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
# @energy8platform/game-engine
|
|
2
|
+
|
|
3
|
+
A universal casino game engine built on [PixiJS v8](https://pixijs.com/) and [@energy8platform/game-sdk](https://github.com/energy8platform/game-sdk). Provides a batteries-included framework for developing slot machines, card games, and other iGaming titles with responsive scaling, animated loading screens, scene management, audio, state machines, tweens, and a rich UI component library.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Architecture](#architecture)
|
|
12
|
+
- [Configuration](#configuration)
|
|
13
|
+
- [Scenes](#scenes)
|
|
14
|
+
- [Lifecycle](#lifecycle)
|
|
15
|
+
- [Assets](#assets)
|
|
16
|
+
- [Audio](#audio)
|
|
17
|
+
- [Viewport & Scaling](#viewport--scaling)
|
|
18
|
+
- [State Machine](#state-machine)
|
|
19
|
+
- [Animation](#animation)
|
|
20
|
+
- [UI Components](#ui-components)
|
|
21
|
+
- [Input](#input)
|
|
22
|
+
- [Vite Configuration](#vite-configuration)
|
|
23
|
+
- [DevBridge](#devbridge)
|
|
24
|
+
- [Debug](#debug)
|
|
25
|
+
- [API Reference](#api-reference)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Create a new project
|
|
34
|
+
mkdir my-game && cd my-game
|
|
35
|
+
npm init -y
|
|
36
|
+
|
|
37
|
+
# Install dependencies
|
|
38
|
+
npm install pixi.js @energy8platform/game-sdk @energy8platform/game-engine
|
|
39
|
+
|
|
40
|
+
# (Optional) install spine and audio support
|
|
41
|
+
npm install @pixi/sound @esotericsoftware/spine-pixi-v8
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Create the entry point:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// src/main.ts
|
|
48
|
+
import { GameApplication, ScaleMode } from '@energy8platform/game-engine';
|
|
49
|
+
import { GameScene } from './scenes/GameScene';
|
|
50
|
+
|
|
51
|
+
async function bootstrap() {
|
|
52
|
+
const game = new GameApplication({
|
|
53
|
+
container: '#game',
|
|
54
|
+
designWidth: 1920,
|
|
55
|
+
designHeight: 1080,
|
|
56
|
+
scaleMode: ScaleMode.FIT,
|
|
57
|
+
loading: {
|
|
58
|
+
backgroundColor: 0x0a0a1a,
|
|
59
|
+
backgroundGradient:
|
|
60
|
+
'linear-gradient(135deg, #0a0a1a 0%, #1a1a3e 50%, #0a0a1a 100%)',
|
|
61
|
+
showPercentage: true,
|
|
62
|
+
tapToStart: true,
|
|
63
|
+
tapToStartText: 'TAP TO PLAY',
|
|
64
|
+
minDisplayTime: 2000,
|
|
65
|
+
},
|
|
66
|
+
manifest: {
|
|
67
|
+
bundles: [
|
|
68
|
+
{ name: 'preload', assets: [] },
|
|
69
|
+
{
|
|
70
|
+
name: 'game',
|
|
71
|
+
assets: [
|
|
72
|
+
{ alias: 'background', src: 'background.png' },
|
|
73
|
+
{ alias: 'symbols', src: 'symbols.json' },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
audio: {
|
|
79
|
+
music: 0.5,
|
|
80
|
+
sfx: 1.0,
|
|
81
|
+
persist: true,
|
|
82
|
+
},
|
|
83
|
+
debug: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
game.scenes.register('game', GameScene as any);
|
|
87
|
+
|
|
88
|
+
game.on('initialized', () => console.log('Engine initialized'));
|
|
89
|
+
game.on('loaded', () => console.log('Assets loaded'));
|
|
90
|
+
game.on('started', () => console.log('Game started'));
|
|
91
|
+
game.on('error', (err) => console.error('Error:', err));
|
|
92
|
+
|
|
93
|
+
await game.start('game');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
bootstrap();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Installation
|
|
102
|
+
|
|
103
|
+
### Peer Dependencies
|
|
104
|
+
|
|
105
|
+
| Package | Version | Required |
|
|
106
|
+
| --- | --- | --- |
|
|
107
|
+
| `pixi.js` | `^8.16.0` | Yes |
|
|
108
|
+
| `@energy8platform/game-sdk` | `^2.5.0` | Yes |
|
|
109
|
+
| `@pixi/sound` | `^6.0.0` | Optional — for audio |
|
|
110
|
+
| `@esotericsoftware/spine-pixi-v8` | `~4.2.0` | Optional — for Spine animations |
|
|
111
|
+
|
|
112
|
+
### Sub-path Exports
|
|
113
|
+
|
|
114
|
+
The package exposes granular entry points for tree-shaking:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { GameApplication } from '@energy8platform/game-engine'; // full bundle
|
|
118
|
+
import { Scene, SceneManager } from '@energy8platform/game-engine/core';
|
|
119
|
+
import { AssetManager } from '@energy8platform/game-engine/assets';
|
|
120
|
+
import { AudioManager } from '@energy8platform/game-engine/audio';
|
|
121
|
+
import { Button, Label, Modal } from '@energy8platform/game-engine/ui';
|
|
122
|
+
import { Tween, Timeline, Easing } from '@energy8platform/game-engine/animation';
|
|
123
|
+
import { DevBridge, FPSOverlay } from '@energy8platform/game-engine/debug';
|
|
124
|
+
import { defineGameConfig } from '@energy8platform/game-engine/vite';
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Architecture
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
┌──────────────────────────────────────────────────────┐
|
|
133
|
+
│ GameApplication │
|
|
134
|
+
│ (orchestrates lifecycle, holds all sub-systems) │
|
|
135
|
+
├──────────┬───────────┬───────────┬───────────────────┤
|
|
136
|
+
│ Viewport │ Scenes │ Assets │ Audio │ Input │
|
|
137
|
+
│ Manager │ Manager │ Manager │ Manager │ Manager │
|
|
138
|
+
├──────────┴───────────┴───────────┴─────────┴─────────┤
|
|
139
|
+
│ PixiJS v8 Application │
|
|
140
|
+
├──────────────────────────────────────────────────────┤
|
|
141
|
+
│ @energy8platform/game-sdk │
|
|
142
|
+
└──────────────────────────────────────────────────────┘
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Boot Sequence
|
|
146
|
+
|
|
147
|
+
1. **CSS Preloader** — an instant HTML/CSS overlay shown while PixiJS initializes (inline SVG logo with a shimmer animation and "Loading..." text).
|
|
148
|
+
2. **PixiJS initialization** — creates `Application`, initializes `ResizeObserver`.
|
|
149
|
+
3. **SDK handshake** — connects to the casino host (or DevBridge in dev mode).
|
|
150
|
+
4. **Canvas Loading Screen** — `LoadingScene` displays the SVG logo with an animated progress bar, `preload` bundle is loaded first.
|
|
151
|
+
5. **Asset loading** — remaining bundles are loaded; the progress bar fills in real time.
|
|
152
|
+
6. **Tap-to-start** — optional screen shown after loading (required on mobile for audio unlock).
|
|
153
|
+
7. **First scene** — transitions to the registered first scene.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
### `GameApplicationConfig`
|
|
160
|
+
|
|
161
|
+
| Property | Type | Default | Description |
|
|
162
|
+
| --- | --- | --- | --- |
|
|
163
|
+
| `container` | `HTMLElement \| string` | `document.body` | Container element or CSS selector |
|
|
164
|
+
| `designWidth` | `number` | `1920` | Reference design width |
|
|
165
|
+
| `designHeight` | `number` | `1080` | Reference design height |
|
|
166
|
+
| `scaleMode` | `ScaleMode` | `FIT` | Scaling strategy |
|
|
167
|
+
| `orientation` | `Orientation` | `ANY` | Preferred orientation |
|
|
168
|
+
| `loading` | `LoadingScreenConfig` | — | Loading screen options |
|
|
169
|
+
| `manifest` | `AssetManifest` | — | Asset manifest |
|
|
170
|
+
| `audio` | `AudioConfig` | — | Audio configuration |
|
|
171
|
+
| `sdk` | `object \| false` | — | SDK options; `false` to disable |
|
|
172
|
+
| `pixi` | `Partial<ApplicationOptions>` | — | PixiJS pass-through options |
|
|
173
|
+
| `debug` | `boolean` | `false` | Enable FPS overlay |
|
|
174
|
+
|
|
175
|
+
### `LoadingScreenConfig`
|
|
176
|
+
|
|
177
|
+
| Property | Type | Default | Description |
|
|
178
|
+
| --- | --- | --- | --- |
|
|
179
|
+
| `backgroundColor` | `number \| string` | `0x0a0a1a` | Background color |
|
|
180
|
+
| `backgroundGradient` | `string` | — | CSS gradient for the preloader background |
|
|
181
|
+
| `logoAsset` | `string` | — | Logo texture alias from `preload` bundle |
|
|
182
|
+
| `logoScale` | `number` | `1` | Logo scale factor |
|
|
183
|
+
| `showPercentage` | `boolean` | `true` | Show loading percentage text |
|
|
184
|
+
| `progressTextFormatter` | `(progress: number) => string` | — | Custom progress text formatter |
|
|
185
|
+
| `tapToStart` | `boolean` | `false` | Show "Tap to start" overlay |
|
|
186
|
+
| `tapToStartText` | `string` | `'TAP TO START'` | Tap-to-start label |
|
|
187
|
+
| `minDisplayTime` | `number` | `0` | Minimum display time (ms) |
|
|
188
|
+
| `cssPreloaderHTML` | `string` | — | Custom HTML for the CSS preloader |
|
|
189
|
+
|
|
190
|
+
### `AudioConfig`
|
|
191
|
+
|
|
192
|
+
| Property | Type | Default | Description |
|
|
193
|
+
| --- | --- | --- | --- |
|
|
194
|
+
| `music` | `number` | `1` | Default music volume (0–1) |
|
|
195
|
+
| `sfx` | `number` | `1` | Default SFX volume |
|
|
196
|
+
| `ui` | `number` | `1` | Default UI sounds volume |
|
|
197
|
+
| `ambient` | `number` | `1` | Default ambient volume |
|
|
198
|
+
| `persist` | `boolean` | `false` | Save mute state to localStorage |
|
|
199
|
+
| `storageKey` | `string` | `'ge-audio'` | localStorage key prefix |
|
|
200
|
+
|
|
201
|
+
### Enums
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
enum ScaleMode {
|
|
205
|
+
FIT = 'FIT', // Letterbox/pillarbox — preserves aspect ratio
|
|
206
|
+
FILL = 'FILL', // Fill container, crop edges
|
|
207
|
+
STRETCH = 'STRETCH' // Stretch to fill (distorts)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
enum Orientation {
|
|
211
|
+
LANDSCAPE = 'landscape',
|
|
212
|
+
PORTRAIT = 'portrait',
|
|
213
|
+
ANY = 'any'
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
enum TransitionType {
|
|
217
|
+
NONE = 'none',
|
|
218
|
+
FADE = 'fade',
|
|
219
|
+
SLIDE_LEFT = 'slide-left',
|
|
220
|
+
SLIDE_RIGHT = 'slide-right'
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Scenes
|
|
227
|
+
|
|
228
|
+
All game screens are scenes. Extend the base `Scene` class and override lifecycle hooks:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { Scene } from '@energy8platform/game-engine';
|
|
232
|
+
import { Sprite, Assets } from 'pixi.js';
|
|
233
|
+
|
|
234
|
+
export class GameScene extends Scene {
|
|
235
|
+
async onEnter(data?: unknown) {
|
|
236
|
+
const bg = new Sprite(Assets.get('background'));
|
|
237
|
+
this.container.addChild(bg);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
onUpdate(dt: number) {
|
|
241
|
+
// Called every frame (dt = delta time from PixiJS ticker)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
onResize(width: number, height: number) {
|
|
245
|
+
// Called on viewport resize — use for responsive layout
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async onExit() {
|
|
249
|
+
// Cleanup before leaving the scene
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
onDestroy() {
|
|
253
|
+
// Final cleanup when the scene is removed from the stack
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Scene Navigation
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const scenes = game.scenes;
|
|
262
|
+
|
|
263
|
+
// Register scenes
|
|
264
|
+
scenes.register('menu', MenuScene as any);
|
|
265
|
+
scenes.register('game', GameScene as any);
|
|
266
|
+
scenes.register('bonus', BonusScene as any);
|
|
267
|
+
|
|
268
|
+
// Navigate (replaces entire stack)
|
|
269
|
+
await scenes.goto('game');
|
|
270
|
+
|
|
271
|
+
// Push overlay/modal (previous scene stays rendered)
|
|
272
|
+
await scenes.push('bonus', { multiplier: 3 });
|
|
273
|
+
|
|
274
|
+
// Pop back
|
|
275
|
+
await scenes.pop();
|
|
276
|
+
|
|
277
|
+
// Replace top scene
|
|
278
|
+
await scenes.replace('game');
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Transitions
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import { TransitionType } from '@energy8platform/game-engine';
|
|
285
|
+
|
|
286
|
+
await scenes.goto('game', undefined, {
|
|
287
|
+
type: TransitionType.FADE,
|
|
288
|
+
duration: 500, // ms
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await scenes.push('bonus', { data: 42 }, {
|
|
292
|
+
type: TransitionType.SLIDE_LEFT,
|
|
293
|
+
duration: 300,
|
|
294
|
+
easing: Easing.easeOutCubic,
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Lifecycle
|
|
301
|
+
|
|
302
|
+
`GameApplication` emits the following events:
|
|
303
|
+
|
|
304
|
+
| Event | Payload | When |
|
|
305
|
+
| --- | --- | --- |
|
|
306
|
+
| `initialized` | `void` | Engine initialized, PixiJS and SDK are ready |
|
|
307
|
+
| `loaded` | `void` | All asset bundles loaded |
|
|
308
|
+
| `started` | `void` | First scene entered, game loop running |
|
|
309
|
+
| `resize` | `{ width, height }` | Viewport resized |
|
|
310
|
+
| `orientationChange` | `Orientation` | Device orientation changed |
|
|
311
|
+
| `sceneChange` | `{ from, to }` | Scene transition completed |
|
|
312
|
+
| `error` | `Error` | An error occurred |
|
|
313
|
+
| `destroyed` | `void` | Engine destroyed |
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
game.on('resize', ({ width, height }) => {
|
|
317
|
+
console.log(`New size: ${width}x${height}`);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
game.once('started', () => {
|
|
321
|
+
// Runs once after the first scene starts
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Assets
|
|
328
|
+
|
|
329
|
+
### Asset Manifest
|
|
330
|
+
|
|
331
|
+
Assets are organized in named **bundles**:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
const manifest: AssetManifest = {
|
|
335
|
+
bundles: [
|
|
336
|
+
{
|
|
337
|
+
name: 'preload',
|
|
338
|
+
assets: [
|
|
339
|
+
{ alias: 'logo', src: 'logo.png' },
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: 'game',
|
|
344
|
+
assets: [
|
|
345
|
+
{ alias: 'background', src: 'bg.webp' },
|
|
346
|
+
{ alias: 'symbols', src: 'symbols.json' },
|
|
347
|
+
{ alias: 'win-sound', src: 'sounds/win.mp3' },
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'bonus',
|
|
352
|
+
assets: [
|
|
353
|
+
{ alias: 'bonus-bg', src: 'bonus/bg.webp' },
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The `preload` bundle is loaded first (before the progress bar fills). All other bundles load together with a combined progress indicator.
|
|
361
|
+
|
|
362
|
+
### AssetManager API
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const assets = game.assets;
|
|
366
|
+
|
|
367
|
+
// Load on demand
|
|
368
|
+
await assets.loadBundle('bonus', (progress) => {
|
|
369
|
+
console.log(`${Math.round(progress * 100)}%`);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Synchronous cache access
|
|
373
|
+
const texture = assets.get<Texture>('background');
|
|
374
|
+
|
|
375
|
+
// Background preloading (low priority)
|
|
376
|
+
await assets.backgroundLoad('bonus');
|
|
377
|
+
|
|
378
|
+
// Unload to free memory
|
|
379
|
+
await assets.unloadBundle('bonus');
|
|
380
|
+
|
|
381
|
+
// Check state
|
|
382
|
+
assets.isBundleLoaded('game'); // true
|
|
383
|
+
assets.getBundleNames(); // ['preload', 'game', 'bonus']
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Audio
|
|
389
|
+
|
|
390
|
+
`AudioManager` wraps `@pixi/sound` with category-based volume management. If `@pixi/sound` is not installed, all methods work silently as no-ops.
|
|
391
|
+
|
|
392
|
+
### Audio Categories
|
|
393
|
+
|
|
394
|
+
Four categories with independent volume and mute controls: `music`, `sfx`, `ui`, `ambient`.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
const audio = game.audio;
|
|
398
|
+
|
|
399
|
+
// Play a sound effect
|
|
400
|
+
audio.play('click', 'ui');
|
|
401
|
+
audio.play('coin-drop', 'sfx', { volume: 0.8 });
|
|
402
|
+
|
|
403
|
+
// Music with crossfade
|
|
404
|
+
audio.playMusic('main-theme', 1000); // 1s crossfade
|
|
405
|
+
audio.stopMusic();
|
|
406
|
+
|
|
407
|
+
// Volume control
|
|
408
|
+
audio.setVolume('music', 0.3);
|
|
409
|
+
audio.muteCategory('sfx');
|
|
410
|
+
audio.unmuteCategory('sfx');
|
|
411
|
+
audio.toggleCategory('sfx'); // returns new state
|
|
412
|
+
|
|
413
|
+
// Global mute
|
|
414
|
+
audio.muteAll();
|
|
415
|
+
audio.unmuteAll();
|
|
416
|
+
audio.toggleMute(); // returns new state
|
|
417
|
+
|
|
418
|
+
// Music ducking (e.g., during a big win animation)
|
|
419
|
+
audio.duckMusic(0.2); // reduce to 20%
|
|
420
|
+
audio.unduckMusic(); // restore
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Mobile Audio Unlock
|
|
424
|
+
|
|
425
|
+
On iOS and many mobile browsers, audio cannot play until the first user interaction. The engine handles this automatically when `tapToStart: true` is set — the tap event serves as the audio context unlock.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Viewport & Scaling
|
|
430
|
+
|
|
431
|
+
`ViewportManager` handles responsive scaling using `ResizeObserver` with debouncing.
|
|
432
|
+
|
|
433
|
+
### Scale Modes
|
|
434
|
+
|
|
435
|
+
| Mode | Behavior |
|
|
436
|
+
| --- | --- |
|
|
437
|
+
| `FIT` | Fits the entire design area inside the container. Adds letterbox (horizontal bars) or pillarbox (vertical bars) as needed. **Industry standard for iGaming.** |
|
|
438
|
+
| `FILL` | Fills the entire container, cropping edges. No bars, but some content may be hidden. |
|
|
439
|
+
| `STRETCH` | Stretches to fill the container. Distorts aspect ratio. Not recommended. |
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
const vp = game.viewport;
|
|
443
|
+
|
|
444
|
+
// Current dimensions
|
|
445
|
+
console.log(vp.width, vp.height, vp.scale);
|
|
446
|
+
console.log(vp.orientation); // 'landscape' | 'portrait'
|
|
447
|
+
|
|
448
|
+
// Reference design size
|
|
449
|
+
console.log(vp.designWidth, vp.designHeight);
|
|
450
|
+
|
|
451
|
+
// Force re-calculation
|
|
452
|
+
vp.refresh();
|
|
453
|
+
|
|
454
|
+
// Listen for changes
|
|
455
|
+
game.on('resize', ({ width, height }) => {
|
|
456
|
+
// Respond to viewport changes
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## State Machine
|
|
463
|
+
|
|
464
|
+
`StateMachine` is a generic finite state machine with typed context, async hooks, guards, and per-frame updates.
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
import { StateMachine } from '@energy8platform/game-engine';
|
|
468
|
+
|
|
469
|
+
interface GameContext {
|
|
470
|
+
balance: number;
|
|
471
|
+
bet: number;
|
|
472
|
+
lastWin: number;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const fsm = new StateMachine<GameContext>({
|
|
476
|
+
balance: 10000,
|
|
477
|
+
bet: 100,
|
|
478
|
+
lastWin: 0,
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
fsm.addState('idle', {
|
|
482
|
+
enter: (ctx) => console.log('Waiting for player...'),
|
|
483
|
+
update: (ctx, dt) => { /* per-frame logic */ },
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
fsm.addState('spinning', {
|
|
487
|
+
enter: async (ctx) => {
|
|
488
|
+
// Play spin animation
|
|
489
|
+
await spinReels();
|
|
490
|
+
// Auto-transition to result
|
|
491
|
+
await fsm.transition('result');
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
fsm.addState('result', {
|
|
496
|
+
enter: async (ctx) => {
|
|
497
|
+
if (ctx.lastWin > 0) {
|
|
498
|
+
await showWinAnimation(ctx.lastWin);
|
|
499
|
+
}
|
|
500
|
+
await fsm.transition('idle');
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Guards
|
|
505
|
+
fsm.addGuard('idle', 'spinning', (ctx) => ctx.balance >= ctx.bet);
|
|
506
|
+
|
|
507
|
+
// Events
|
|
508
|
+
fsm.on('transition', ({ from, to }) => {
|
|
509
|
+
console.log(`${from} → ${to}`);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Start
|
|
513
|
+
await fsm.start('idle');
|
|
514
|
+
|
|
515
|
+
// Trigger transitions
|
|
516
|
+
const success = await fsm.transition('spinning');
|
|
517
|
+
if (!success) {
|
|
518
|
+
console.log('Transition blocked by guard');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Per-frame update (usually called from Scene.onUpdate)
|
|
522
|
+
fsm.update(dt);
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Animation
|
|
528
|
+
|
|
529
|
+
### Tween
|
|
530
|
+
|
|
531
|
+
`Tween` provides a Promise-based animation system integrated with the PixiJS Ticker:
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
import { Tween, Easing } from '@energy8platform/game-engine';
|
|
535
|
+
|
|
536
|
+
// Animate to target values
|
|
537
|
+
await Tween.to(sprite, { alpha: 0, y: 100 }, 500, Easing.easeOutCubic);
|
|
538
|
+
|
|
539
|
+
// Animate from starting values to current
|
|
540
|
+
await Tween.from(sprite, { scale: 0 }, 300, Easing.easeOutBack);
|
|
541
|
+
|
|
542
|
+
// Animate between two sets of values
|
|
543
|
+
await Tween.fromTo(sprite, { x: -100 }, { x: 500 }, 1000, Easing.easeInOutQuad);
|
|
544
|
+
|
|
545
|
+
// Wait (useful in timelines or sequences)
|
|
546
|
+
await Tween.delay(1000);
|
|
547
|
+
|
|
548
|
+
// Cancel tweens
|
|
549
|
+
Tween.killTweensOf(sprite);
|
|
550
|
+
Tween.killAll();
|
|
551
|
+
|
|
552
|
+
// Supports nested properties
|
|
553
|
+
await Tween.to(sprite, { 'scale.x': 2, 'position.y': 300 }, 500);
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Timeline
|
|
557
|
+
|
|
558
|
+
`Timeline` chains sequential and parallel animation steps:
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
import { Timeline, Tween, Easing } from '@energy8platform/game-engine';
|
|
562
|
+
|
|
563
|
+
const tl = new Timeline();
|
|
564
|
+
|
|
565
|
+
tl.to(title, { alpha: 1, y: 0 }, 500, Easing.easeOutCubic)
|
|
566
|
+
.delay(200)
|
|
567
|
+
.parallel(
|
|
568
|
+
() => Tween.to(btn1, { alpha: 1 }, 300),
|
|
569
|
+
() => Tween.to(btn2, { alpha: 1 }, 300),
|
|
570
|
+
() => Tween.to(btn3, { alpha: 1 }, 300),
|
|
571
|
+
)
|
|
572
|
+
.call(() => console.log('Intro complete!'));
|
|
573
|
+
|
|
574
|
+
await tl.play();
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Available Easings
|
|
578
|
+
|
|
579
|
+
All 24 easing functions:
|
|
580
|
+
|
|
581
|
+
| Linear | Quad | Cubic | Quart |
|
|
582
|
+
| --- | --- | --- | --- |
|
|
583
|
+
| `linear` | `easeInQuad` | `easeInCubic` | `easeInQuart` |
|
|
584
|
+
| | `easeOutQuad` | `easeOutCubic` | `easeOutQuart` |
|
|
585
|
+
| | `easeInOutQuad` | `easeInOutCubic` | `easeInOutQuart` |
|
|
586
|
+
|
|
587
|
+
| Sine | Expo | Back | Bounce / Elastic |
|
|
588
|
+
| --- | --- | --- | --- |
|
|
589
|
+
| `easeInSine` | `easeInExpo` | `easeInBack` | `easeOutBounce` |
|
|
590
|
+
| `easeOutSine` | `easeOutExpo` | `easeOutBack` | `easeInBounce` |
|
|
591
|
+
| `easeInOutSine` | `easeInOutExpo` | `easeInOutBack` | `easeInOutBounce` |
|
|
592
|
+
| | | | `easeOutElastic` |
|
|
593
|
+
| | | | `easeInElastic` |
|
|
594
|
+
|
|
595
|
+
### Spine Animations
|
|
596
|
+
|
|
597
|
+
If `@esotericsoftware/spine-pixi-v8` is installed:
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
import { SpineHelper } from '@energy8platform/game-engine';
|
|
601
|
+
|
|
602
|
+
// Create a Spine instance
|
|
603
|
+
const spine = await SpineHelper.create('character-skel', 'character-atlas', {
|
|
604
|
+
scale: 0.5,
|
|
605
|
+
});
|
|
606
|
+
container.addChild(spine);
|
|
607
|
+
|
|
608
|
+
// Play animation (returns Promise that resolves on completion)
|
|
609
|
+
await SpineHelper.playAnimation(spine, 'idle', true); // loop
|
|
610
|
+
|
|
611
|
+
// Queue animation
|
|
612
|
+
SpineHelper.addAnimation(spine, 'walk', 0.2, true);
|
|
613
|
+
|
|
614
|
+
// Skins
|
|
615
|
+
SpineHelper.setSkin(spine, 'warrior');
|
|
616
|
+
console.log(SpineHelper.getSkinNames(spine));
|
|
617
|
+
console.log(SpineHelper.getAnimationNames(spine));
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## UI Components
|
|
623
|
+
|
|
624
|
+
### Button
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
import { Button } from '@energy8platform/game-engine';
|
|
628
|
+
|
|
629
|
+
const spinBtn = new Button({
|
|
630
|
+
width: 200,
|
|
631
|
+
height: 60,
|
|
632
|
+
borderRadius: 12,
|
|
633
|
+
colors: {
|
|
634
|
+
normal: 0xffd700,
|
|
635
|
+
hover: 0xffe44d,
|
|
636
|
+
pressed: 0xccac00,
|
|
637
|
+
disabled: 0x666666,
|
|
638
|
+
},
|
|
639
|
+
pressScale: 0.95,
|
|
640
|
+
animationDuration: 100,
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
spinBtn.onTap = () => {
|
|
644
|
+
console.log('Spin!');
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// Or use texture-based states
|
|
648
|
+
const btn = new Button({
|
|
649
|
+
textures: {
|
|
650
|
+
normal: 'btn-normal',
|
|
651
|
+
hover: 'btn-hover',
|
|
652
|
+
pressed: 'btn-pressed',
|
|
653
|
+
disabled: 'btn-disabled',
|
|
654
|
+
},
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
btn.enable();
|
|
658
|
+
btn.disable();
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Label
|
|
662
|
+
|
|
663
|
+
```typescript
|
|
664
|
+
import { Label } from '@energy8platform/game-engine';
|
|
665
|
+
|
|
666
|
+
const label = new Label({
|
|
667
|
+
text: 'TOTAL WIN',
|
|
668
|
+
style: { fontSize: 36, fill: 0xffffff },
|
|
669
|
+
});
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### BalanceDisplay
|
|
673
|
+
|
|
674
|
+
Displays player balance with formatting:
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
import { BalanceDisplay } from '@energy8platform/game-engine';
|
|
678
|
+
|
|
679
|
+
const balance = new BalanceDisplay({
|
|
680
|
+
initialBalance: 10000,
|
|
681
|
+
currency: 'USD',
|
|
682
|
+
// ... text style options
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
balance.updateBalance(9500); // Animates the balance change
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### WinDisplay
|
|
689
|
+
|
|
690
|
+
Animated win amount display with countup:
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
import { WinDisplay } from '@energy8platform/game-engine';
|
|
694
|
+
|
|
695
|
+
const winDisplay = new WinDisplay({
|
|
696
|
+
// ... text style options
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
await winDisplay.show(5000, 2000); // Show $50.00, countup over 2 seconds
|
|
700
|
+
winDisplay.hide();
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### ProgressBar
|
|
704
|
+
|
|
705
|
+
```typescript
|
|
706
|
+
import { ProgressBar } from '@energy8platform/game-engine';
|
|
707
|
+
|
|
708
|
+
const bar = new ProgressBar({
|
|
709
|
+
width: 400,
|
|
710
|
+
height: 20,
|
|
711
|
+
fillColor: 0x00ff00,
|
|
712
|
+
backgroundColor: 0x333333,
|
|
713
|
+
borderRadius: 10,
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
bar.setProgress(0.75); // 75%
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### Panel
|
|
720
|
+
|
|
721
|
+
Container with a background:
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
import { Panel } from '@energy8platform/game-engine';
|
|
725
|
+
|
|
726
|
+
const panel = new Panel({
|
|
727
|
+
width: 600,
|
|
728
|
+
height: 400,
|
|
729
|
+
backgroundColor: 0x1a1a2e,
|
|
730
|
+
borderRadius: 16,
|
|
731
|
+
alpha: 0.9,
|
|
732
|
+
});
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Modal
|
|
736
|
+
|
|
737
|
+
Full-screen overlay dialog:
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
import { Modal } from '@energy8platform/game-engine';
|
|
741
|
+
|
|
742
|
+
const modal = new Modal({
|
|
743
|
+
width: 500,
|
|
744
|
+
height: 350,
|
|
745
|
+
overlayAlpha: 0.7,
|
|
746
|
+
backgroundColor: 0x222244,
|
|
747
|
+
borderRadius: 16,
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
await modal.show();
|
|
751
|
+
await modal.hide();
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Toast
|
|
755
|
+
|
|
756
|
+
Brief notification messages:
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
import { Toast } from '@energy8platform/game-engine';
|
|
760
|
+
|
|
761
|
+
const toast = new Toast({
|
|
762
|
+
duration: 2000,
|
|
763
|
+
backgroundColor: 0x333333,
|
|
764
|
+
textStyle: { fill: 0xffffff, fontSize: 20 },
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
await toast.show('Free spins activated!');
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
---
|
|
771
|
+
|
|
772
|
+
## Input
|
|
773
|
+
|
|
774
|
+
`InputManager` provides unified touch/mouse/keyboard handling with gesture detection:
|
|
775
|
+
|
|
776
|
+
```typescript
|
|
777
|
+
const input = game.input;
|
|
778
|
+
|
|
779
|
+
// Tap/click
|
|
780
|
+
input.on('tap', ({ x, y }) => {
|
|
781
|
+
console.log(`Tap at ${x}, ${y}`);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// Swipe gesture
|
|
785
|
+
input.on('swipe', ({ direction, velocity }) => {
|
|
786
|
+
console.log(`Swipe ${direction} at ${velocity}px/s`);
|
|
787
|
+
// direction: 'up' | 'down' | 'left' | 'right'
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
// Keyboard
|
|
791
|
+
input.on('keydown', ({ key, code }) => {
|
|
792
|
+
if (code === 'Space') startSpin();
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Check current key state
|
|
796
|
+
if (input.isKeyDown('ArrowLeft')) {
|
|
797
|
+
// Move left
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Lock input during animations
|
|
801
|
+
input.lock();
|
|
802
|
+
// ... animation plays ...
|
|
803
|
+
input.unlock();
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
**Events:** `tap`, `press`, `release`, `move`, `swipe`, `keydown`, `keyup`
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
## Vite Configuration
|
|
811
|
+
|
|
812
|
+
The engine provides a pre-configured Vite setup via `defineGameConfig`:
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
// vite.config.ts
|
|
816
|
+
import { defineGameConfig } from '@energy8platform/game-engine/vite';
|
|
817
|
+
|
|
818
|
+
export default defineGameConfig({
|
|
819
|
+
base: '/games/my-slot/',
|
|
820
|
+
outDir: 'dist',
|
|
821
|
+
devBridge: true, // Auto-inject DevBridge in dev mode
|
|
822
|
+
assetExtensions: [
|
|
823
|
+
'png', 'jpg', 'webp', 'avif',
|
|
824
|
+
'mp3', 'ogg', 'wav',
|
|
825
|
+
'json', 'atlas', 'skel',
|
|
826
|
+
'fnt', 'ttf', 'woff2',
|
|
827
|
+
],
|
|
828
|
+
vite: {
|
|
829
|
+
// Additional Vite config overrides
|
|
830
|
+
},
|
|
831
|
+
});
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### What `defineGameConfig` Provides
|
|
835
|
+
|
|
836
|
+
- **PixiJS optimizations** — tree-shaking, proper externals handling
|
|
837
|
+
- **DevBridge injection** — automatically available in dev mode via virtual module
|
|
838
|
+
- **Asset handling** — preconfigured loader patterns for game assets
|
|
839
|
+
- **HTML minification** — optimized production builds
|
|
840
|
+
- **Gzip-friendly output** — chunking strategy optimized for compression
|
|
841
|
+
- **Base path** — configurable for deployment subpaths
|
|
842
|
+
|
|
843
|
+
### Custom DevBridge Configuration
|
|
844
|
+
|
|
845
|
+
Create `dev.config.ts` at the project root:
|
|
846
|
+
|
|
847
|
+
```typescript
|
|
848
|
+
// dev.config.ts
|
|
849
|
+
import type { DevBridgeConfig } from '@energy8platform/game-engine/debug';
|
|
850
|
+
|
|
851
|
+
export default {
|
|
852
|
+
balance: 50000,
|
|
853
|
+
currency: 'EUR',
|
|
854
|
+
networkDelay: 100,
|
|
855
|
+
onPlay: ({ action, bet }) => {
|
|
856
|
+
// Custom play result logic
|
|
857
|
+
const win = Math.random() < 0.3 ? bet * 10 : 0;
|
|
858
|
+
return { win, balance: 50000 - bet + win };
|
|
859
|
+
},
|
|
860
|
+
} satisfies DevBridgeConfig;
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
This file is auto-imported by the Vite plugin when `devBridge: true`.
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
## DevBridge
|
|
868
|
+
|
|
869
|
+
`DevBridge` simulates a casino host for local development. It intercepts `postMessage` calls from the SDK and responds with mock data.
|
|
870
|
+
|
|
871
|
+
```typescript
|
|
872
|
+
import { DevBridge } from '@energy8platform/game-engine/debug';
|
|
873
|
+
|
|
874
|
+
const bridge = new DevBridge({
|
|
875
|
+
balance: 10000,
|
|
876
|
+
currency: 'USD',
|
|
877
|
+
assetsUrl: '/assets/',
|
|
878
|
+
networkDelay: 200,
|
|
879
|
+
debug: true,
|
|
880
|
+
gameConfig: {
|
|
881
|
+
viewport: { width: 1920, height: 1080 },
|
|
882
|
+
},
|
|
883
|
+
onPlay: ({ action, bet, roundId }) => {
|
|
884
|
+
// Return custom play result
|
|
885
|
+
const win = Math.random() < 0.4 ? bet * 5 : 0;
|
|
886
|
+
return { win };
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
bridge.start();
|
|
891
|
+
|
|
892
|
+
// Update balance programmatically
|
|
893
|
+
bridge.setBalance(5000);
|
|
894
|
+
|
|
895
|
+
// Cleanup
|
|
896
|
+
bridge.destroy();
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
### Handled Messages
|
|
900
|
+
|
|
901
|
+
| Message | Description |
|
|
902
|
+
| --- | --- |
|
|
903
|
+
| `GAME_READY` | SDK initialization handshake |
|
|
904
|
+
| `PLAY_REQUEST` | Player action (spin, deal, etc.) |
|
|
905
|
+
| `PLAY_RESULT_ACK` | Acknowledge play result |
|
|
906
|
+
| `GET_BALANCE` | Balance query |
|
|
907
|
+
| `GET_STATE` | Game state query |
|
|
908
|
+
| `OPEN_DEPOSIT` | Deposit dialog request |
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
## Debug
|
|
913
|
+
|
|
914
|
+
### FPS Overlay
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
import { FPSOverlay } from '@energy8platform/game-engine/debug';
|
|
918
|
+
|
|
919
|
+
const fps = new FPSOverlay(game.app);
|
|
920
|
+
fps.show();
|
|
921
|
+
fps.toggle();
|
|
922
|
+
fps.hide();
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
When `debug: true` is set in `GameApplicationConfig`, the FPS overlay is enabled automatically.
|
|
926
|
+
|
|
927
|
+
The overlay displays:
|
|
928
|
+
- Average FPS
|
|
929
|
+
- Minimum FPS
|
|
930
|
+
- Frame time (ms)
|
|
931
|
+
|
|
932
|
+
Updated every ~500ms, sampled over 60 frames.
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
## API Reference
|
|
937
|
+
|
|
938
|
+
### GameApplication
|
|
939
|
+
|
|
940
|
+
```typescript
|
|
941
|
+
class GameApplication extends EventEmitter<GameEngineEvents> {
|
|
942
|
+
// Fields
|
|
943
|
+
app: Application;
|
|
944
|
+
scenes: SceneManager;
|
|
945
|
+
assets: AssetManager;
|
|
946
|
+
audio: AudioManager;
|
|
947
|
+
viewport: ViewportManager;
|
|
948
|
+
sdk: CasinoGameSDK | null;
|
|
949
|
+
initData: InitData | null;
|
|
950
|
+
readonly config: GameApplicationConfig;
|
|
951
|
+
|
|
952
|
+
// Getters
|
|
953
|
+
get gameConfig(): GameConfigData | null;
|
|
954
|
+
get session(): SessionData | null;
|
|
955
|
+
get balance(): number;
|
|
956
|
+
get currency(): string;
|
|
957
|
+
get isRunning(): boolean;
|
|
958
|
+
|
|
959
|
+
// Methods
|
|
960
|
+
constructor(config?: GameApplicationConfig);
|
|
961
|
+
async start(firstScene: string, sceneData?: unknown): Promise<void>;
|
|
962
|
+
destroy(): void;
|
|
963
|
+
}
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
### SceneManager
|
|
967
|
+
|
|
968
|
+
```typescript
|
|
969
|
+
class SceneManager extends EventEmitter<{ change: { from: string | null; to: string } }> {
|
|
970
|
+
get current(): SceneEntry | null;
|
|
971
|
+
get currentKey(): string | null;
|
|
972
|
+
get isTransitioning(): boolean;
|
|
973
|
+
|
|
974
|
+
setRoot(root: Container): void;
|
|
975
|
+
register(key: string, ctor: SceneConstructor): this;
|
|
976
|
+
async goto(key: string, data?: unknown, transition?: TransitionConfig): Promise<void>;
|
|
977
|
+
async push(key: string, data?: unknown, transition?: TransitionConfig): Promise<void>;
|
|
978
|
+
async pop(transition?: TransitionConfig): Promise<void>;
|
|
979
|
+
async replace(key: string, data?: unknown, transition?: TransitionConfig): Promise<void>;
|
|
980
|
+
update(dt: number): void;
|
|
981
|
+
resize(width: number, height: number): void;
|
|
982
|
+
destroy(): void;
|
|
983
|
+
}
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### AssetManager
|
|
987
|
+
|
|
988
|
+
```typescript
|
|
989
|
+
class AssetManager {
|
|
990
|
+
get initialized(): boolean;
|
|
991
|
+
get basePath(): string;
|
|
992
|
+
get loadedBundles(): ReadonlySet<string>;
|
|
993
|
+
|
|
994
|
+
constructor(basePath?: string, manifest?: AssetManifest);
|
|
995
|
+
async init(): Promise<void>;
|
|
996
|
+
async loadBundle(name: string, onProgress?: (p: number) => void): Promise<Record<string, unknown>>;
|
|
997
|
+
async loadBundles(names: string[], onProgress?: (p: number) => void): Promise<Record<string, unknown>>;
|
|
998
|
+
async load<T>(urls: string | string[], onProgress?: (p: number) => void): Promise<T>;
|
|
999
|
+
get<T>(alias: string): T;
|
|
1000
|
+
async unloadBundle(name: string): Promise<void>;
|
|
1001
|
+
async backgroundLoad(name: string): Promise<void>;
|
|
1002
|
+
getBundleNames(): string[];
|
|
1003
|
+
isBundleLoaded(name: string): boolean;
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
### AudioManager
|
|
1008
|
+
|
|
1009
|
+
```typescript
|
|
1010
|
+
class AudioManager {
|
|
1011
|
+
get initialized(): boolean;
|
|
1012
|
+
get muted(): boolean;
|
|
1013
|
+
|
|
1014
|
+
constructor(config?: AudioConfig);
|
|
1015
|
+
async init(): Promise<void>;
|
|
1016
|
+
play(alias: string, category?: AudioCategoryName, options?: { volume?: number; loop?: boolean; speed?: number }): void;
|
|
1017
|
+
playMusic(alias: string, fadeDuration?: number): void;
|
|
1018
|
+
stopMusic(): void;
|
|
1019
|
+
stopAll(): void;
|
|
1020
|
+
setVolume(category: AudioCategoryName, volume: number): void;
|
|
1021
|
+
getVolume(category: AudioCategoryName): number;
|
|
1022
|
+
muteCategory(category: AudioCategoryName): void;
|
|
1023
|
+
unmuteCategory(category: AudioCategoryName): void;
|
|
1024
|
+
toggleCategory(category: AudioCategoryName): boolean;
|
|
1025
|
+
muteAll(): void;
|
|
1026
|
+
unmuteAll(): void;
|
|
1027
|
+
toggleMute(): boolean;
|
|
1028
|
+
duckMusic(factor: number): void;
|
|
1029
|
+
unduckMusic(): void;
|
|
1030
|
+
destroy(): void;
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
### ViewportManager
|
|
1035
|
+
|
|
1036
|
+
```typescript
|
|
1037
|
+
class ViewportManager extends EventEmitter<ViewportEvents> {
|
|
1038
|
+
get width(): number;
|
|
1039
|
+
get height(): number;
|
|
1040
|
+
get scale(): number;
|
|
1041
|
+
get orientation(): Orientation;
|
|
1042
|
+
get designWidth(): number;
|
|
1043
|
+
get designHeight(): number;
|
|
1044
|
+
|
|
1045
|
+
constructor(app: Application, container: HTMLElement, config: ViewportConfig);
|
|
1046
|
+
refresh(): void;
|
|
1047
|
+
destroy(): void;
|
|
1048
|
+
}
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
### StateMachine
|
|
1052
|
+
|
|
1053
|
+
```typescript
|
|
1054
|
+
class StateMachine<TContext> extends EventEmitter<StateMachineEvents> {
|
|
1055
|
+
get current(): string | null;
|
|
1056
|
+
get isTransitioning(): boolean;
|
|
1057
|
+
get context(): TContext;
|
|
1058
|
+
|
|
1059
|
+
constructor(context: TContext);
|
|
1060
|
+
addState(name: string, config: { enter?, exit?, update? }): this;
|
|
1061
|
+
addGuard(from: string, to: string, guard: (ctx: TContext) => boolean): this;
|
|
1062
|
+
async start(initialState: string, data?: unknown): Promise<void>;
|
|
1063
|
+
async transition(to: string, data?: unknown): Promise<boolean>;
|
|
1064
|
+
update(dt: number): void;
|
|
1065
|
+
hasState(name: string): boolean;
|
|
1066
|
+
canTransition(to: string): boolean;
|
|
1067
|
+
async reset(): Promise<void>;
|
|
1068
|
+
async destroy(): Promise<void>;
|
|
1069
|
+
}
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
### Tween
|
|
1073
|
+
|
|
1074
|
+
```typescript
|
|
1075
|
+
class Tween {
|
|
1076
|
+
static get activeTweens(): number;
|
|
1077
|
+
|
|
1078
|
+
static to(target: any, props: Record<string, number>, duration: number, easing?: EasingFunction, onUpdate?: (p: number) => void): Promise<void>;
|
|
1079
|
+
static from(target: any, props: Record<string, number>, duration: number, easing?, onUpdate?): Promise<void>;
|
|
1080
|
+
static fromTo(target: any, fromProps: Record<string, number>, toProps: Record<string, number>, duration: number, easing?, onUpdate?): Promise<void>;
|
|
1081
|
+
static delay(ms: number): Promise<void>;
|
|
1082
|
+
static killTweensOf(target: any): void;
|
|
1083
|
+
static killAll(): void;
|
|
1084
|
+
}
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
### Timeline
|
|
1088
|
+
|
|
1089
|
+
```typescript
|
|
1090
|
+
class Timeline {
|
|
1091
|
+
get isPlaying(): boolean;
|
|
1092
|
+
|
|
1093
|
+
to(target: any, props: Record<string, number>, duration: number, easing?: EasingFunction): this;
|
|
1094
|
+
from(target: any, props: Record<string, number>, duration: number, easing?: EasingFunction): this;
|
|
1095
|
+
delay(ms: number): this;
|
|
1096
|
+
call(fn: () => void | Promise<void>): this;
|
|
1097
|
+
parallel(...fns: Array<() => Promise<void>>): this;
|
|
1098
|
+
async play(): Promise<void>;
|
|
1099
|
+
cancel(): void;
|
|
1100
|
+
clear(): this;
|
|
1101
|
+
}
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
### InputManager
|
|
1105
|
+
|
|
1106
|
+
```typescript
|
|
1107
|
+
class InputManager extends EventEmitter<InputEvents> {
|
|
1108
|
+
get locked(): boolean;
|
|
1109
|
+
|
|
1110
|
+
constructor(canvas: HTMLCanvasElement);
|
|
1111
|
+
lock(): void;
|
|
1112
|
+
unlock(): void;
|
|
1113
|
+
isKeyDown(key: string): boolean;
|
|
1114
|
+
destroy(): void;
|
|
1115
|
+
}
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
### EventEmitter
|
|
1119
|
+
|
|
1120
|
+
```typescript
|
|
1121
|
+
class EventEmitter<TEvents extends {}> {
|
|
1122
|
+
on<K>(event: K, handler: (data: TEvents[K]) => void): this;
|
|
1123
|
+
once<K>(event: K, handler: (data: TEvents[K]) => void): this;
|
|
1124
|
+
off<K>(event: K, handler: (data: TEvents[K]) => void): this;
|
|
1125
|
+
emit<K>(event: K, data: TEvents[K]): void;
|
|
1126
|
+
removeAllListeners(event?: keyof TEvents): this;
|
|
1127
|
+
}
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
---
|
|
1131
|
+
|
|
1132
|
+
## License
|
|
1133
|
+
|
|
1134
|
+
MIT
|