npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

easy-game-maker

v0.2.4

Published

TypeScript-first cross-platform 2D and 3D game engine

Readme

Easy Game Maker (EGM)

TypeScript-first 2D and 3D game engine. Write once, publish everywhere.

npm install -g easy-game-maker
egm new my-game
cd my-game && npm install && egm simulate

Platforms supported

Only the desktop target can be built today. Every other target is planned: running its command prints a "not available yet" message and exits with code 1.

| Command | Output | Status | |---|---|---| | egm build desktop macos | .app + .dmg | Available | | egm build desktop windows | .msi + .exe (Tauri) | Available | | egm build desktop linux | .AppImage + .deb (Tauri) | Available | | egm build web | Static bundle for Netlify, Vercel or any CDN | Coming soon | | egm build ios | Xcode project for the App Store | Coming soon | | egm build android | Gradle project for Google Play | Coming soon | | egm build tizen | Tizen project, package as .wgt with Tizen Studio | Coming soon | | egm build webos | .ipk for LG Smart TV | Coming soon | | egm build androidtv | Gradle + Leanback project for Android TV | Coming soon | | egm build tvos | Xcode project for Apple TV | Coming soon | | egm build xbox | PWA project; .msix requires the Windows SDK | Coming soon | | egm build playstation | PS5 project plus manual PlayStation SDK packaging | Coming soon |


Table of Contents

  1. Architecture
  2. Installation
  3. Project structure
  4. Configuration — egm.config.ts
  5. Engine API
  6. CLI reference
  7. E2E Testing
  8. Simulator
  9. Icons
  10. Build requirements
  11. Examples

Architecture

Your game (TypeScript)
  └── App  →  SceneManager  →  Scene  →  DisplayObjects
                                          └── Physics / Tweens / Audio / Network
                  │
         ─────────┴──────────────────────────────────────────
         WebGLRenderer   InputManager   AudioManager
         PhysicsWorld    TransitionMgr  TimerManager
         NetworkManager  AssetManager   ShaderSystem
         ─────────────────────────────────────────────────────
                  │
        ┌─────────┴─────────────────────────────┐
        │                                       │
   egm simulate                        egm build <platform>
   Simulator (localhost:5173)           dist/<platform>/
   X-Ray · Record E2E                   Web · iOS · Android
   Device previews                      Desktop · TV · Console

The engine is a pure TypeScript/WebGL2 library. Games run in any browser or WebView — the CLI wraps them in native shells per platform (Swift for iOS/macOS, Kotlin for Android/AndroidTV, Tauri for Windows/Linux).


Installation

Requirements: Node.js 18+

npm install -g easy-game-maker

# verify
egm --version

Project structure

my-game/
├── src/
│   ├── main.ts              ← createApp() entry point
│   ├── scenes/
│   │   ├── MenuScene.ts
│   │   └── GameScene.ts
│   ├── game/                ← entities, logic
│   └── helpers/
│       └── loadTexture.ts
├── public/
│   └── assets/              ← images, audio, fonts
├── egm.config.ts            ← project config
├── vite.config.ts
├── tsconfig.json
└── package.json

Configuration

egm.config.ts

import { defineConfig } from 'easy-game-maker';

export default defineConfig({
  app: {
    name: 'My Game',
    version: '1.0.0',
    bundleId: 'com.studio.mygame',
    icon: 'public/assets/icon.png',   // 512×512+ source image
  },
  display: {
    width: 568,
    height: 320,
    orientation: 'landscape',         // 'portrait' | 'landscape'
    backgroundColor: '#1a1a2e',
    scaling: 'fit',                   // see below
  },
  build: {
    ios:     { deploymentTarget: '16.0' },
    android: { minSdkVersion: 26, targetSdkVersion: 35 },
    desktop: { width: 1136, height: 640, resizable: false },
  },
});

scaling modes

| Value | Behaviour | |---|---| | fit | Scales to fit screen, preserves aspect ratio, letterbox bars (default) | | fill | Scales to fill screen, preserves aspect ratio, crops edges | | stretch | Stretches to fill screen (may distort) | | none | No scaling |

