dorkhub

eva.js

Eva.js is a front-end game engine specifically for creating interactive game projects.

eva-engine
JavaScript1.8k107 forksMITupdated 3 weeks ago
visit the demogit clone https://github.com/eva-engine/eva.js.giteva-engine/eva.js

Eva.js (Interactive Game Engine)

Eva.js logo

npm-version npm-size npm-download

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 ID
  • gameObject - target GameObject
  • stopPropagation() - 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.js is the DragonBones PixiJS runtime. PixiJS v8 polyfills for setTransform, BLEND_MODES, Ticker.shared, mesh slot stub and Texture constructor 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 to auto so 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.

import { Video, VideoSystem } from '@eva/plugin-renderer-video';

game.addSystem(new VideoSystem());

const video = go.addComponent(new Video({
  src: 'https://cdn.example.com/intro.mp4',  // raw URL or registered resource key
  loop: false,
  autoplay: true,
  muted: true,        // required for autoplay without user gesture
  volume: 1,
  playbackRate: 1,
  width: 640,
  height: 360,
  anchorX: 0.5,
  anchorY: 0.5,
  playsInline: true,  // iOS inline playback
  onComplete: 'video:ended',
}));

video.play();
video.pause();
video.setCurrentTime(3.5);             // seek (seconds)
const canvas = video.snapshot();        // current frame -> HTMLCanvasElement (or null)
video.videoElement.muted = false;       // raw <video> access if needed
Param Type Default Description
src string '' Video URL, or a registered resource key
loop boolean false Loop playback
autoplay boolean true Auto-start after loadeddata
muted boolean true Required by browsers for unattended autoplay
volume number 1 0~1
playbackRate number 1 Playback speed
width / height number video intrinsic Display size
anchorX / anchorY number 0.5 Sprite anchor
crossOrigin 'anonymous' / 'use-credentials' / '' 'anonymous' Required for snapshot() to read pixels
playsInline boolean true iOS inline playback
onComplete string DSL hook key, fired on ended

Tilemap - @eva/plugin-renderer-tilemap

more like this

Scythe

SCYTHE is a lightweight, C#-based game engine focused on modifiability and rapid iteration using Raylib.

C#50

search

search projects, people, and tags