Eva.js (Interactive Game Engine)

April 21, 2026 · View on GitHub

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

PackageDescription
@eva/eva.jsCore engine: Game, GameObject, Component, System, Resource
@eva/plugin-rendererCore renderer (PixiJS)
@eva/plugin-renderer-imgImage rendering
@eva/plugin-renderer-textText rendering (Text, HTMLText, BitmapText)
@eva/plugin-renderer-spriteSprite sheet rendering
@eva/plugin-renderer-sprite-animationFrame animation
@eva/plugin-renderer-spineSpine skeleton animation
@eva/plugin-renderer-dragonboneDragonBones skeleton animation
@eva/plugin-renderer-lottieLottie animation
@eva/plugin-renderer-graphicsVector graphics drawing
@eva/plugin-renderer-nine-patchNine-slice scaling
@eva/plugin-renderer-tiling-spriteTiling sprite
@eva/plugin-renderer-maskMask / clipping
@eva/plugin-renderer-meshPerspective mesh deformation
@eva/plugin-renderer-renderRender properties (alpha, zIndex, visible)
@eva/plugin-renderer-eventTouch / pointer events
@eva/plugin-soundAudio playback
@eva/plugin-transitionTween animation
@eva/plugin-a11yAccessibility
@eva/plugin-evaxGlobal state management
@eva/plugin-matterjsPhysics engine (Matter.js)
@eva/plugin-layoutFlexbox layout
@eva/plugin-statsPerformance monitor

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)
});
MethodDescription
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
PropertyDescription
sceneCurrent main scene
playingWhether the game is running
tickerTicker instance
systemsRegistered 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 },
});
MethodDescription
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
PropertyDescription
transformTransform component
parentParent GameObject
childrenChild GameObjects
sceneScene 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,
});
MethodDescription
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' }));
ParamTypeDescription
resourcestringResource 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',
}));
ParamTypeDescription
resourcestringResource name (SPRITE type)
spriteNamestringSub-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();
ParamTypeDefaultDescription
resourcestringResource name (SPRITE_ANIMATION type)
autoPlaybooleantrueAuto play on load
speednumber100Milliseconds per frame
forwardsbooleanfalseFreeze on last frame when complete
PropertyDescription
currentFrameCurrent frame number
totalFramesTotal frame count
EventDescription
completeAll play iterations finished
loopEach loop iteration
frameChangeFrame 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,
}));
ParamTypeDescription
resourcestringImage or sprite resource name
spriteNamestringSub-image name (when using SPRITE resource)
leftWidthnumberLeft non-stretch width
topHeightnumberTop non-stretch height
rightWidthnumberRight non-stretch width
bottomHeightnumberBottom 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 },
}));
ParamTypeDefaultDescription
resourcestringImage 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);
EventDescription
completeAnimation complete
startAnimation started
endAnimation ended
eventSpine event triggered
interruptAnimation interrupted

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'));
EventDescription
completePlay complete
loopCompleteLoop iteration
enterFrameEach 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;
PropertyDescription
playingWhether sound is playing
volumeVolume (0-1)
mutedMute 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());

Questions

For questions and support please use Gitter or WeChat (微信) to scan this QR Code.

Issues

Please make sure to read the Issue Reporting Checklist before opening an issue. Issues not conforming to the guidelines may be closed immediately.

Changelog

release notes in documentation.

Contribute

How to Contribute

License

The Eva.js is released under the MIT license. See LICENSE file.