Scaling is injected automatically into every build — no game code change needed.


Engine API

App

import { App } from 'easy-game-maker';

const app = new App({
  width: 568,
  height: 320,
  backgroundColor: '#000',
  physics: false,        // enable engine-level physics world
});
app.init();              // creates canvas, initialises systems

app.scenes.add('menu', MenuScene);
void app.scenes.go('menu', { params: { app } });
app.run();

Properties:

| Property | Type | Description | |---|---|---| | app.renderer | WebGLRenderer | WebGL2 renderer, sprite batcher | | app.input | InputManager | Pointer & keyboard events | | app.audio | AudioManager | Sound & music (Web Audio API) | | app.physics | PhysicsWorld | planck.js physics | | app.scenes | SceneManager | Scene stack / navigation | | app.timers | TimerManager | Delayed / repeating callbacks | | app.transitions | TransitionManager | Tween animations | | app.assets | AssetManager | Batch asset preloading | | app.network | NetworkManager | Multiplayer via native WebSocket |


Scenes

import { Scene, type SceneParams, type App } from 'easy-game-maker';

export class GameScene extends Scene {
  private _app!: App;

  override async onCreate(params?: SceneParams): Promise<void> {
    this._app = params?.['app'] as App;
    // load textures, build scene graph — await texture loads here
  }

  override onUpdate(dt: number): void {
    // called every frame; dt = seconds since last frame (capped at 0.1)
  }

  override onResume(): void { /* scene becomes active   */ }
  override onPause():  void { /* scene goes to background */ }
  override destroy():  void { /* cleanup when destroyed  */ }
}

// Navigate between scenes
await app.scenes.go('game', {
  transition: 'fade',
  duration: 300,
  params: { app, level: 2 },
});

Display Objects

import {
  Sprite, AnimatedSprite, RectShape, CircleShape,
  LineShape, Text, Group,
} from 'easy-game-maker';

// Sprite
const s = new Sprite({ texture, x: 100, y: 200, width: 64, height: 64 });
s.anchorX = 0.5;   // 0 = left edge, 0.5 = center, 1 = right edge
s.anchorY = 0.5;
s.rotation = Math.PI / 4;   // radians
s.alpha    = 0.8;
s.scaleX   = 2;
scene.add(s);

// Animated sprite (frame array)
const anim = new AnimatedSprite({ frames: [tex0, tex1, tex2], fps: 12, x: 0, y: 0 });
anim.play();

// Shapes
const rect   = new RectShape({ x: 0, y: 0, width: 100, height: 50, fill: '#f00' });
const circle = new CircleShape({ x: 200, y: 200, radius: 30, fill: '#0f0', stroke: '#fff', strokeWidth: 2 });
const line   = new LineShape({ x1: 0, y1: 0, x2: 100, y2: 100, stroke: '#fff', strokeWidth: 3 });

// Text
const label = new Text({ text: 'Score: 0', x: 10, y: 10, fontSize: 24, color: '#fff' });

// Group (container with its own transform)
const group = new Group();
group.add(s);
group.add(label);
scene.add(group);

Input

import type { PointerEvent2D, KeyEvent2D } from 'easy-game-maker';

// Pointer — fires in game coordinates (accounts for CSS scaling)
app.input.on<PointerEvent2D>('pointerdown', (e) => { console.log(e.x, e.y); });
app.input.on<PointerEvent2D>('pointermove', (e) => { ... });
app.input.on<PointerEvent2D>('pointerup',   (e) => { ... });

// Keyboard
app.input.on<KeyEvent2D>('keydown', (e) => {
  if (e.code === 'Space') jump();
});
app.input.on<KeyEvent2D>('keyup', (e) => { ... });

// Polling
if (app.input.isKeyDown('ArrowLeft')) moveLeft();
const { x, y, isDown } = app.input.pointer;

Physics

Uses planck.js (Box2D port). Coordinates are in pixels; the engine converts to metres internally.

import { PhysicsWorld } from 'easy-game-maker';

