English | Chinese
Introduction
Eva.js is a front-end game engine specifically for creating interactive game projects.
Easy to Use: Eva.js provides out-of-box game components for developers to use right away. Yes, it's simple and elegant!
High-performance: Eva.js is powered by efficient runtime and rendering pipeline (Pixi.JS) which makes it possible to unleash the full potential of your device.
Scalability: Thanks to the ECS(Entity-Component-System) structure, you can expand your needs by highly customizable APIs. The only limitation is your imagination!
Documentation
You can find the Eva.js Documentation on eva.js.org, we appreciate your devotion by sending pull requests to this repository.
Checking out the Live example.
Packages
| Package | Description |
|---|---|
@eva/eva.js |
Core engine: Game, GameObject, Component, System, Resource |
@eva/plugin-renderer |
Core renderer (PixiJS) |
@eva/plugin-renderer-img |
Image rendering |
@eva/plugin-renderer-text |
Text rendering (Text, HTMLText, BitmapText) |
@eva/plugin-renderer-sprite |
Sprite sheet rendering |
@eva/plugin-renderer-sprite-animation |
Frame animation |
@eva/plugin-renderer-spine |
Spine skeleton animation |
@eva/plugin-renderer-dragonbone |
DragonBones skeleton animation |
@eva/plugin-renderer-lottie |
Lottie animation |
@eva/plugin-renderer-graphics |
Vector graphics drawing |
@eva/plugin-renderer-nine-patch |
Nine-slice scaling |
@eva/plugin-renderer-tiling-sprite |
Tiling sprite |
@eva/plugin-renderer-mask |
Mask / clipping |
@eva/plugin-renderer-mesh |
Perspective mesh deformation |
@eva/plugin-renderer-render |
Render properties (alpha, zIndex, visible) |
@eva/plugin-renderer-event |
Touch / pointer events |
@eva/plugin-sound |
Audio playback |
@eva/plugin-transition |
Tween animation |
@eva/plugin-a11y |
Accessibility |
@eva/plugin-evax |
Global state management |
@eva/plugin-matterjs |
Physics engine (Matter.js) |
@eva/plugin-layout |
Flexbox layout |
@eva/plugin-stats |
Performance monitor |
@eva/plugin-renderer-particle |
Particle emitter with zones, ranges and atlas frames |
@eva/plugin-renderer-filter |
PixiJS 2D filters (blur, colorMatrix, displacement, noise, alpha) |
@eva/plugin-renderer-render-texture |
Phaser-style dynamic render texture |
@eva/plugin-renderer-dom-element |
Pin an HTML element to a GameObject transform |
@eva/plugin-renderer-video |
HTML5 <video> rendering |
@eva/plugin-renderer-tilemap |
2D tilemap (Phaser v1 + Godot-style chunked v2) |
@eva/plugin-renderer-spine36 |
Spine 3.6 skeleton animation |
@eva/plugin-hitarea |
Trigger-style overlap detection (Godot Area2D equivalent, no physics) |
@eva/plugin-input-action |
Map raw input (key / mouse / touch) to semantic action signals |
@eva/plugin-camera2d |
2D camera with follow / deadzone / shake |
@eva/plugin-canvas-layer |
Named render layers with zIndex / screen-space flag |
@eva/plugin-parallax |
Camera-driven background parallax with optional tiling |
@eva/plugin-path-follow |
Move a GameObject along a waypoint path (once / loop / pingpong) |
@eva/plugin-tween |
Godot-style tween with sequence / parallel / yoyo |
@eva/plugin-animation-track |
Multi-track keyframe animation |
@eva/plugin-easing |
Shared easing functions (linear / quad / cubic / elastic / back / bounce) |
@eva/plugin-trigger |
Signal -> declarative actions, the stateless cousin of StateMachine |
@eva/plugin-state-machine |
Finite state machine driven by signals |
@eva/plugin-behavior-script |
Godot-style scriptable behaviors |
@eva/plugin-signal-bus |
Namespaced global event bus |
@eva/plugin-tick |
Reliable frame scheduler with RAF / wall fallback |
@eva/plugin-timer |
Godot-style countdown / interval timer |
@eva/plugin-ui |
UI kit: shapes + 14 @pixi/ui widgets + RadioGroup |
@eva/plugin-ai |
DOM AI overlay (semantic mirror of GameObjects) |
@eva/plugin-persistence |
localStorage <-> mx.store auto sync |
@eva/plugin-pool |
GameObject object pool with scene-scoped recycling |
@eva/plugin-worker |
Run Eva.js inside a Web Worker (OffscreenCanvas) |
@eva/spine-base |
Internal base for Spine renderers |
@eva/renderer-adapter |
Internal PixiJS display-object adapter layer |
Usage
Install
npm i @eva/eva.js @eva/plugin-renderer @eva/plugin-renderer-img --save
Quick Start
<canvas id="canvas"></canvas>
import { Game, GameObject, resource, RESOURCE_TYPE } from '@eva/eva.js'; import { RendererSystem } from '@eva/plugin-renderer'; import { Img, ImgSystem } from '@eva/plugin-renderer-img'; resource.addResource([ { name: 'imageName', type: RESOURCE_TYPE.IMAGE, src: { image: { type: 'png', url: 'https://gw.alicdn.com/tfs/TB1DNzoOvb2gK0jSZK9XXaEgFXa-658-1152.webp', }, }, preload: true, }, ]); const game = new Game(); await game.init({ systems: [ new RendererSystem({ canvas: document.querySelector('#canvas'), width: 750, height: 1000, }), new ImgSystem(), ], }); const image = new GameObject('image', { size: { width: 750, height: 1319 }, origin: { x: 0, y: 0 }, position: { x: 0, y: -319 }, anchor: { x: 0, y: 0 }, }); image.addComponent( new Img({ resource: 'imageName', }), ); game.scene.addChild(image);
API Reference
Core - @eva/eva.js
Game
Game engine entry. Manages systems, scenes, and the game loop.
import { Game } from '@eva/eva.js'; const game = new Game(); await game.init({ autoStart: true, // auto start the game loop (default: true) frameRate: 60, // target frame rate (default: 60) systems: [], // systems to register needScene: true, // auto create default scene (default: true) });
| Method | Description |
|---|---|
addSystem(system) |
Register a system |
removeSystem(system) |
Remove a system |
getSystem(SystemClass) |
Get registered system instance |
start() |
Start the game loop |
pause() |
Pause the game loop |
resume() |
Resume the game loop |
destroy() |
Destroy the game |
loadScene({ scene, mode?, params? }) |
Load a scene |
findByName(name) |
Find first GameObject by name |
findAllByName(name) |
Find all GameObjects by name |
| Property | Description |
|---|---|
scene |
Current main scene |
playing |
Whether the game is running |
ticker |
Ticker instance |
systems |
Registered systems array |
GameObject
Entity in the ECS architecture. Holds components and supports parent-child hierarchy.
import { GameObject } from '@eva/eva.js'; const go = new GameObject('name', { position: { x: 0, y: 0 }, size: { width: 100, height: 100 }, origin: { x: 0, y: 0 }, // transform origin anchor: { x: 0.5, y: 0.5 }, // anchor point scale: { x: 1, y: 1 }, rotation: 0, // radians skew: { x: 0, y: 0 }, });
| Method | Description |
|---|---|
addComponent(component) |
Add a component instance |
addComponent(ComponentClass, params) |
Add component by class + params |
removeComponent(component) |
Remove a component |
getComponent(ComponentClass) |
Get component by class |
addChild(gameObject) |
Add child GameObject |
removeChild(gameObject) |
Remove child GameObject |
remove() |
Remove self from parent |
destroy() |
Destroy self and all children |
| Property | Description |
|---|---|
transform |
Transform component |
parent |
Parent GameObject |
children |
Child GameObjects |
scene |
Scene this object belongs to |
Component
Base class for all components.
import { Component } from '@eva/eva.js'; class MyComponent extends Component { static componentName = 'MyComponent'; init(params) {} // called when added to GameObject awake() {} // called after init start() {} // called before first update update({ deltaTime, time, fps }) {} lateUpdate({ deltaTime }) {} onPause() {} onResume() {} onDestroy() {} }
System
Base class for all systems. Processes components each frame.
import { System } from '@eva/eva.js'; class MySystem extends System { static systemName = 'MySystem'; init(params) {} awake() {} start() {} update({ deltaTime, time, fps }) {} lateUpdate({ deltaTime }) {} onPause() {} onResume() {} onDestroy() {} }
resource
Global resource manager singleton.
import { resource, RESOURCE_TYPE, LOAD_EVENT } from '@eva/eva.js'; // Add resources resource.addResource([ { name: 'img', type: RESOURCE_TYPE.IMAGE, src: { image: { type: 'png', url: 'path/to/image.png' } }, preload: true, }, ]); // Preload all preload:true resources resource.preload(); // Listen to loading progress resource.on(LOAD_EVENT.PROGRESS, (progress) => {}); // 0-1 resource.on(LOAD_EVENT.COMPLETE, () => {}); resource.on(LOAD_EVENT.ERROR, (err) => {}); // Get resource (async) const res = await resource.getResource('img'); // Destroy resource resource.destroy('img');
RESOURCE_TYPE: IMAGE, SPRITE, SPRITE_ANIMATION, AUDIO, VIDEO, FONT
RendererSystem - @eva/plugin-renderer
Core rendering system powered by PixiJS. Required by all renderer plugins.
import { RendererSystem } from '@eva/plugin-renderer'; new RendererSystem({ canvas: document.querySelector('#canvas'), width: 750, height: 1000, preference: 'webgl', // 'webgl' | 'webgpu' | 'canvas' backgroundAlpha: 1, // 0=fully transparent, 1=opaque antialias: false, resolution: window.devicePixelRatio, backgroundColor: 0x000000, enableScroll: false, debugMode: false, });
| Method | Description |
|---|---|
resize(width, height) |
Resize the canvas |
Img - @eva/plugin-renderer-img
Render a single image.
import { Img, ImgSystem } from '@eva/plugin-renderer-img'; // Register system game.addSystem(new ImgSystem()); // Add component go.addComponent(new Img({ resource: 'imageName' }));
| Param | Type | Description |
|---|---|---|
resource |
string |
Resource name (IMAGE type) |
Sprite - @eva/plugin-renderer-sprite
Render a sub-image from a sprite sheet.
import { Sprite, SpriteSystem } from '@eva/plugin-renderer-sprite'; game.addSystem(new SpriteSystem()); go.addComponent(new Sprite({ resource: 'spriteName', spriteName: 'frame01.png', }));
| Param | Type | Description |
|---|---|---|
resource |
string |
Resource name (SPRITE type) |
spriteName |
string |
Sub-image name in the sprite sheet |
SpriteAnimation - @eva/plugin-renderer-sprite-animation
Play frame-by-frame animation from a sprite sheet.
import { SpriteAnimation, SpriteAnimationSystem } from '@eva/plugin-renderer-sprite-animation'; game.addSystem(new SpriteAnimationSystem()); const anim = go.addComponent(new SpriteAnimation({ resource: 'animResource', autoPlay: true, speed: 100, // ms per frame forwards: false, // stop at last frame when done })); anim.play(3); // play 3 times anim.gotoAndPlay(5); // jump to frame 5 and play anim.gotoAndStop(0); // jump to frame 0 and stop anim.stop();
| Param | Type | Default | Description |
|---|---|---|---|
resource |
string |
Resource name (SPRITE_ANIMATION type) | |
autoPlay |
boolean |
true |
Auto play on load |
speed |
number |
100 |
Milliseconds per frame |
forwards |
boolean |
false |
Freeze on last frame when complete |
| Property | Description |
|---|---|
currentFrame |
Current frame number |
totalFrames |
Total frame count |
| Event | Description |
|---|---|
complete |
All play iterations finished |
loop |
Each loop iteration |
frameChange |
Frame changed |
Text / HTMLText / BitmapText - @eva/plugin-renderer-text
Render text content with three rendering modes.
import { Text, HTMLText, BitmapText, TextSystem } from '@eva/plugin-renderer-text'; game.addSystem(new TextSystem()); // Canvas Text go.addComponent(new Text({ text: 'Hello World', style: { fontFamily: 'Arial', fontSize: 36, fill: 0xff1010, stroke: { color: 0xffffff, width: 5 }, fontWeight: 'bold', wordWrap: true, wordWrapWidth: 200, align: 'center', dropShadow: { alpha: 1, angle: Math.PI / 6, blur: 5, color: 0x000000, distance: 5, }, }, })); // HTML Rich Text (supports <b>, <i>, <span>, <br> tags) go.addComponent(new HTMLText({ text: '<b>Bold</b> and <i>italic</i>', style: { fontFamily: 'Arial', fontSize: 24, fill: 0x000000, wordWrap: true, wordWrapWidth: 300, }, })); // Bitmap Text (using bitmap font resource) go.addComponent(new BitmapText({ text: 'Score: 100', style: { fontFamily: 'myBitmapFont', fontSize: 32, }, }));
Graphics - @eva/plugin-renderer-graphics
Draw vector shapes using PixiJS Graphics API.
import { Graphics, GraphicsSystem } from '@eva/plugin-renderer-graphics'; game.addSystem(new GraphicsSystem()); const comp = go.addComponent(new Graphics()); // Use PixiJS Graphics API directly comp.graphics.rect(0, 0, 100, 100); comp.graphics.fill(0xff0000); comp.graphics.circle(50, 50, 30); comp.graphics.fill(0x00ff00);
NinePatch - @eva/plugin-renderer-nine-patch
Nine-slice scaling. Corners stay fixed while edges and center stretch.
import { NinePatch, NinePatchSystem } from '@eva/plugin-renderer-nine-patch'; game.addSystem(new NinePatchSystem()); go.addComponent(new NinePatch({ resource: 'panelImg', leftWidth: 20, topHeight: 20, rightWidth: 20, bottomHeight: 20, }));
| Param | Type | Description |
|---|---|---|
resource |
string |
Image or sprite resource name |
spriteName |
string |
Sub-image name (when using SPRITE resource) |
leftWidth |
number |
Left non-stretch width |
topHeight |
number |
Top non-stretch height |
rightWidth |
number |
Right non-stretch width |
bottomHeight |
number |
Bottom non-stretch height |
TilingSprite - @eva/plugin-renderer-tiling-sprite
Repeating tiled texture within a region.
import { TilingSprite, TilingSpriteSystem } from '@eva/plugin-renderer-tiling-sprite'; game.addSystem(new TilingSpriteSystem()); go.addComponent(new TilingSprite({ resource: 'bgTexture', tileScale: { x: 1, y: 1 }, tilePosition: { x: 0, y: 0 }, }));
| Param | Type | Default | Description |
|---|---|---|---|
resource |
string |
Image resource name | |
tileScale |
{x, y} |
{x:1, y:1} |
Tile scale |
tilePosition |
{x, y} |
{x:0, y:0} |
Tile offset |
Mask - @eva/plugin-renderer-mask
Clip the display area of a GameObject.
import { Mask, MaskSystem, MASK_TYPE } from '@eva/plugin-renderer-mask'; game.addSystem(new MaskSystem()); // Circle mask go.addComponent(new Mask({ type: MASK_TYPE.Circle, style: { x: 50, y: 50, radius: 50 }, })); // Rect mask go.addComponent(new Mask({ type: MASK_TYPE.Rect, style: { x: 0, y: 0, width: 200, height: 100 }, })); // Image mask (alpha-based) go.addComponent(new Mask({ type: MASK_TYPE.Img, resource: 'maskImg', style: { x: 0, y: 0, width: 200, height: 200 }, }));
MASK_TYPE: Circle, Ellipse, Rect, RoundedRect, Polygon, Img, Sprite
PerspectiveMesh - @eva/plugin-renderer-mesh
Perspective mesh deformation by adjusting four corner points.
import { PerspectiveMesh, MeshSystem } from '@eva/plugin-renderer-mesh'; game.addSystem(new MeshSystem()); const mesh = go.addComponent(new PerspectiveMesh({ resource: 'cardImg', verticesX: 10, // horizontal vertex count verticesY: 10, // vertical vertex count })); // Set four corners (x0,y0 x1,y1 x2,y2 x3,y3) // top-left, top-right, bottom-right, bottom-left mesh.setCorners(0, 0, 200, 20, 180, 300, 20, 280);
Render - @eva/plugin-renderer-render
Control render properties: visibility, transparency, z-order.
import { Render, RenderSystem } from '@eva/plugin-renderer-render'; game.addSystem(new RenderSystem()); go.addComponent(new Render({ alpha: 1, // opacity 0-1 visible: true, zIndex: 0, sortableChildren: false, resolution: 1, }));
Event - @eva/plugin-renderer-event
Add touch/pointer interaction to GameObjects.
import { Event, EventSystem, HIT_AREA_TYPE } from '@eva/plugin-renderer-event'; game.addSystem(new EventSystem()); const evt = go.addComponent(new Event({ hitArea: { type: HIT_AREA_TYPE.Rect, style: { x: 0, y: 0, width: 100, height: 100 }, }, })); evt.on('tap', (e) => { console.log('tapped!', e.data.position); }); evt.on('touchstart', (e) => { e.stopPropagation(); // stop bubbling });
Events: tap, touchstart, touchmove, touchend, touchendoutside, touchcancel
Event data:
data.position- global coordinates{x, y}data.localPosition- local coordinates{x, y}data.pointerId- pointer IDgameObject- target GameObjectstopPropagation()- stop event bubbling
HIT_AREA_TYPE: Circle, Ellipse, Polygon, Rect, RoundedRect
Spine - @eva/plugin-renderer-spine
Play Spine skeleton animations. Use @eva/plugin-renderer-spine36 for Spine 3.6 format.
import { Spine, SpineSystem } from '@eva/plugin-renderer-spine'; game.addSystem(new SpineSystem()); const spine = go.addComponent(new Spine({ resource: 'spineRes', animationName: 'idle', autoPlay: true, scale: 1, })); spine.play('walk', true); // play looping spine.stop(); spine.addAnimation('attack', 0, false); // queue animation spine.setMix('idle', 'walk', 0.2); // transition blend spine.setAttachment('weapon', 'sword'); // slot attachment (skin swap) spine.getBone('head'); // get bone // Mount a GameObject to a spine slot spine.addSlotObject('hand', weaponGameObject); spine.removeSlotObject(weaponGameObject);
| Event | Description |
|---|---|
complete |
Animation complete |
start |
Animation started |
end |
Animation ended |
event |
Spine event triggered |
interrupt |
Animation interrupted |
DragonBone - @eva/plugin-renderer-dragonbone
Play DragonBones skeleton animations. Compatible with PixiJS v8.
import { Game, GameObject, resource, RESOURCE_TYPE } from '@eva/eva.js'; import { RendererSystem } from '@eva/plugin-renderer'; import { DragonBone, DragonBoneSystem } from '@eva/plugin-renderer-dragonbone'; // DragonBones requires three asset files: skeleton json, atlas json, atlas image. resource.addResource([ { name: 'hero', type: RESOURCE_TYPE.DRAGONBONE, src: { image: { type: 'png', url: '/assets/hero/texture.png' }, tex: { type: 'json', url: '/assets/hero/texture.json' }, // atlas ske: { type: 'json', url: '/assets/hero/skeleton.json' }, // skeleton }, preload: true, }, ]); game.addSystem(new DragonBoneSystem()); const db = go.addComponent(new DragonBone({ resource: 'hero', armatureName: 'Hero', // required: armature name in the DragonBones project animationName: 'idle', autoPlay: true, })); db.play('run'); // play default loop count db.play('attack', 1); // play once db.play('idle', 0); // loop forever db.stop(); db.stop('walk');
| Param | Type | Default | Description |
|---|---|---|---|
resource |
string |
Resource name (DRAGONBONE type) | |
armatureName |
string |
Armature name; required, throws on missing | |
animationName |
string |
Initial animation to play | |
autoPlay |
boolean |
true |
Auto play animationName once armature is ready |
| Event | Description |
|---|---|
start |
Animation start / loop start |
loopComplete |
One loop finished |
complete |
All loops finished |
fadeIn / fadeInComplete |
Fade-in start / complete |
fadeOut / fadeOutComplete |
Fade-out start / complete |
frameEvent |
Custom keyframe event |
soundEvent |
Sound event |
The internal
lib/db.jsis the DragonBones PixiJS runtime. PixiJS v8 polyfills forsetTransform,BLEND_MODES,Ticker.shared, mesh slot stub andTextureconstructor are applied at the import layer. Mesh slot vertex deformation falls back to a flat sprite — image-based slots render fully.
Lottie - @eva/plugin-renderer-lottie
Play Lottie (After Effects) animations.
import { Lottie, LottieSystem } from '@eva/plugin-renderer-lottie'; game.addSystem(new LottieSystem()); const lottie = go.addComponent(new Lottie({ resource: 'lottieRes', autoStart: false, })); // Play frame range [0, 60], loop infinitely lottie.play([0, 60], { repeats: -1 }); // Play with slot replacement lottie.play([0, 100], { slot: [ { type: 'IMAGE', name: 'layerName', url: 'newImg.png' }, { type: 'TEXT', name: 'textLayer', value: 'New Text', style: { fontSize: 24 } }, ], }); // Tap interaction on named layer lottie.onTap('buttonLayer', () => console.log('clicked'));
| Event | Description |
|---|---|
complete |
Play complete |
loopComplete |
Loop iteration |
enterFrame |
Each frame |
Sound - @eva/plugin-sound
Audio playback based on Web Audio API.
import { Sound, SoundSystem } from '@eva/plugin-sound'; game.addSystem(new SoundSystem({ autoPauseAndStart: true, // sync with game pause/resume })); const sound = go.addComponent(new Sound({ resource: 'bgm', autoplay: false, loop: true, volume: 0.8, speed: 1, muted: false, onEnd: () => console.log('done'), })); sound.play(); sound.pause(); sound.resume(); sound.stop(); sound.volume = 0.5; sound.muted = true;
| Property | Description |
|---|---|
playing |
Whether sound is playing |
volume |
Volume (0-1) |
muted |
Mute state |
state |
'unloaded' / 'loading' / 'loaded' |
Transition - @eva/plugin-transition
Tween animation with keyframes and easing.
import { Transition, TransitionSystem } from '@eva/plugin-transition'; game.addSystem(new TransitionSystem()); const render = go.addComponent(new Render({ alpha: 1 })); const transition = go.addComponent(new Transition({ group: { fadeIn: [ { name: 'alpha', component: render, values: [ { time: 0, value: 0, tween: 'ease-in' }, { time: 1000, value: 1 }, ], }, ], moveRight: [ { name: 'position.x', component: go.transform, values: [ { time: 0, value: 0, tween: 'ease-out' }, { time: 500, value: 300 }, ], }, ], }, })); transition.play('fadeIn'); transition.play('moveRight', Infinity); // loop forever transition.stop('fadeIn'); transition.on('finish', (name) => console.log(`${name} finished`));
Easing functions: linear, ease-in, ease-out, ease-in-out, bounce-in, bounce-out, bounce-in-out
A11y - @eva/plugin-a11y
Accessibility support. Creates transparent DOM overlay with ARIA attributes over canvas.
import { A11y, A11ySystem, A11yActivate } from '@eva/plugin-a11y'; game.addSystem(new A11ySystem({ debug: false, activate: A11yActivate.CHECK, // CHECK | ENABLE | DISABLE delay: 100, })); go.addComponent(new A11y({ hint: 'Play button', role: 'button', 'aria-label': 'Start the game', }));
Physics - @eva/plugin-matterjs
2D physics powered by Matter.js.
import { Physics, PhysicsSystem, PhysicsType } from '@eva/plugin-matterjs'; game.addSystem(new PhysicsSystem({ resolution: 1, isTest: false, // debug render world: { gravity: { x: 0, y: 1 }, }, })); // Rectangle body go.addComponent(new Physics({ type: PhysicsType.RECTANGLE, bodyOptions: { isStatic: false, restitution: 0.8, density: 0.01, }, })); // Circle body go.addComponent(new Physics({ type: PhysicsType.CIRCLE, radius: 25, bodyOptions: { restitution: 1 }, })); // Polygon body go.addComponent(new Physics({ type: PhysicsType.POLYGON, sides: 6, radius: 30, }));
PhysicsType: RECTANGLE, CIRCLE, POLYGON
Layout - @eva/plugin-layout
Flexbox-like layout system.
import { Layout, LayoutChild, LayoutSystem } from '@eva/plugin-layout'; game.addSystem(new LayoutSystem()); // Container container.addComponent(new Layout({ direction: 'row', // 'row' | 'column' justifyContent: 'center', // 'start' | 'center' | 'end' | 'space-between' | 'space-around' alignItems: 'center', // 'start' | 'center' | 'end' | 'stretch' gap: 10, padding: [10, 20], // number | [v,h] | [top,right,bottom,left] autoSize: true, // auto-fit container size })); // Child item child.addComponent(new LayoutChild({ flexGrow: 1, flexShrink: 0, alignSelf: 'center', margin: 5, fixedSize: { width: 100 }, }));
Stats - @eva/plugin-stats
Performance monitoring panel displaying FPS and other metrics.
import { Stats, StatsSystem } from '@eva/plugin-stats'; game.addSystem(new StatsSystem({ show: true, style: { x: 0, y: 0, width: 200, height: 100 }, })); go.addComponent(new Stats());
ParticleEmitter - @eva/plugin-renderer-particle
GPU-batched particle emitter built on PixiJS ParticleContainer / Particle. Supports range-driven properties (number, {min,max}, {start,end,ease}, number[]), emit/death zones, atlas frame pools, gravity, acceleration and moveTo targeting.
import { ParticleEmitter, ParticleEmitterSystem } from '@eva/plugin-renderer-particle'; game.addSystem(new ParticleEmitterSystem()); const emitter = go.addComponent(new ParticleEmitter({ resource: 'sparkAtlas', frame: ['spark0.png', 'spark1.png'], // random per emit (atlas) or single string auto: true, frequency: 30, // ms between emits quantity: 4, // particles per emit maxParticles: 500, lifespan: { min: 600, max: 1200 }, speed: { min: 80, max: 200 }, angle: { min: 0, max: 360 }, // degrees scale: { start: 1, end: 0, ease: 'quad.out' }, alpha: { start: 1, end: 0 }, tint: [0xffcc33, 0xff5522], gravityY: 200, emitZone: { shape: { type: 'circle', radius: 40 } }, deathZone: { shape: { type: 'rect', x: -300, y: -300, width: 600, height: 600 }, mode: 'onLeave' }, })); emitter.stop(); // pause emission (existing particles finish their lifespan) emitter.start(); // resume
| Param | Type | Default | Description |
|---|---|---|---|
resource |
string |
Image or sprite atlas resource name | |
frame |
string | string[] |
Atlas frame(s); array samples randomly per emit | |
auto |
boolean |
true |
Auto-start on add |
explode |
number |
One-shot burst of N particles (overrides frequency) |
|
duration |
number |
-1 |
Emit duration in ms; -1 = infinite |
stopAfter |
number |
Stop after N total particles emitted | |
frequency |
number |
250 |
ms between emits |
quantity |
number |
1 |
Particles per emit |
maxParticles |
number |
500 |
Hard cap of live particles |
lifespan |
RangeValue |
1000 |
Particle lifespan (ms) |
speed / speedX / speedY / angle |
RangeValue |
Initial velocity; angle in degrees |
|
rotate |
RangeValue |
Per-particle rotation speed | |
scale / scaleX / scaleY / alpha |
RangeValue |
Tween-capable via {start,end,ease} |
|
tint |
number | number[] |
Hex color, or random sample from array | |
gravityX / gravityY |
number |
0 |
Constant world gravity |
accelerationX / accelerationY |
RangeValue |
Per-particle acceleration | |
moveTo |
{x, y} |
Aim each particle at this point (overrides angle) |
|
emitZone |
EmitZoneSpec |
Spawn area: point / rect / circle / ellipse / line, type:'random'|'edge' |
|
deathZone |
DeathZoneSpec |
Kill area with mode:'onEnter'|'onLeave' |
|
onEmit / onUpdate |
string |
Named hooks resolved by host (DSL stores event keys) |
RangeValue forms: number, {min, max} (uniform), {start, end, ease?} (per-particle interpolation, see easing list), or number[] (random pick).
Easing: linear, sine.in/out/inout, quad.in/out/inout, cubic.in/out, expo.out.
Filter - @eva/plugin-renderer-filter
Apply PixiJS built-in 2D filters to a GameObject. Each entry in filters is instantiated by FilterSystem and bound to the underlying display container's filters array.
import { Filter, FilterSystem } from '@eva/plugin-renderer-filter'; game.addSystem(new FilterSystem()); sprite.addComponent(new Filter({ filters: [ { type: 'blur', strength: 8, quality: 4 }, { type: 'colorMatrix', preset: 'sepia' }, { type: 'displacement', resource: 'noiseMap', scaleX: 20, scaleY: 20 }, { type: 'noise', noise: 0.3, seed: 0.1 }, { type: 'alpha', alpha: 0.7 }, ], filterArea: { x: 0, y: 0, width: 750, height: 1000 }, }));
| FilterSpec field | Type | Used by | Description |
|---|---|---|---|
type |
'blur' | 'colorMatrix' | 'displacement' | 'noise' | 'alpha' |
all | Filter kind |
enabled |
boolean |
all | Per-filter toggle |
strength / quality / blurX / blurY |
number |
blur | Pixi BlurFilter knobs |
preset / presetArg / matrix |
ColorMatrixPreset / number / number[] |
colorMatrix | Built-in preset (e.g. sepia, grayscale, negative, polaroid, vintage, hue, saturate, brightness, contrast) or raw 4x5 matrix |
resource / scaleX / scaleY |
string / number |
displacement | Displacement map image resource + scale |
noise / seed / noiseAnimSpeed |
number |
noise | Noise filter params |
alpha |
number |
alpha | 0..1 opacity multiplier |
ColorMatrixPreset: sepia, grayscale, negative, polaroid, vintage, lsd, predator, kodachrome, browni, technicolor, blackAndWhite, tint, saturate, brightness, contrast, hue, night
RenderTexture - @eva/plugin-renderer-render-texture
Phaser-style dynamic render texture. Holds an offscreen Pixi RenderTexture and replays a declarative queue of draw ops (fill, draw, drawFrame, drawText, erase, paint, clear). Optionally registers the final texture as a resource via saveAs so other Img / Sprite components can reference it.
import { RenderTexture, RenderTextureSystem } from '@eva/plugin-renderer-render-texture'; game.addSystem(new RenderTextureSystem()); const rt = go.addComponent(new RenderTexture({ width: 512, height: 512, backgroundColor: 0x000000, backgroundAlpha: 0, append: true, // accumulate ops (Phaser-like); false = clear each commit saveAs: 'paintCanvas', ops: [ { type: 'fill', color: 0x222222, alpha: 1 }, { type: 'draw', resource: 'bg', x: 0, y: 0, width: 512, height: 512 }, { type: 'drawText', text: 'HELLO', x: 100, y: 60, style: { fontSize: 48, fill: 0xffffff } }, ], })); // Mutate the queue at runtime — system detects via `dirty` counter rt.addOp({ type: 'paint', resource: 'brush', x: 200, y: 200, step: { x: 4, y: 0 }, times: 8 }); rt.clearOps();
| Param | Type | Default | Description |
|---|---|---|---|
width |
number |
256 |
Logical RT width |
height |
number |
256 |
Logical RT height |
ops |
RenderTextureOp[] |
[] |
Declarative draw queue, replayed on each commit |
backgroundColor |
number |
-1 |
Clear color (when append=false); -1 = transparent |
backgroundAlpha |
number |
1 |
Background alpha |
append |
boolean |
true |
Append ops without clearing; false clears each commit |
saveAs |
string |
Register final RT as a resource key for Img / Sprite |
Op types: fill (solid rect), clear (transparent), draw (image / atlas frame at x,y with tint/anchor/scale/rotation/blendMode), drawFrame (atlas-frame shorthand), drawText (PIXI.Text), erase (destination-out rect), paint (repeat a sprite N times with offset for paint trails).
DOMElement - @eva/plugin-renderer-dom-element
Pin a real HTML element onto a GameObject. The element lives in a .eva-dom-layer overlay aligned to the PixiJS canvas, while transform.position / scale / rotation are synced every frame to CSS transform. Useful for <input>, <video>, rich HTML buttons, or any DOM-only feature on top of Canvas.
import { DOMElement, DOMElementSystem } from '@eva/plugin-renderer-dom-element'; game.addSystem(new DOMElementSystem()); // Option A: raw HTML string (first root node is used) go.addComponent(new DOMElement({ html: '<input type="text" placeholder="Name" />', width: 240, height: 48, anchorX: 0.5, anchorY: 0.5, style: { background: '#fff', borderRadius: '8px', padding: '0 12px' }, attrs: { maxlength: '12' }, })); // Option B: tag + style go.addComponent(new DOMElement({ element: 'div', className: 'overlay-card', cssText: 'background: rgba(0,0,0,0.6); color: #fff;', width: 320, height: 120, pointerEvents: 'auto', blendMode: 'multiply', }));
| Param | Type | Default | Description |
|---|---|---|---|
html |
string |
Raw HTML; first root node becomes the element | |
element |
string |
'div' |
Tag name when html is empty |
className |
string |
className applied to the element | |
cssText |
string |
Inline style.cssText |
|
style |
Record<string,string> |
Individual style props | |
attrs |
Record<string,string> |
setAttribute map |
|
width / height |
number |
Element pixel size | |
anchorX / anchorY |
number |
0.5 |
Origin (0..1), Phaser-style |
blendMode |
string |
CSS mix-blend-mode |
|
pointerEvents |
string |
'auto' |
CSS pointer-events (layer itself is none) |
The DOM layer is
pointer-events: none; individual elements default toautoso inputs and buttons stay interactive.Game.pause()halts the layer-sync RAF loop to avoid forced layouts.
Video - @eva/plugin-renderer-video
Phaser-style video component (MVP). Wires a detached <video> DOM element into a PixiJS Sprite so videos render inside the ECS pipeline. Browser autoplay restrictions still apply — keep muted: true if you need playback without a user gesture.
Not supported: HLS/DASH/MSE streaming, getUserMedia, alpha / chroma-key video, shader-bound video textures.
