@dreams-engine/engine
v1.0.2
Published
A lightweight set of PixiJS v8 application extensions and utilities that add:
Readme
@dreams-engine/engine
A lightweight set of PixiJS v8 application extensions and utilities that add:
- Core application helpers (events bus, registry map, lifecycle controls)
- Localization (i18next integration with live text binding)
- Styled logger for structured, readable console output
- A static application registry to manage multiple
PIXI.Applicationinstances
Works as class-based PixiJS extensions; add them once and they augment every Application you create.
Installation
npm i @dreams-engine/engineQuick Start
import { Application, extensions } from 'pixi.js';
import { CorePlugin, LocalizationPlugin, Logger, LocalizationOptions } from '@dreams-engine/engine';
// 1) Register PixiJS extensions once in your app's entry
extensions.add(CorePlugin, LocalizationPlugin);
async function main() {
const app = new Application();
await app.init({
background: '#20242a',
resizeTo: window,
// Enable engine debug features
debug: { pixiExtension: true, log: true },
// Localization configuration
localization: {
lng: 'en', // initial language
fallbackLng: 'en',
langPath: 'locales', // where your JSON files are served from
debug: true // warns on missing keys
} as LocalizationOptions,
});
document.body.appendChild(app.canvas);
// Initialize localization service once
await app.lang.init();
// Try the logger
Logger.info('Engine initialized');
}
main();Features & Examples
CorePlugin
Adds helpers to every PIXI.Application:
- events: An
EventEmitterfor app-level events. - registry: A
Map<string, any>for simple data sharing. - pause/resume: Control the ticker.
- show/hide: Toggle canvas visibility.
- debug options:
debug.logtoggles the engineLogger,debug.pixiExtensionexposes the current app atglobalThis.__PIXI_APP__.
Example: events and registry
import { Application, extensions } from 'pixi.js';
import { CorePlugin, Logger } from '@dreams-engine/engine';
extensions.add(CorePlugin);
const app = new Application();
await app.init({ debug: { pixiExtension: true, log: true } });
// events
app.events.on('game:start', ({ level }) => Logger.log('Starting level', level));
app.events.emit('game:start', { level: 1 });
// registry
app.registry.set('playerName', 'Alice');
Logger.info('Player:', app.registry.get('playerName'));
// lifecycle
app.pause();
app.resume();
app.hide();
app.show();LocalizationPlugin + LocalizationService
Provides app.lang with i18next under the hood. Loads JSON resources via PIXI.Assets and supports live text binding.
Files structure (example):
public/
locales/
en.json
tr.jsonExample: initialize and translate
import { Application, Text, extensions } from 'pixi.js';
import { LocalizationPlugin, LocalizationOptions } from '@dreams-engine/engine';
extensions.add(LocalizationPlugin);
const app = new Application();
await app.init({
localization: { lng: 'en', fallbackLng: 'en', langPath: 'locales', debug: true } as LocalizationOptions
});
await app.lang.init();
const label = new Text({ text: app.lang.t('welcome_message'), style: { fill: 0xffffff, fontSize: 32 } });
app.stage.addChild(label);Example: bind live text updates and change language
const scoreText = new Text({ text: '', style: { fill: 0xffff00, fontSize: 20 } });
app.stage.addChild(scoreText);
app.lang.bind(scoreText, 'score_label', { count: 0 }); // binds to key
// Later: update language at runtime
await app.lang.changeLanguage('tr');
// Add or remove bundles dynamically
app.lang.addBundle('en', 'translation', { dynamic_key: 'Hello Dynamic' }, true, true);
app.lang.removeBundle('en', 'translation');Notes:
- Set
localization.langPathto the folder where your language files are served from. - If
localization.debugis true, missing keys log a warning in the console.
Logger
A small, styled console logger. Enabled/disabled by CorePlugin via ApplicationOptions.debug.log.
Methods: log, info, warn, error, debug, trace, assert, table, dir.
Example:
import { Logger } from '@dreams-engine/engine';
Logger.log('Hello');
Logger.info('Loaded scene:', { id: 1 });
Logger.warn('Missing config, using defaults');
Logger.error('Failed to load asset');
Logger.debug('State:', { x: 10, y: 42 });
Logger.trace('Step');
Logger.assert(2 + 2 === 4, 'Math holds');
Logger.table([{ id: 1 }, { id: 2 }]);
Logger.dir({ deep: { nested: true } });ApplicationRegistry
Manage multiple PIXI.Application instances from one place. The first app you register becomes the "master" app.
Example:
import { Application } from 'pixi.js';
import { ApplicationRegistry } from '@dreams-engine/engine';
const main = new Application();
await main.init({ background: '#000' });
ApplicationRegistry.addApp('main', main);
await (async () => {
const overlay = new Application();
await overlay.init({ background: 'transparent' });
ApplicationRegistry.addApp('overlay', overlay);
})();
// Access anywhere
const app = ApplicationRegistry.getApp('main');
app.stage.removeChildren();
// Destroy when done
ApplicationRegistry.destroyApp('overlay');
ApplicationRegistry.destroyAllApps();
#### Master Application
- The first `addApp(id, app)` call marks that app as the master.
- Retrieve it anywhere via `ApplicationRegistry.getMaster()`.
- If the master app is destroyed (`destroyApp(masterId)` or `destroyAllApps()`), the master reference is cleared and `getMaster()` throws until a new app is added.
Example: use master as a shared event hub
```ts
import { ApplicationRegistry } from '@dreams-engine/engine';
// Overlay app finishes a Big Win animation
const master = ApplicationRegistry.getMaster();
master.events.emit('bigwin:finished', { amount: 1234 });
// Elsewhere (e.g., state machine bridge)
master.events.on('bigwin:finished', ({ amount }) => {
// advance flow, show summary, etc.
});
## Types & Augmentations
The package augments PixiJS types so you get proper IntelliSense and type safety:
- `ApplicationOptions.localization?: LocalizationOptions`
- `ApplicationOptions.debug?: boolean | { pixiExtension?: boolean; log?: boolean }`
- `ApplicationOptions.animations?: Array<{ type: 'sprite'|'animatedSprite'|'spine'|'text'|'bitmapText'|'tween'; name: string; def: any }>| string`
- `Application.events: EventEmitter`
- `Application.registry: Map<string, any>`
- `Application.pause/resume/show/hide()`
- `Application.lang: LocalizationService`
- `Application.animations: AnimationPlugin`
See `src/global.d.ts` for the exact declarations.
### GameObjectFactory
Create Pixi display objects with typed options, build scenes from JSON, and load prefabs via `PIXI.Assets`.
- Methods: `container`, `sprite`, `text`, `graphics`, `animatedSprite`, `sprite-nine-slice` (`nineSliceSprite`), `sprite-tiling` (`tilingSprite`), `text-bitmap` (`bitmapText`), `particle-container` (`particleContainer`), `mesh`, `spine`.
- Extras: i18n binding for `text`/`bitmapText` via `keyword`/`keywordOpts` when `LocalizationPlugin` is active.
- JSON builder: `fromJson(defs, parent?)` with `type`, optional `label`, `ignore`, `children`.
- Prefabs: `prefab(id, opts?, parent?, ignore?)` loads JSON and instantiates.
Register and basic usage
```ts
import { extensions } from 'pixi.js';
import { GameObjectFactory } from '@dreams-engine/engine';
extensions.add(GameObjectFactory);
const container = app.create.container({ x: 100, y: 50 }, app.stage);
const logo = app.create.sprite({ texture: 'logo', anchor: 0.5, x: 200, y: 120 }, container);
// i18n-bound text (auto-updates on language changes)
const title = app.create.text({ keyword: 'welcome_message', style: { fontSize: 36, fill: 0xffffff } }, container);Build from JSON
const defs = [
{ type: 'container', label: 'root', x: 0, y: 0, children: [
{ type: 'sprite', texture: 'logo', anchor: 0.5, x: 320, y: 120 },
{ type: 'text', keyword: 'current_language', style: { fontSize: 20, fill: 0xffff00 }, x: 320, y: 200 },
]}
];
const { rootNodes, sceneMap } = app.create.fromJson(defs, app.stage);Prefabs
// Serve JSON under your public directory (e.g., /prefabs/ui.json)
const { rootNode, sceneMap } = await app.create.prefab('/prefabs/ui.json', { x: 0, y: 0 }, app.stage);Notes
- Texture fields accept alias/URL/Texture. The factory normalizes these via a resolver so sprites always receive a valid
Texture. - Property application uses
PIXI.utils.assignWithIgnoreand skips meta fields (children,type,label,keyword,keywordOpts, etc.). - For
spine, preload the skeleton or add it toPIXI.Assetsand reference withspineAssetId.
Audio Plugin
Layered audio control built on top of @pixi/sound and exposed as app.audio.
- Layers: logical buckets like
bgm,sfx,ui. Add custom layers as needed. - Master: global volume (
sound.volumeAll) and global mute/unmute helpers. - Layer control: per-layer volume/mute, stop/pause/resume all instances.
Register and configure
import { extensions } from 'pixi.js';
import { AudioPlugin } from '@dreams-engine/engine';
extensions.add(AudioPlugin);
await app.init({
audio: { layers: ['bgm','sfx'], masterVolume: 0.8 }
});Load sounds and play
import { sound } from '@pixi/sound';
// Add sounds (alias → URL)
sound.add('bgm', '/audio/bgm.mp3');
sound.add('click', '/audio/click.mp3');
// Start looping BGM on its layer
app.audio.bgm.play('bgm', { loop: true, volume: 0.3, singleInstance: true });
// Fire SFX when needed
app.audio.sfx.play('click', { volume: 0.9 });
// Master and layers
app.audio.setMasterVolume(0.7);
app.audio.muteAll();
app.audio.unmuteAll();
app.audio.getLayer('ui').setVolume(0.5);Manifest-based loading (optional)
// public/manifest.json
{
"bundles": [
{
"name": "audio",
"assets": [
{ "alias": "bgm", "src": "/audio/bgm.mp3" },
{ "alias": "click", "src": "/audio/click.mp3" }
]
}
]
}Assets.init({ manifest: '/manifest.json' });
await Assets.loadBundle('audio');
app.audio.sfx.play('click');Animation Plugin (Registry)
Type-based animation registry exposed as app.animations. Lets you register reusable animations (sprite/animatedSprite/spine/text/bitmapText/tween) and apply them to any target.
Register
import { extensions } from 'pixi.js';
import { AnimationPlugin } from '@dreams-engine/engine';
extensions.add(AnimationPlugin);Init-time bulk (optional)
await app.init({
animations: [
{ type: 'tween', name: 'pulse', def: { gsap: { scale: 1.2, yoyo: true, repeat: 3, duration: 0.2 } } },
{ type: 'animatedSprite', name: 'idle', def: { generator: { prefix: 'f_', startIndex: 1, stopIndex: 4, pad: 2, suffix: '.png' }, fps: 12, loop: true } },
]
});
// Or from a bundle id (Assets.load) that returns either an array or a typed map
await app.animations.ready(); // wait for any in-flight bulk loadManual registration
// sprite: texture swap + optional gsap tween + restore
app.animations.set('sprite', 'swap', { toTexture: 'atlas::icon_on', durationMs: 150, restore: true });
// animatedSprite: frames via generator or explicit list + fps
app.animations.set('animatedSprite', 'spin', { generator: { prefix: 'reel_', startIndex: 1, stopIndex: 10, pad: 2, suffix: '.png' }, fps: 24, loop: true });
// spine: animation name + speed + track
app.animations.set('spine', 'win', { anim: 'bigwin', loop: false, speed: 1 });
// text/bitmapText: content swap + duration/gsap
app.animations.set('text', 'flash', { text: 'READY!', durationMs: 300 });
// tween: generic gsap tween reusable on any DisplayObject
app.animations.set('tween', 'float', { gsap: { y: '-=10', yoyo: true, repeat: -1, duration: 0.8 } });Applying animations
// sprite
const ctrl1 = app.animations.apply('sprite', 'swap', mySprite);
await ctrl1.finished; // resolves on completion or stop()
ctrl1.stop();
// animatedSprite
const ctrl2 = app.animations.apply('animatedSprite', 'spin', myAnimatedSprite);
// later
ctrl2.stop();
// spine
app.animations.apply('spine', 'win', mySpine);
// text
app.animations.apply('text', 'flash', myText);
// generic tween (works on any DisplayObject)
app.animations.apply('tween', 'float', myContainer);Notes
fpsmaps to PixianimationSpeed ≈ fps/60.generatorbuilds frame names using{ prefix, suffix, startIndex, stopIndex, pad }.sprite.restore=truerestores the previous texture on completion/stop.load(defsOrBundleId)andready()help with async bulk registration.- If GSAP isn't present, sprite/text can fallback to
durationMstimeout; other types no-op the tween.
Navigation Plugin
High-level screen navigation system inspired by Pixi Create.
- Screens: simple classes extending
PIXI.Container(or any Container) implementing optional hooks:prepare,show,hide,pause,resume,resize,update,blur,focus,onLoad(progress). - Asset preload: declare static
assetBundles: string[]on your screen ctor to preload viaPIXI.Assetsbefore showing. - Background + current + popup: supports a persistent background screen and a single popup over the current screen.
Register
import { extensions } from 'pixi.js';
import { NavigationPlugin } from '@dreams-engine/engine';
extensions.add(NavigationPlugin);Define a screen
import { Container } from 'pixi.js';
class MenuScreen extends Container {
// Optional: preload bundles before first creation
static assetBundles = ['ui'];
// Optional: one-time async creation for heavy prefab setup
async create() {
// E.g., await GameObjectFactory.prefab('menu-root', this);
}
prepare() {/* layout elements */}
async show() {/* run intro tween */}
async hide() {/* run outro tween */}
resize(w:number,h:number) {/* reposition */}
}Use it
// set a persistent background (optional)
await app.navigation.setBackground(MenuScreen);
// show a screen
await app.navigation.showScreen(MenuScreen);
// present/dismiss a popup
await app.navigation.presentPopup(ModalScreen);
await app.navigation.dismissPopup();
// resize propagation is wired to ResizePlugin automaticallyLoad screen from JSON
// From JSON file path
const { screen, sceneMap } = await app.navigation.showScreenFromJson('scenes/main-menu.json');
// Access elements via sceneMap
const title = sceneMap.get('menuContainer/title');
title.text = 'Welcome!';
// Or pass JSON directly
const { screen, sceneMap } = await app.navigation.showScreenFromJson({
assetBundles: ['ui', 'backgrounds'],
metadata: { name: 'MainMenu', version: '1.0' },
elements: [
{
type: 'container',
label: 'menuContainer',
children: [
{ type: 'sprite', label: 'background', src: 'bg.png', width: 1920, height: 1080 },
{ type: 'text', label: 'title', text: 'Main Menu', x: 960, y: 100, style: { fontSize: 48, fill: 0xffffff } }
]
}
]
});JSON Scene Schema
{
"assetBundles": ["ui-bundle", "game-assets"], // Optional: preload bundles before showing
"metadata": { // Optional: scene metadata
"name": "MainMenu",
"version": "1.0"
},
"elements": [ // Required: array of visual definitions
{
"type": "container",
"label": "root",
"x": 0,
"y": 0,
"children": [
{
"type": "sprite",
"label": "bg",
"src": "background.png",
"width": 1920,
"height": 1080
},
{
"type": "text",
"label": "title",
"text": "Welcome",
"x": 960,
"y": 540,
"style": { "fontSize": 64, "fill": 0xffffff }
}
]
}
]
}Notes
app.navigationis created by the plugin and listens toapp.scale.events('resize')to keep screens responsive.- Use
app.ticker.add(screen.update, screen)pattern by definingupdate()on the screen; Navigation wires it automatically. create()runs once per instance after its bundles are loaded and beforeprepare()/show(). Useful for async prefab creation.
Lifecycle
- Show:
assets (optional)→create (once)→prepare→resize→ticker.add(update, screen)→show→interactiveChildren = true. - Hide/Remove:
interactiveChildren = false→hide→ticker.remove(update, screen)→removeChild→reset. - Reuse: Screens retrieved from
BigPoolare reused;create()will not run again for the same instance. To force re-create in your screen, clear the flag inreset():delete (this as any)[Symbol.for('dc.screen.created')]
Focus & Blur
- The plugin automatically maps browser focus signals to your screens:
- Blur on:
window.blur,document.visibilitychange(hidden),pagehide - Focus on:
window.focus,document.visibilitychange(visible),pageshow
- Blur on:
- Navigation propagates
blur()/focus()tobackground,currentScreen, andcurrentPopupif those hooks exist. - Typical usage: pause/slow animations and mute/duck audio on blur; resume/unmute on focus.
- SSR/tests safe: Listeners are only attached when
windowanddocumentare available.
Responsive Manager (Scene‑Scoped)
Declarative, JSON‑driven responsive layout per scene. Keeps your screen code free of if/else orientation logic by applying overrides on resize.
- Scope: attach one
ResponsiveManagerper scene/screen (lifecycle friendly). - Sources: reads current orientation and game/safe sizes from the Resize plugin (
app.scale.metrics). - Rules: orientation overrides, simple breakpoints, and relative placement keywords/percentages.
Add to a scene
import { ResponsiveManager } from '@dreams-engine/engine';
export class MenuScreen extends Container {
private responsive!: ResponsiveManager;
prepare() {
// Pass optional root so manager auto-clears when this container is destroyed
this.responsive = new ResponsiveManager(this.game, this);
// Optional: use safe area from your scale config
const m = (this.game as any).scale?.metrics;
if (m) this.responsive.setSafeArea(m.gameWidth, m.gameHeight);
}
destroy() {
this.responsive?.destroy();
super.destroy({ children: true });
}
}Use with prefab/factory (automatic registration)
// defs comes from JSON/prefab. When a node has a `responsive` block,
// the factory will call responsive.add(node, rules) for you.
const rm = new ResponsiveManager(app);
const { sceneMap } = app.create.fromJson(defs, app.stage, { responsive: rm });
rm.applyAll(); // initial layoutWith prefab
const rm = new ResponsiveManager(app);
// options.responsive passes the manager through to fromJson internally
const { rootNode, sceneMap } = await app.create.prefab('/prefabs/ui.json', { x: 0, y: 0 }, app.stage, undefined, { responsive: rm });
rm.applyAll();JSON example
{
"label": "playBtn",
"type": "sprite",
"texture": "btn",
"anchor": 0.5,
"responsive": {
"landscape": { "x": "50%", "y": 900 },
"portrait": { "x": "50%", "y": "80%", "scale": 1.1 },
"breakpoints": {
"w<=800": { "scale": 0.85 },
"ar<=1.6": { "y": 950 }
},
"relative": {
"x": "center", // left | center | right | percentage "50%"
"y": "bottom" // top | middle | bottom | percentage
}
}
}Rules and precedence
- Base props (from node) < orientation override (landscape/portrait) < matching breakpoints < runtime code.
- Relative keywords use game and safe sizes from
app.scale.metrics. - Supported props:
x,y,scale,scaleX,scaleY,anchor,width,height.
Resize/Scale Plugin
Responsive scaling and alignment for your Application, inspired by Phaser 3’s Scale Manager.
- Scale modes:
FIT,ENVELOP,RESIZE,EXPAND,WIDTH_CONTROLS_HEIGHT,HEIGHT_CONTROLS_WIDTH,NONE. - Per-orientation configs: set different base sizes, safe areas, and alignment for landscape/portrait.
- Events:
resize(metrics),orientation:change,fullscreen:change,fullscreen:error. - Fullscreen helpers:
startFullscreen(),stopFullscreen(),toggleFullscreen().
Register and configure
import { extensions } from 'pixi.js';
import { ResizePlugin, type ResizePluginOptions } from '@dreams-engine/engine';
extensions.add(ResizePlugin);
const resizeOpts: ResizePluginOptions = {
landscape: { width: 1280, height: 720, scaleMode: 'FIT', align: 0.5, safeWidth: 1024, safeHeight: 576 },
portrait: { width: 720, height: 1280, scaleMode: 'FIT', align: 0.5, safeWidth: 576, safeHeight: 1024 },
debounceDelay: 200,
};
const app = new Application();
await app.init({ resizeTo: window, resizeOpts });
// Access manager at runtime
const m = app.scale.metrics; // { orientation, displayWidth, displayHeight, scaleX, ... }Listen to events
app.scale.events.on('resize', (metrics) => {
console.log('Resized:', metrics.displayWidth, metrics.displayHeight);
});
app.scale.events.on('orientation:change', (o) => console.log('Orientation:', o));Toggle fullscreen
await app.scale.toggleFullscreen();Change scale mode at runtime
app.scale.setScaleMode('ENVELOP'); // for current orientation
app.scale.setScaleMode('FIT', 'landscape'); // override only landscape configNotes
- Renderer stays at the base logical size for consistent interaction; the canvas scales via CSS.
- Make sure your page/layout gives the canvas parent a real size (100vw/100vh etc.).
- Alignment accepts
0..1values:{ x: 0.5, y: 0.5 }centers the canvas.
Build
Inside packages/engine:
pnpm run build # builds library with Vite (ESM + UMD)
pnpm run dev # rebuilds on change
pnpm run clean # removes distBuild config: vite.config.ts outputs slot-engine.mjs and slot-engine.umd.js with externals for Pixi, i18next, gsap.
Compatibility
- PixiJS v8 (class-based extensions)
- TypeScript support out of the box (types emitted to
dist/)