// Per-scene physics world (recommended — gives full control)
const physics = new PhysicsWorld({ gravity: { x: 0, y: 9.8 } });

const body = physics.addBody(sprite, {
  type:   'dynamic',    // 'dynamic' | 'static' | 'kinematic'
  shape:  'circle',     // 'circle' | 'rect'
  radius: 20,           // for circle
  // width, height      // for rect
});

// Events: 'beginContact' and 'endContact' (payload: bodyA, bodyB, displayA, displayB)
physics.on('beginContact', ({ bodyA, bodyB }) => {
  console.log('hit!', bodyA, bodyB);
});

// IMPORTANT — use fixed-step for stability across platforms:
private _acc = 0;
override onUpdate(dt: number): void {
  this._acc += dt;
  while (this._acc >= 1/60) {
    physics.step(1/60);
    this._acc -= 1/60;
  }
}

Animation

import { TransitionManager, Easing } from 'easy-game-maker';

const tween = new TransitionManager();

// Animate any numeric properties on any object
tween.to(sprite as unknown as Record<string, number>, {
  x:        400,
  alpha:    0,
  duration: 1000,                // milliseconds
  easing:   Easing.outQuad,
  onComplete: () => scene.remove(sprite),
});

// Must be updated every frame
override onUpdate(dt: number): void {
  tween.update(dt);
}

Audio

// Load
await app.audio.loadBuffer('shoot', await fetch('assets/shoot.ogg').then(r => r.arrayBuffer()));

// Play
app.audio.play('shoot', { volume: 0.8 });
app.audio.play('music', { loop: true, volume: 0.5 });
app.audio.stop('music');

Network - Multiplayer

Uses the browser's native WebSocket API. The engine supplies the client; you provide a server that implements the room protocol.

// Configure the server once, for example in MenuScene.
app.network.setServer('ws://localhost:2567');

// Join a room. The server must respond with room:joined or room:error.
const room = await app.network.joinRoom<RaceState>('race', {
  playerName: 'Ada',
  map: 'circuit-1',
});

// Receive JSON messages sent by the server.
room.onMessage<{ id: string; x: number; y: number }>('kart:update', (kart) => {
  updateKartPosition(kart.id, kart.x, kart.y);
});

// Subscribe to typed server messages
room.onMessage<{ startTime: number }>('race:start', ({ startTime }) => {
  beginRace(startTime);
});

// Send messages to server (20 Hz typical)
room.send('kart:update', { x, y, angle, speed });

// Leave
room.leave();

NetworkManager methods:

| Method | Description | |---|---| | setServer(url) | Set the WebSocket server URL | | joinRoom<T>(type, opts?) | Connect and join a server room | | leaveAll() | Leave all joined rooms |


CLI Reference

egm new <name>              # scaffold a new project
egm simulate                # start Simulator (localhost:5173)
egm build <platform> [os]   # build for target platform
egm test                    # run test suite (Vitest)
egm e2e                     # run E2E tests (visual)
egm e2e --headless          # only generates __e2e.html; does NOT run tests, exits 1
egm e2e --headless --json   # same, plus .egm-e2e-report.json with status "not-run"

Build targets

Only desktop is available for now. The other targets are refused with a "not available yet" message (exit code 1).

egm build desktop                # builds for the current OS
egm build desktop --yes          # answer yes to "build now?" / "open the app?" (--no-prompt: never ask)
egm build desktop macos          # .app + .dmg
egm build desktop windows        # .msi + .exe  (requires Rust + Tauri)
egm build desktop linux          # .AppImage + .deb

Coming soon: web, ios, android, tizen, webos, androidtv, tvos, xbox and playstation.


E2E Testing

