eva.js

eva-engineMIT更新放缓

面向前端开发者的交互式游戏引擎,基于 Canvas2D 和 WebGL 渲染,帮你在网页里快速搭建可玩的游戏项目。

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

v2.0.1发布于 2026-04-21 07:57 UTC1,788star106fork同步于 2026-11-07 07:34 UTC

怎么上手

从 README 代码块原样提取,可能是其中一种安装方式
npm i @eva/eva.js @eva/plugin-renderer @eva/plugin-renderer-img --save
硬件要求

README 没有说明硬件要求。

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

这个项目的说明还在生成中,先参考下面「最近发版」和 README。

最近发版

5 个版本v2.0.1
  1. v2.0.1最新正式版2026-04-21 · 2026-04-21 07:57 UTC

    Breaking Changes

    Upgrade to PixiJS v8

    • Upgraded pixi.js dependency from v4.8.9 to v8.17.0 with performance and feature improvements
    • Refactored renderer code to adapt to PixiJS v8 API

    Web Worker Support

    • Added Web Worker runtime mode for multi-threaded rendering
    • Implemented Worker event mapping and screen coordinate transformation
    • Batch event sending to optimize main thread performance

    Multi Canvas Support

    • Support for multi-canvas rendering scenarios

    New Features

    Text Rendering Enhancements

    • HTMLText Component: New HTMLText component with rich text formatting, custom styles, and dynamic updates
    • Drop Shadow: Text and BitmapText components now support drop shadow effects
    • Gradient Fill: Text component supports PixiJS v8 fill gradients
    • Stroke Style: BitmapText now supports stroke properties with processStrokeStyle method for v8 compatibility
    • Font Resources: Support for async font resource loading and font family aliases
    • Resolution Parameter: Render component now accepts a resolution parameter for improved text rendering clarity

    Spine Skeletal Animation

    • Added Spine 3.6 version support (plugin-renderer-spine36)
    • Upgraded to pixi-spine 4.2
    • Added keepResource property for optimized Spine resource management
    • Refactored slot object management

    Lottie Animation

    • Added Lottie animation support
    • Support for dynamic Lottie data replacement
    • Playback direction control

    Audio

    • Migrated to @pixi/sound for v8 audio system support
    • Added AAC format support

    Scene Management

    • Added pauseScene / startScene methods for scene lifecycle management
    • Added resizeByScene method to adjust renderer size based on scene
    • Implemented name-based game object search across scenes

    Other Features

    • Mesh Plugin: New renderer-mesh plugin with perspective mesh and vertex count support
    • NinePatch: Support for sprite resources with optimized texture loading
    • Event System: Integrated canvas mapping event handling with canvas dimensions in event data
    • GameObject: Added children getter for accessing child game objects
    • Ticker: Implemented catch-up frame logic to maintain game time synchronization during RAF throttling
    • Animation Events: Added update event callback
    • KTX2 Compressed Texture support

    Bug Fixes

    • Fixed incomplete resource cleanup during game destruction
    • Fixed world matrix calculation and Ticker shared instance issues
    • Fixed resource loading error capture
    • Fixed A11y system setTransform call
    • Fixed BitmapText fontWeight/fontStyle type definitions
    • Fixed type definition for _flushPendingSlotObjects method in Spine component

    Engineering

    • Removed miniapp-related packages and configurations
    • Refactored type definition generation logic
    • Enhanced component and system documentation with descriptions and examples
    • Added benchmark example and canvas demo
    • Added test scripts with improved test coverage for resource management and rendering systems

    Total Changes: 269 files, +66,320 lines, -48,409 lines

  2. v1.2.82022-09-29 · 2022-09-29 03:33 UTC
    查看更新说明

    What's Changed

    New Contributors

    Full Changelog: https://github.com/eva-engine/eva.js/compare/v1.2.7...v1.2.8

  3. v1.2.72022-02-25 · 2022-02-25 07:46 UTC
    查看更新说明

    What's Changed

    New Contributors

    Full Changelog: https://github.com/eva-engine/eva.js/compare/v1.2.6...v1.2.7

  4. v1.2.62022-02-17 · 2022-02-17 09:55 UTC
    查看更新说明

    What's Changed

    Full Changelog: https://github.com/eva-engine/eva.js/compare/v1.2.5...v1.2.6

  5. v1.2.52021-12-21 · 2021-12-21 06:58 UTC
    查看更新说明

    What's Changed

    Full Changelog: https://github.com/eva-engine/eva.js/compare/v1.2.4...v1.2.5

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Eva.js (Interactive Game Engine)

    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
    @eva/plugin-renderer-particleParticle emitter with zones, ranges and atlas frames
    @eva/plugin-renderer-filterPixiJS 2D filters (blur, colorMatrix, displacement, noise, alpha)
    @eva/plugin-renderer-render-texturePhaser-style dynamic render texture
    @eva/plugin-renderer-dom-elementPin an HTML element to a GameObject transform
    @eva/plugin-renderer-videoHTML5 <video> rendering
    @eva/plugin-renderer-tilemap2D tilemap (Phaser v1 + Godot-style chunked v2)
    @eva/plugin-renderer-spine36Spine 3.6 skeleton animation
    @eva/plugin-hitareaTrigger-style overlap detection (Godot Area2D equivalent, no physics)
    @eva/plugin-input-actionMap raw input (key / mouse / touch) to semantic action signals
    @eva/plugin-camera2d2D camera with follow / deadzone / shake
    @eva/plugin-canvas-layerNamed render layers with zIndex / screen-space flag
    @eva/plugin-parallaxCamera-driven background parallax with optional tiling
    @eva/plugin-path-followMove a GameObject along a waypoint path (once / loop / pingpong)
    @eva/plugin-tweenGodot-style tween with sequence / parallel / yoyo
    @eva/plugin-animation-trackMulti-track keyframe animation
    @eva/plugin-easingShared easing functions (linear / quad / cubic / elastic / back / bounce)
    @eva/plugin-triggerSignal -> declarative actions, the stateless cousin of StateMachine
    @eva/plugin-state-machineFinite state machine driven by signals
    @eva/plugin-behavior-scriptGodot-style scriptable behaviors
    @eva/plugin-signal-busNamespaced global event bus
    @eva/plugin-tickReliable frame scheduler with RAF / wall fallback
    @eva/plugin-timerGodot-style countdown / interval timer
    @eva/plugin-uiUI kit: shapes + 14 @pixi/ui widgets + RadioGroup
    @eva/plugin-aiDOM AI overlay (semantic mirror of GameObjects)
    @eva/plugin-persistencelocalStorage <-> mx.store auto sync
    @eva/plugin-poolGameObject object pool with scene-scoped recycling
    @eva/plugin-workerRun Eva.js inside a Web Worker (OffscreenCanvas)
    @eva/spine-baseInternal base for Spine renderers
    @eva/renderer-adapterInternal 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)
    });
    
    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

    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');
    
    ParamTypeDefaultDescription
    resourcestringResource name (DRAGONBONE type)
    armatureNamestringArmature name; required, throws on missing
    animationNamestringInitial animation to play
    autoPlaybooleantrueAuto play animationName once armature is ready
    EventDescription
    startAnimation start / loop start
    loopCompleteOne loop finished
    completeAll loops finished
    fadeIn / fadeInCompleteFade-in start / complete
    fadeOut / fadeOutCompleteFade-out start / complete
    frameEventCustom keyframe event
    soundEventSound 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'));
    
    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());
    

    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
    
    ParamTypeDefaultDescription
    resourcestringImage or sprite atlas resource name
    framestring | string[]Atlas frame(s); array samples randomly per emit
    autobooleantrueAuto-start on add
    explodenumberOne-shot burst of N particles (overrides frequency)
    durationnumber-1Emit duration in ms; -1 = infinite
    stopAfternumberStop after N total particles emitted
    frequencynumber250ms between emits
    quantitynumber1Particles per emit
    maxParticlesnumber500Hard cap of live particles
    lifespanRangeValue1000Particle lifespan (ms)
    speed / speedX / speedY / angleRangeValueInitial velocity; angle in degrees
    rotateRangeValuePer-particle rotation speed
    scale / scaleX / scaleY / alphaRangeValueTween-capable via {start,end,ease}
    tintnumber | number[]Hex color, or random sample from array
    gravityX / gravityYnumber0Constant world gravity
    accelerationX / accelerationYRangeValuePer-particle acceleration
    moveTo{x, y}Aim each particle at this point (overrides angle)
    emitZoneEmitZoneSpecSpawn area: point / rect / circle / ellipse / line, type:'random'|'edge'
    deathZoneDeathZoneSpecKill area with mode:'onEnter'|'onLeave'
    onEmit / onUpdatestringNamed 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 fieldTypeUsed byDescription
    type'blur' | 'colorMatrix' | 'displacement' | 'noise' | 'alpha'allFilter kind
    enabledbooleanallPer-filter toggle
    strength / quality / blurX / blurYnumberblurPixi BlurFilter knobs
    preset / presetArg / matrixColorMatrixPreset / number / number[]colorMatrixBuilt-in preset (e.g. sepia, grayscale, negative, polaroid, vintage, hue, saturate, brightness, contrast) or raw 4x5 matrix
    resource / scaleX / scaleYstring / numberdisplacementDisplacement map image resource + scale
    noise / seed / noiseAnimSpeednumbernoiseNoise filter params
    alphanumberalpha0..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();
    
    ParamTypeDefaultDescription
    widthnumber256Logical RT width
    heightnumber256Logical RT height
    opsRenderTextureOp[][]Declarative draw queue, replayed on each commit
    backgroundColornumber-1Clear color (when append=false); -1 = transparent
    backgroundAlphanumber1Background alpha
    appendbooleantrueAppend ops without clearing; false clears each commit
    saveAsstringRegister 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',
    }));
    
    ParamTypeDefaultDescription
    htmlstringRaw HTML; first root node becomes the element
    elementstring'div'Tag name when html is empty
    classNamestringclassName applied to the element
    cssTextstringInline style.cssText
    styleRecord<string,string>Individual style props
    attrsRecord<string,string>setAttribute map
    width / heightnumberElement pixel size
    anchorX / anchorYnumber0.5Origin (0..1), Phaser-style
    blendModestringCSS mix-blend-mode
    pointerEventsstring'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
    
    ParamTypeDefaultDescription
    srcstring''Video URL, or a registered resource key
    loopbooleanfalseLoop playback
    autoplaybooleantrueAuto-start after loadeddata
    mutedbooleantrueRequired by browsers for unattended autoplay
    volumenumber10~1
    playbackRatenumber1Playback speed
    width / heightnumbervideo intrinsicDisplay size
    anchorX / anchorYnumber0.5Sprite anchor
    crossOrigin'anonymous' / 'use-credentials' / '''anonymous'Required for snapshot() to read pixels
    playsInlinebooleantrueiOS inline playback
    onCompletestringDSL hook key, fired on ended

    Tilemap - @eva/plugin-renderer-tilemap

    2D tilemap renderer supporting two coexisting paths:

    • v1 (Phaser-style, static): drive layers from a row-major 2D data[row][col] array against a single tileset image.
    • v2 (Godot-style, chunked): drive layers from tilemapRef + layersV2[].cellData.chunks (16x16 chunks, packed int32 per cell). The system also exposes peering / autotile / animation / mesh-vs-sprite strategy / static physics body helpers.

    The Tilemap system picks the path automatically based on whether tilemapRef is set.

    import { Tilemap, TilemapSystem } from '@eva/plugin-renderer-tilemap';
    import { resource, RESOURCE_TYPE } from '@eva/eva.js';
    
    resource.addResource([
      { name: 'tiles', type: RESOURCE_TYPE.IMAGE, src: { image: { type: 'png', url: '/tiles.png' } }, preload: true },
    ]);
    
    game.addSystem(new TilemapSystem());
    
    // v1 Phaser-style: static 2D grid, id 0 = empty, id N = (N-1)th tile.
    const tilemap = go.addComponent(new Tilemap({
      tileset: 'tiles',
      tileWidth: 32,
      tileHeight: 32,
      layers: [
        {
          name: 'ground',
          data: [
            [1, 1, 2, 0],
            [1, 2, 2, 3],
          ],
          offsetX: 0,
          offsetY: 0,
          alpha: 1,
        },
      ],
    }));
    
    // v2 Godot-style: chunked cellData via TILESET resource
    // resource.addResource([{ name: 'world', type: 'TILESET', src: { json: { url: '/world.tileset.json' } } }])
    // new Tilemap({ tilemapRef: 'world', layersV2: [...], renderStrategy: 'auto' })
    
    Param (v1)TypeDefaultDescription
    tilesetstring''Tileset image resource key
    tileWidth / tileHeightnumber32Source tile size in tileset
    tilesetColumnsnumberautoColumns in tileset (else derived from image)
    tilesetSpacing / tilesetMarginnumber0Atlas spacing / margin
    renderTileWidth / renderTileHeightnumbertileW/HRender-time tile size (scale)
    layersTilemapLayer[][]{ name, data[][], offsetX, offsetY, alpha, visible, tint }
    Param (v2)TypeDefaultDescription
    tilemapRefstringTILESET resource key (enables chunked v2 path)
    mapOrigin{x,y}{0,0}World origin of cell (0,0)
    cellSize{width,height}derivedCell pixel size (else from tileset doc)
    layersV2TileMapLayerV2[]Chunked layers (cellData: { kind: 'chunked', chunks })
    renderStrategy'sprite' / 'mesh' / 'auto''auto'Per-chunk renderer pick
    collisionEnabled / navigationEnabled / animationEnabledbooleanToggle physics body / navmesh / tile animations

    Also exports utilities: decodeChunk / unpackCell (cell codec), PeeringBitIndex / pickAutotileCandidate (autotile lookup), TileAnimationDriver, buildChunkMesh, buildTileMapStaticBodies (Matter.js bodies), diffTilesetForChunkRebuild (hot-reload diff).


    Spine36 - @eva/plugin-renderer-spine36

    Spine skeleton animation for the 3.6 export format, built on pixi-spine36. API is identical to @eva/plugin-renderer-spine (both extend the same @eva/spine-base base class) — pick this package when your Spine assets are exported from the 3.6 toolchain and the modern runtime cannot load them.

    import { Spine, SpineSystem } from '@eva/plugin-renderer-spine36';
    
    game.addSystem(new SpineSystem());
    
    const spine = go.addComponent(new Spine({
      resource: 'heroSpine36',
      animationName: 'idle',
      autoPlay: true,
    }));
    
    spine.play('walk', true);
    spine.addAnimation('attack', 0, false);
    spine.setMix('idle', 'walk', 0.2);
    spine.setAttachment('weapon', 'sword');
    

    The package also re-exports the bundled runtime as pixiSpine for advanced low-level access:

    import { pixiSpine } from '@eva/plugin-renderer-spine36';
    // pixiSpine.Spine, pixiSpine.AtlasAttachmentLoader, ...
    

    For the full event / Params reference see the Spine section above — same params (resource, animationName, autoPlay, scale), same events (start, complete, end, event, interrupt).


    HitArea - @eva/plugin-hitarea

    Trigger-style overlap detection without a physics engine — a lightweight Godot Area2D equivalent. Every frame the system pairs entities by layer / mask and emits enter / exit signals through @eva/plugin-signal-bus.

    import { HitArea, HitAreaSystem } from '@eva/plugin-hitarea';
    import { getSignalBus } from '@eva/plugin-signal-bus';
    
    game.addSystem(new HitAreaSystem());
    
    // Monster: listens for rockets entering its body
    monster.addComponent(new HitArea({
      shape: { type: 'circle', radius: 238 },
      layer: ['monster'],
      mask: ['rocket'],
      signalEnter: 'monster:hit',
      signalExit: 'monster:leave',
      oneShot: false,
      enabled: true,
    }));
    
    // Rocket: just declares its layer
    rocket.addComponent(new HitArea({
      shape: { type: 'rect', width: 40, height: 80 },
      layer: ['rocket'],
      mask: [],
    }));
    
    getSignalBus().on('monster:hit', ({ selfGo, otherGo }) => {
      console.log('rocket', otherGo.name, 'hit monster', selfGo.name);
    });
    
    ParamTypeDefaultDescription
    shape{ type: 'circle', radius } / { type: 'rect', width, height } / { type: 'point' }circle r=10Hit shape, with optional offsetX / offsetY
    layerstring[][]Which layers this area belongs to
    maskstring[][]Layers this area cares about (only matches when other.layer ∩ self.mask)
    signalEnterstring—Signal emitted on first overlap
    signalExitstring—Signal emitted when overlap ends
    oneShotbooleanfalseDisable self after first enter
    enabledbooleantrueSkip evaluation when false

    Signal payload: { self, other, selfGo, otherGo } (both components and GameObjects). If both layer and mask are empty, all HitAreas pair with each other (legacy DSL fallback). Broad phase is N² — fine for < ~50 active areas; add a quadtree above for larger scenes.


    InputActionMap - @eva/plugin-input-action

    Map raw keyboard / mouse / touch / click events to semantic actions (e.g. fire, left, jump) and emit press / release / hold signals through @eva/plugin-signal-bus. Keeps gameplay components decoupled from the underlying input device.

    import { InputActionMap, InputActionSystem } from '@eva/plugin-input-action';
    import { getSignalBus } from '@eva/plugin-signal-bus';
    
    game.addSystem(new InputActionSystem());
    
    const input = player.addComponent(new InputActionMap({
      bindings: [
        { action: 'fire', sources: [{ type: 'click' }, { type: 'key', code: 'Space' }] },
        { action: 'left', sources: [{ type: 'key', code: 'ArrowLeft' }] },
        { action: 'right', sources: [{ type: 'key', code: 'ArrowRight' }] },
      ],
    }));
    
    getSignalBus().on('input:fire:press', () => player.fire());
    getSignalBus().on('input:left:hold', () => player.moveLeft());
    
    // Polling-style query for non-signal callers
    if (input.isPressed('right')) player.moveRight();
    
    ParamTypeDefaultDescription
    bindingsActionBinding[][]Action → input source list
    rootSelectorstring—CSS selector for the listening element; defaults to document

    Input sources: { type: 'key', code }, { type: 'mouse', button? }, { type: 'touch' }, { type: 'click' } (touch + mouse merged).

    Default signals (override per binding via pressSignal / releaseSignal / holdSignal):

    SignalWhen
    input:{action}:pressFirst time any source goes down
    input:{action}:releaseWhen all sources are released
    input:{action}:holdEvery frame while the action is held

    Camera2D - @eva/plugin-camera2d

    2D camera with follow target, deadzone, damping, world limits and shake. The camera does not alter a renderer viewport — instead it translates the worldRoot GameObject every frame in the opposite direction of the follow target. Pair with @eva/plugin-canvas-layer (screenSpace: true) for HUD entities that should stay fixed.

    import { Camera2D, Camera2DSystem } from '@eva/plugin-camera2d';
    import { getSignalBus } from '@eva/plugin-signal-bus';
    
    game.addSystem(new Camera2DSystem());
    
    cameraGo.addComponent(new Camera2D({
      followEntity: 'Player',
      worldRoot: 'world',
      viewportCenter: { x: 360, y: 640 },
      deadzone: { x: 80, y: 60 },
      damping: 0.15,
      limits: { minX: 0, maxX: 2000, minY: 0, maxY: 1280 },
    }));
    
    // Trigger a shake via the signal bus
    getSignalBus().emit('camera:shake', { intensity: 12, duration: 250 });
    
    ParamTypeDefaultDescription
    followEntitystringEntity name to follow (searched in the active scene)
    worldRootstringEntity whose transform.position represents the world; gets offset every frame
    viewportCenter{x,y}{0,0}Screen point the target should be anchored to
    deadzone{x,y}{0,0}Pixel offset from viewportCenter before the camera starts moving
    dampingnumber0Smoothing factor 0..1 — 0 snaps instantly, 1 never catches up
    limits{minX,maxX,minY,maxY}Clamp the world offset on each axis (any field optional)
    SignalPayloadDescription
    camera:shake{ intensity?: number; duration?: number }Trigger a decaying random shake (default intensity=8, duration=200ms)

    CanvasLayer - @eva/plugin-canvas-layer

    Declare which logical layer a GameObject belongs to and whether it should ignore the camera. The system sorts sibling children of any parent that contains CanvasLayer entities by zIndex; the screenSpace flag is read by @eva/plugin-camera2d to decide whether the entity should follow world translation.

    import { CanvasLayer, CanvasLayerSystem } from '@eva/plugin-canvas-layer';
    
    game.addSystem(new CanvasLayerSystem());
    
    worldGo.addComponent(new CanvasLayer({
      name: 'world',
      zIndex: 0,
      screenSpace: false,
    }));
    
    hudGo.addComponent(new CanvasLayer({
      name: 'ui-hud',
      zIndex: 1000,
      screenSpace: true,   // pinned to screen — Camera2D will skip translating it
    }));
    
    ParamTypeDefaultDescription
    namestringSemantic layer name (e.g. background, world, ui-hud, ui-modal)
    zIndexnumber0Sort key — higher renders in front (siblings re-sorted each frame)
    screenSpacebooleanfalseWhen true, the entity is treated as HUD and won't be translated by Camera2D

    Parallax - @eva/plugin-parallax

    Move a GameObject opposite to a camera reference at a configurable speed factor, optionally wrapping after a tile width / height for looping backgrounds. Pair with @eva/plugin-camera2d (or any entity acting as a camera anchor).

    import { Parallax, ParallaxSystem } from '@eva/plugin-parallax';
    
    game.addSystem(new ParallaxSystem());
    
    // Far background — barely moves with the camera
    far.addComponent(new Parallax({
      speedX: 0.2,
      tileWidth: 1080,
      cameraEntity: 'mainCamera',
    }));
    
    // Mid background — moves faster, also tiles vertically
    mid.addComponent(new Parallax({
      speedX: 0.6,
      speedY: 0.6,
      tileWidth: 1080,
      tileHeight: 1920,
      cameraEntity: 'mainCamera',
    }));
    
    ParamTypeDefaultDescription
    speedXnumber0Horizontal parallax factor; 0 = static, 1 = locked to camera, >1 = faster than camera
    speedYnumber0Vertical parallax factor
    tileWidthnumber0Wrap position.x by tileWidth; 0 disables tiling
    tileHeightnumber0Wrap position.y by tileHeight
    cameraEntitystring—Name of the camera GameObject to read transform.position from

    The parallax entity must NOT be a child of the moving world root — keep it sibling to the camera (or under a fixed UI root), otherwise the world translation is applied twice. Base offset is captured in awake(), so set the entity's initial position to its design-time anchor.


    PathFollow - @eva/plugin-path-follow

    Move a GameObject along a polyline of waypoints at constant speed, with once / loop / pingpong modes and optional tangent-aligned rotation. Useful for patrolling enemies, conveyor items, or scripted cameras.

    import { PathFollow, PathFollowSystem } from '@eva/plugin-path-follow';
    import { getSignalBus } from '@eva/plugin-signal-bus';
    
    game.addSystem(new PathFollowSystem());
    
    const follow = enemy.addComponent(new PathFollow({
      waypoints: [
        { x: 0, y: 0 },
        { x: 200, y: 0 },
        { x: 200, y: 200 },
        { x: 0, y: 200 },
      ],
      speed: 180,              // px / sec
      loop: 'pingpong',         // 'once' | 'loop' | 'pingpong'
      autostart: true,
      rotateToFace: true,       // align transform.rotation to current segment
      signal: 'enemy:path:done',
    }));
    
    follow.play();
    follow.pause();
    follow.stop();
    
    getSignalBus().on('path:finish', ({ component }) => {
      console.log('reached the end', component);
    });
    
    ParamTypeDefaultDescription
    waypoints{ x, y }[][]Path points; ≥ 2 required to move
    speednumber0Travel speed in pixels per second
    loop'once' | 'loop' | 'pingpong''once'End-of-path behavior
    autostartbooleanfalseStart moving from awake()
    rotateToFacebooleanfalseSet transform.rotation to the current segment angle (radians)
    signalstring—Extra signal emitted on once completion

    Built-in signals: path:finish always fires when a once path completes, with { component } payload. Methods on the component: play(), pause(), stop() (resets to start and reapplies position).


    Tween - @eva/plugin-tween

    Godot-equivalent Tween component. Differences vs. @eva/plugin-transition:

    • Target is any path: transform.*, components.<Name>.<field>, store.<key> (mx.store)
    • Built-in sequence and parallel orchestration
    • Yoyo + finite/infinite loops
    • Emits a named signal on finish
    import { Tween, TweenSystem, Easing } from '@eva/plugin-tween';
    
    game.addSystem(new TweenSystem());
    
    // Single-step tween
    const tween = go.addComponent(new Tween({
      step: {
        target: 'components.RocketAimer.currentAngleDeg',
        from: -60, to: 60,
        duration: 1500,
        easing: 'easeInOutQuad',
      },
      yoyo: true,
      loop: -1,           // -1 = infinite
      autostart: true,
      signal: 'aimer:swept',
    }));
    
    // Multi-step (sequence by default, set parallel:true for simultaneous)
    go.addComponent(new Tween({
      steps: [
        { target: 'transform.position.x', to: 300, duration: 500, easing: 'easeOutCubic' },
        { target: 'transform.position.y', to: 100, duration: 500, delay: 100 },
      ],
      parallel: false,
    }));
    
    tween.play();
    tween.pause();
    tween.resume();
    tween.stop();
    
    ParamTypeDefaultDescription
    stepTweenStep-Single-step shorthand
    stepsTweenStep[][]Multi-step list
    parallelbooleanfalseRun steps in parallel instead of sequence
    yoyobooleanfalseReverse on every cycle end
    loopnumber0-1 for infinite, 0 for play-once
    autostartbooleanfalseStart on awake
    signalstring-Signal emitted on finish

    Each TweenStep is { target, from?, to, duration, easing?, delay? }. If from is omitted, the current value at play-time is captured.

    Easing names: linear, easeIn|Out|InOutQuad, easeIn|Out|InOutCubic, easeIn|Out|InOutElastic, easeIn|Out|InOutBack, easeIn|Out|InOutBounce. Import Easing to call the curves directly.

    SignalWhen
    tween:finishAlways emitted on finish (non-looping completion)
    signalEmitted on finish if configured

    AnimationTrack - @eva/plugin-animation-track

    Multi-track keyframe animation driven by a single timeline. Each track binds to a property path on the GameObject (e.g. transform.position.x) and interpolates between keyframes with optional per-segment easing.

    import { AnimationTrack, AnimationTrackSystem } from '@eva/plugin-animation-track';
    
    game.addSystem(new AnimationTrackSystem());
    
    const track = go.addComponent(new AnimationTrack({
      duration: 2,          // seconds; if omitted, derived from max keyframe time
      loop: true,
      autostart: true,
      signal: 'intro:done', // emitted on plugin-signal-bus when non-looping finishes
      tracks: [
        {
          target: 'transform.position.x',
          keyframes: [
            { time: 0,   value: 0,   easing: 'easeInOutCubic' },
            { time: 1,   value: 200, easing: 'easeOutBack' },
            { time: 2,   value: 0 },
          ],
        },
      ],
    }));
    
    track.play();
    track.pause();
    track.stop();
    track.seek(0.5);                 // jump to t=0.5s
    track.setTracks(newTracks, 3);   // swap tracks at runtime
    
    ParamTypeDefaultDescription
    tracksTrack[][]Array of { target, keyframes }; target is a dotted property path
    durationnumbermax keyframe timeTotal timeline length in seconds
    autostartbooleanfalseStart playing immediately on awake
    loopbooleanfalseLoop the timeline
    signalstringSignal name emitted on getSignalBus() when finished (non-loop only)

    A Keyframe is { time: number; value: number; easing?: EasingName }. Easing names come from @eva/plugin-easing (linear, easeInOutCubic, easeOutBack, …). Completion always fires track:finish on the signal bus; signal adds a second custom emit.


    Easing - @eva/plugin-easing

    Shared easing functions used by plugin-transition, plugin-animation-track, plugin-path-follow, plugin-camera2d. Pure math utility — no Component / System, no DSL.

    import { Easing, applyEase, type EasingName } from '@eva/plugin-easing';
    
    // Direct lookup
    const y = Easing.easeOutBack(0.5);
    
    // Safe apply (falls back to linear if name is missing / unknown)
    const v = applyEase('easeInOutCubic', 0.25);
    
    // Use in a custom tween loop
    function tween(from: number, to: number, t: number, name: EasingName) {
      return from + (to - from) * applyEase(name, t);
    }
    

    Available easings: linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInElastic, easeOutElastic, easeInOutElastic, easeInBack, easeOutBack, easeInOutBack, easeInBounce, easeOutBounce, easeInOutBounce.

    All easings accept and return t ∈ [0, 1]. Use this package instead of re-implementing easing math in your own plugin so curves stay consistent across the engine.


    Trigger - @eva/plugin-trigger

    Maps signals to declarative actions — emit, mutate mx.store, log, or call a method on another component. Stateless cousin of @eva/plugin-state-machine: covers the 80% of "on button press, increment score" wiring without writing a custom Component.

    import { Trigger, TriggerSystem } from '@eva/plugin-trigger';
    
    game.addSystem(new TriggerSystem());
    
    go.addComponent(new Trigger({
      rules: [
        { on: 'input:fire:press',
          do: [
            { type: 'emit', signal: 'rocket:spawn' },
            { type: 'incStore', key: 'shotsFired' },
          ] },
        { on: 'rocket:hit',
          guard: 'ctx.allowedScene === true',
          do: [
            { type: 'incStore', key: 'score', delta: 10 },
            { type: 'callMethod', entity: 'Monster', component: 'MonsterAI', method: 'onHurt' },
          ] },
      ],
      context: { allowedScene: true },
    }));
    
    Action TypeFieldsDescription
    emitsignal, payload?Re-emit via signal bus (default payload = incoming)
    setStorekey, valuemx.store.update(key, () => value)
    `incS