The current E2E runner generates a visual runner from files in src/e2e/*.e2e.ts. Its helpers are bundled with generated projects; they are not yet published as an importable package subpath.

// `easy-game-maker/e2e` is not an installable subpath: the runner replaces this import
// with its own `test` function. Relative imports between your .e2e.ts files are bundled.
import { test } from 'easy-game-maker/e2e';

test('Menu navigates to level', async ({ game }) => {
  await game.wait(3000);
  await game.screenshot('menu-ready');
  await game.tap(284, 240);                        // tap Play button
  await game.expect.scene('level');                // assert scene changed (retries 6s)
  await game.screenshot('level-loaded');
});

test('Drag cuts the rope', async ({ game }) => {
  await game.wait(1000);
  await game.expect.scene('game');
  await game.drag(120, 150, 230, 130, { duration: 300 });
  await game.expect.state(
    (win) => win.__EGM_APP__?.scenes?.current?._state !== 'playing',
    'rope should be cut',
  );
});
egm e2e          # opens visual runner in browser

game fixture API:

| Method | Description | |---|---| | game.wait(ms) | Wait N milliseconds | | game.tap(x, y) | Pointer tap at game coordinates | | game.drag(x1,y1, x2,y2, opts?) | Drag gesture | | game.key(key, opts?) | Press and release a key | | game.screenshot(name?) | Capture canvas screenshot | | game.expect.scene(name) | Assert active scene (with retry) | | game.expect.state(fn, msg?) | Assert custom game state (with retry) |


Simulator

egm simulate

Opens at http://localhost:5173/__simulator.html with:

  • Live reload on file change
  • Device presets (iPhone, Android, tablet, desktop HD/FHD)
  • X-Ray overlay — visualises display objects and physics bodies
  • Record E2E — records interactions and generates .e2e.ts test files
  • Keyboard shortcuts 1–9 to switch device

Icons

Set app.icon in egm.config.ts pointing to a 512×512+ PNG. EGM auto-generates all required sizes per platform at build time.

| Platform | Sizes generated | |---|---| | Android | 48, 72, 96, 144, 192 px (all mipmap densities) | | Android TV | 320×180 launcher banner | | macOS | 32, 128, 256 px + .icns via iconutil | | Tizen | 512×512 | | webOS | 80×80, 130×130 | | Xbox | 44, 50, 150, 310 (all UWP required sizes) |

Uses sips (macOS built-in), sharp, or convert (ImageMagick) — whichever is installed first.


Build Requirements

Web

No extra tools needed.

iOS

xcodes install 15.4           # or newer
sudo xcode-select --switch /Applications/Xcode-15.4.app/Contents/Developer
sudo xcodebuild -license accept

Android

brew install --cask android-studio
# Add to ~/.zshrc:
export ANDROID_HOME="$HOME/Library/Android/sdk"
export PATH="$PATH:$ANDROID_HOME/platform-tools"

macOS Desktop

xcode-select --install

Windows / Linux Desktop (Tauri)

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install tauri-cli --version "^2"
# Linux only:
sudo apt install libwebkit2gtk-4.1-dev libssl-dev libayatana-appindicator3-dev

Samsung Tizen

# Install Tizen Studio + TV Extension:
# https://developer.samsung.com/smarttv/develop/getting-started/setting-up-sdk/installing-tv-sdk.html
# Or use zip (built-in on macOS/Linux) — no signing, for testing only

LG webOS

npm install -g @webos-tools/cli

Xbox

Register at partner.microsoft.com ($19 one-time). Upload the generated .msix.

PlayStation 5

Apply (free) at partners.playstation.com. Use the generated project.gp4 with Sony's orbis-pub-cmd.


Examples

Located in easy-game-maker-examples/:

| Project | Description | Key features | |---|---|---| | pong | Classic Pong | Physics, score | | tetris | Tetris clone | Grid logic, level progression | | fhz | Fruits Hate Zombies | Catapult physics, 5 levels, audio | | cut-the-rope | Rope cutting puzzle | planck.js constraints, 3 levels | | chess | Chess | Turn-based logic, validation | | small-mission | Multiplayer arena | Native WebSocket rooms and real-time state |

Run any example

cd easy-game-maker-examples/fhz
npm install
egm simulate

Development

git clone <repo>
cd easy-game-maker
npm install

npm run build:engine   # Vite → dist/engine/engine.js
npm run build:cli      # tsup → dist/cli/index.cjs
npm run build          # both

npm test               # Vitest unit tests

License

MIT