A from-scratch 2D ARPG engine — raw WebGL, zero game-framework dependencies
Live Demo · 中文文档
---
Miu2D is a **176,000-line** 2D ARPG engine written in TypeScript and Rust, rendering through **raw WebGL** with no dependency on Unity, Godot, Phaser, PixiJS, or any other game framework. Every subsystem — sprite batching, A* pathfinding, binary format decoders, scripting VM, weather particles, screen effects — is implemented from first principles.
As a proof of concept, Miu2D has been used to rebuild **three classic Kingsoft (西山居) wuxia RPGs**, all fully playable in any modern browser.
> **Vibe Coding** — This project is developed with AI-assisted programming from day one.
---
###
Legend of Yue Ying (剑侠情缘外传:月影传说) · 2001
| **Developer** | Xishanju (西山居 / Kingsoft) |
|---|---|
| **Genre** | Action RPG |
| **Highlights** | 7+ endings · 100+ story events · 30-person team (20+ artists) · 14-month production |
The largest production Xishanju had ever mounted at the time. The story branches dramatically based on player choices — loyalty, love, wealth — shaping the protagonist's morality and emotional alignment to produce seven or more distinct endings. Scenes were built with a pioneering 3D+2D hybrid rendering technique, and the soundtrack blended classical Chinese instruments with contemporary pop production.

---
###
Swords of Legends 2 (剑侠情缘贰) · 1998
| **Developer** | Xishanju (西山居 / Kingsoft) |
|---|---|
| **Genre** | Action RPG |
| **Highlights** | Diablo-style real-time combat · 200+ NPCs · 640×480 16-bit color · theme song by 谢雨欣 |
Set twenty years after the original, hero 南宫飞云 — son of the first game's protagonists — stumbles into a perilous journey after rescuing a mysterious girl named 若雪. The sequel boldly abandoned turn-based combat for a real-time action system inspired by *Diablo*, a revolutionary move for Chinese RPGs of the era. Three years in development with a budget of nearly ¥3 million and a team of 30.

---
###
New Swords of Legends (新剑侠情缘) · 2001
| **Developer** | Xishanju (西山居 / Kingsoft) |
|---|---|
| **Genre** | Action RPG |
| **Highlights** | Remake of the 1997 original · 110+ maps · indoor map system · real-time combat engine from Swords 2 |
A remake of the franchise's 1997 debut, rebuilt with the acclaimed real-time action combat engine from *Swords of Legends 2*. The story remains faithful to the original while greatly expanding the map count to 110+ scenes and introducing seamless indoor/outdoor transitions.

---
Mobile & Editor Screenshots
**Mobile — virtual joystick + touch controls:**

**Map Editor — visual tilemap editing, collision zones:**

**ASF Editor — sprite animation frame viewer & debugger:**

**Real-time Lighting & Shadows — additive glow + SHD shadow rendering:**

---
## Why Build a Game Engine from Scratch?
Most web game projects reach for PixiJS, Phaser, or a WASM-compiled Unity/Godot build. Miu2D takes a different path: the entire rendering pipeline talks directly to `WebGLRenderingContext`, the pathfinder lives in Rust compiled to WASM with zero-copy shared memory, and the scripting engine supports both 218 DSL commands through a custom parser/executor pair and a full **Lua 5.4 runtime** (via wasmoon) sharing the same GameAPI. The result is a system whose every layer is visible, debuggable, and tailored to 2D RPG mechanics.
**What this buys you:**
- **Full control over the render loop** — a `SpriteBatcher` coalesces ~4,800 map tile draws into 1–5 WebGL draw calls; a `RectBatcher` reduces ~300 weather particles to a single call; a real-time lighting pass composites per-entity additive glow masks with SHD-based shadow rendering.
- **No abstraction tax** — no unused scene graph, no 3D math overhead, no framework event model to work around.
- **Rust-speed where it matters** — A* pathfinding runs in ~0.2 ms via WASM with obstacle data written directly into linear memory (no serialization, no FFI copy).
- **Clean architecture for study** — an 8-level class hierarchy (Sprite → CharacterBase → Movement → Combat → Character → PlayerBase → PlayerCombat → Player) with clear separation of concerns, ideal for understanding how a full 2D RPG engine works under the hood.
---
## Architecture at a Glance
| Layer | Package | Details |
|---|---|---|
| **UI** | `@miu2d/game` | React 19 · 3 themes (Classic / Modern / Mobile) · 84 components |
| **Engine** | `@miu2d/engine` | Pure TypeScript · 215 files · 19 modules · no React dependency |
| ↳ Renderer | `renderer/` | Raw WebGL · SpriteBatcher · Canvas2D fallback · GLSL filters |
| ↳ Script VM | `script/` | 218 commands · custom parser + async executor · **Lua 5.4** (wasmoon WASM) |
| ↳ Character | `character/` | 8-level inheritance chain · NPC AI · bezier movement |
| ↳ Magic | `magic/` | 22 MoveKind trajectories · 10 SpecialKind effects |
| **WASM** | `@miu2d/engine-wasm` | Rust → WebAssembly · A\* pathfinder · decoders · SpatialHash · zstd |
| **Backend** | `@miu2d/server` | Hono + tRPC + Prisma ORM · 21 PostgreSQL tables · 19 routers |
| **Editor** | `@miu2d/dashboard` | VS Code-style layout · 13 editing modules |
### Tech Stack
| Layer | Technology |
|-------|-----------|
| Language | TypeScript 5.9 (strict) · Rust · GLSL |
| Frontend | React 19 · Vite 8 (rolldown) · Tailwind CSS 4 |
| Rendering | Raw WebGL API (Canvas 2D fallback) |
| Audio | Web Audio API (OGG Vorbis) |
| Performance | Rust → WebAssembly (wasm-bindgen, zero-copy) |
| Backend | Hono (lightweight HTTP) · tRPC 11 · Prisma ORM |
| Database | PostgreSQL 16 · MinIO / S3 |
| Validation | Zod 4 (shared schemas across client & server) |
| Quality | Biome (lint + format) · TypeScript strict mode |
| Monorepo | pnpm workspaces (11 packages) |
---
## Engine Systems
Miu2D implements **17 integrated ARPG subsystems** (218 script commands) entirely from first principles:
| System | Module | Highlights |
|--------|--------|------------|
| **Rendering** | `renderer/` | Raw WebGL sprite batcher (~4,800 tiles → 1–5 draw calls), Canvas2D fallback, GLSL color filters (poison / freeze / petrify), screen effects (fade, flash, water ripple), **real-time lighting & shadows** (additive lum masks, per-entity glow, SHD-based shadow rendering) |
| **Character** | `character/` | 8-level inheritance chain (Sprite → CharacterBase → Movement → Combat → Character → PlayerBase → PlayerCombat → Player/NPC); stats, status flags, bezier-curve movement |
| **Combat** | `character/` | Hit detection, damage formula, knockback, death & respawn, party/enemy faction logic |
| **Magic / Skill** | `magic/` | 22 MoveKind trajectories (line, spiral, homing, AoE, summon, time-stop…) × 10 SpecialKind effects; per-level config, passive XiuLian system |
| **NPC & AI** | `npc/` | Behavior state machine (idle / patrol / chase / flee / dead), interaction scripts, spatial grid for fast neighbor lookup |
| **Player** | `player/` | Controller, inventory (goods system), equipment slots, magic slots, experience & leveling |
| **Map** | `map/` | Multi-layer tile parsing, obstacle grid, trap zones, event areas, layer-sorted rendering |
| **Script / Event** | `script/` | Custom VM: parser + async executor, 218 commands across 9 categories (dialog, player, NPC, state, audio, effects, objects, items, misc); **Lua 5.4** scripting via wasmoon WASM with full GameAPI bindings (170 PascalCase functions) |
| **Pathfinding** | `wasm/` | Rust WASM A* with zero-copy shared memory; 5 strategies (greedy → full A*); ~0.2 ms per query, ≈10× faster than TS |
| **Collision** | `wasm/` | SpatialHash in Rust/WASM for O(1) broad-phase entity queries |
| **Audio** | `audio/` | Web Audio API manager: streamed BGM (OGG/MP3), positional SFX (WAV/OGG), fade transitions |
| **Weather / Particles** | `weather/` | Wind-driven rain + splash + lightning flash; wobbling snowflakes; screen-droplet lens effect |
| **Object / Prop** | `obj/` | Interactable scene objects (chests, doors, barriers, traps) with script hooks and sprite animation |
| **GUI / HUD** | `gui/` | Dialog system (branching choices, portraits), shop/buy panel, mini-map, status bars, UI bridge to React |
| **Inventory / Items** | `player/` | 10 goods categories, equip/unequip, use effects, loot drops with configurable drop tables |
| **Save / Load** | `storage/` | Multiple save slots, full game-state serialization to IndexedDB + server-side cloud saves |
| **Resource Loading** | `resource/` | Async loader for 8 binary formats (ASF, MPC, MAP, SHD, XNB, MSF, MMF, INI/OBJ); GBK/UTF-8 decoding |
---
## Engine Deep Dive
### Renderer — Raw WebGL with Automatic Batching
The renderer directly calls `WebGLRenderingContext` — no wrapper library.
- **SpriteBatcher** — accumulates vertex data and flushes per texture change; typical map frame: ~4,800 tiles → 1–5 draw calls
- **RectBatcher** — weather particles and UI rectangles batched into a single draw call
- **GPU texture management** — `ImageData` → `WebGLTexture` with `WeakMap` caching and `FinalizationRegistry` for automatic GPU resource cleanup
- **GLSL color filters** — grayscale (petrification), blue tint (frozen), green tint (poison) applied per-sprite in the fragment shader
- **Screen effects** — fade in/out, color overlays, screen flash, water ripple, all composited in the render loop
- **Canvas 2D fallback** — same `Renderer` interface, full feature parity for devices without WebGL
- **Local lighting (LumMask)** — when `SetMainLum` darkens the scene, light-emitting entities (objects, NPCs, magic projectiles) generate an additive white 800×400 elliptical glow mask at their position. A per-tile dedup (matching C++ `Weather::drawElementLum`) prevents double-drawing. A `noLum` flag on magic sub-projectiles suppresses redundant light sources for dense spell patterns, accurately matching the C++ reference:
- **LineMove**: 1-in-3 sub-projectiles emit light (`i % 3 === 1`)
- **Square region**: 1-in-9 (`i % 3 === 1 && j % 3 === 1`)
- **Wave / Rectangle region**: 1-in-4 (`i % 2 !== 0 && j % 2 !== 0`)
### Script Engine — 218 Commands + Lua 5.4
The engine supports two scripting modes that share the same GameAPI:
**DSL Mode (`.txt` / `.npc`)** — A custom **parser** tokenizes game script files; an **executor** interprets them with blocking/async support. Commands span 9 categories:
| Category | Examples |
|----------|----------|
| Dialog | `Say`, `Talk`, `Choose`, `ChooseMultiple`, `DisplayMessage` |
| Player | `AddLife`, `AddMana`, `SetPlayerPos`, `PlayerGoto`, `Equip` |
| NPC | `AddNpc`, `DelNpc`, `SetNpcRelation`, `NpcAttack`, `MergeNpc` |
| Game State | `LoadMap`, `Assign`, `If/Goto`, `RunScript`, `RunParallelScript` |
| Audio | `PlayMusic`, `StopMusic`, `PlaySound` |
| Effects | `FadeIn`, `FadeOut`, `BeginRain`, `ShowSnow`, `OpenWaterEffect` |
| Objects | `AddObj`, `DelObj`, `OpenObj`, `SetObjScript` |
| Items | `AddGoods`, `DelGoods`, `ClearGoods`, `AddRandGoods` |
| Misc | `Sleep`, `Watch`, `PlayMovie`, `DisableInput`, `ReturnToTitle` |
**Lua Mode (`.lua`)** — Full **Lua 5.4** runtime via [wasmoon](https://github.com/ceifa/wasmoon) (Lua compiled to WASM). All 170 GameAPI functions are exposed as PascalCase Lua globals. wasmoon's proxy system automatically bridges JS async functions to Lua coroutines — blocking operations like `PlayerWalkTo()` or `Talk()` just work with `co.await`-style transparency. Dispatched by file extension; the same `ScriptExecutor` routes `.lua` files to `LuaExecutor` and `.txt`/`.npc` files to the DSL executor.
```lua
-- Example Lua game script
FadeOut()
LoadMap("map/town.map")
SetPlayerPos(10, 15)
FadeIn()
Talk(0, "Welcome to the village.")
local choice = Choose("Join the quest?", "Yes", "No")
if choice == 1 then
AddMagic("magic/fireball.ini")
AddExp(500)
end
```
Scripts drive the entire game narrative — cutscenes, branching dialogs, NPC spawning, map transitions, combat triggers, and weather changes.
### Magic System — 22 Movement Types × 10 Special Effects
Every magic attack follows one of **22 MoveKind** trajectories, each with its own physics and rendering:
| Movement | Behavior |
|----------|----------|
| LineMove | Multi-projectile line — count scales with level |
| CircleMove | Orbital ring pattern |
| SpiralMove | Expanding spiral outward |
| SectorMove | Fan-shaped spread |
| HeartMove | Heart-shaped flight path |
| FollowEnemy | Homing missile tracking |
| Throw | Parabolic arc projectile |
| Transport | Teleportation |
| Summon | Spawn allied NPC |
| TimeStop | Freeze all entities |
| VMove | V-shaped diverging spread |
| *...and 11 more* | |
Combined with **10 SpecialKind** effects (freeze, poison, petrify, invisibility, heal, buff, transform, remove-debuff…), this produces hundreds of unique spell combinations. The system includes specialized sprite factories, a collision handler, and a passive effect manager (XiuLian/修炼).
### Pathfinding — Rust WASM, Zero-Copy Memory
The A* pathfinder is written in Rust, compiled to WebAssembly. It eliminates all FFI overhead through shared linear memory:
1. JavaScript writes obstacle bitmaps directly into WASM linear memory via `Uint8Array` views on `wasm.memory.buffer`
2. WASM executes A* in-place on shared memory
3. JavaScript reads path results via `Int32Array` pointer views — **zero serialization, zero copying**
Five path strategies (from greedy to full A* with configurable max iterations) let the game trade accuracy for speed. Typical pathfind: **~0.2 ms**, roughly **10× faster** than the equivalent TypeScript implementation.
### Binary Format Decoders
The engine parses **8 binary file formats** from the original game — all reverse-engineered and implemented without third-party parsing libraries:
| Format | Description |
|--------|------------|
| **ASF** | Sprite animation frames (RLE-compressed, palette-indexed RGBA) |
| **MPC** | Resource pack container (bundled sprite sheets) |
| **MAP** | Tile map data (multiple layers, obstacle grid, trap zones) |
| **SHD** | Shadow / height map data for terrain |
| **XNB** | XNA Binary format (audio assets from the original game) |
| **MSF** | Miu Sprite Format v2 — custom indexed-palette + zstd compression |
| **MMF** | Miu Map Format — custom zstd-compressed binary map data |
| **INI/OBJ** | Config files in GBK (Chinese legacy encoding) and UTF-8 |
### Weather System — Particle-Driven
Particle physics and rendering:
- **Rain** — wind-affected particles with splash on contact, periodic lightning flash illuminating the scene
- **Screen droplets** — simulated refraction/lens effect of water running down the camera
- **Snow** — individual snowflake physics with wobble, spin, drift, and gradual melt
### Character System — 8-Level Inheritance
A deep, well-structured class hierarchy with clear separation of concerns:
```
Sprite
└─ CharacterBase — stats, properties, status flags
└─ CharacterMovement — A* pathfinding, tile walking, bezier curves
└─ CharacterCombat — attack, damage calc, status effects
└─ Character — shared NPC/Player logic [abstract]
├─ PlayerBase → PlayerCombat → Player
└─ Npc — AI behavior, interaction scripts, spatial grid
```
---
## Game Data Editor (Dashboard)
The project includes a VS Code-style game editor with Activity Bar, Sidebar, and Content panels:
| Module | What it edits |
|--------|---------------|
| Magic Editor | Spell config with live ASF sprite preview |
| NPC Editor | Stats, scripts, AI behavior, sprite preview |
| Scene Editor | Map data, spawn points, traps, triggers |
| Item Editor | Weapons, armor, consumables, drop tables |
| Shop Editor | Store inventories and pricing |
| Dialog Editor | Branching conversation trees + portrait assignment |
| Player Editor | Starting stats, equipment, skill slots |
| Level Editor | Experience curves and stat growth |
| Game Config | Global game settings (drops, player defaults) |
| File Manager | Full file tree with drag-and-drop upload |
| Resources | Resource browser and viewer integration |
| Statistics | Data overview dashboard |
---
## Project Structure
11 packages in a pnpm monorepo, **~176,000 lines** total:
| Package | Role |
|---------|------|
| `@miu2d/engine` | Pure TS game engine — 19 modules, no React dependency |
| `@miu2d/dashboard` | VS Code-style game data editor (13 modules) |
| `@miu2d/game` | Game runtime with 3 UI themes (classic/modern/mobile) |
| `@miu2d/server` | Hono + tRPC backend (21 tables, 19 routers) |
| `@miu2d/types` | Shared Zod 4 schemas (18 domain modules) |
| `@miu2d/web` | App shell, routing, landing page |
| `@miu2d/converter` | Rust CLI: ASF/MPC → MSF, MAP → MMF batch conversion |
| `@miu2d/engine-wasm` | Rust → WASM: pathfinder, decoders, spatial hash, zstd |
| `@miu2d/viewer` | Resource viewers (ASF/Map/MPC/Audio) |
| `@miu2d/ui` | Generic UI components (no business deps) |
| `@miu2d/shared` | i18n, tRPC client, React contexts |
Also included: `resources/` (game assets), `docs/` (format specs), `JxqyHD/` (C# reference from the original engine).
---
## Quick Start
**Requirements:** Node.js 18+, pnpm 9+, modern browser with WebGL
```bash
git clone https://github.com/nicologies/miu2d.git
cd miu2d
pnpm install
pnpm dev # → http://localhost:5173
```
### Full Stack (with backend + database)
```bash
make init # Docker: PostgreSQL + MinIO, migrate, seed
make dev # web + server + db studio concurrently
```
### Commands
| Command | Purpose |
|---------|---------|
| `pnpm dev` | Frontend dev server (port 5173) |
| `make dev` | Full-stack dev (web + server + db) |
| `make tsc` | Type check all packages |
| `pnpm lint` | Biome lint |
| `make test` | Run engine tests (vitest) |
| `make convert` | Batch convert game resources (Rust CLI) |
| `make convert-verify` | Pixel-perfect conversion verification |
---
## Controls
### Desktop
| Input | Action |
|---|---|
| Left click (ground) | Move to position |
| Left click (NPC / object) | Interact |
| Right click (NPC / object) | Alternate interact |
| Ctrl + Left click | Attack in place |
| `Q` | Interact with nearest object |
| `E` | Interact with nearest NPC |
| `A` `S` `D` `F` `G` | Cast magic (skill slots 1 – 5) |
| `Z` `X` `C` | Use item (quick slots 1 – 3) |
| `V` | Toggle sitting / meditate (修炼) |
### Mobile
| Input | Action |
|---|---|
| Virtual joystick | Move |
| Tap (NPC / object) | Interact |
---
## Deployment
| Target | Method |
|--------|--------|
| **Frontend** | Vercel — `pnpm build:web` → static SPA |
| **Full Stack** | Docker Compose — PostgreSQL + MinIO + Hono + Nginx |
See [deploy/](deploy/) for production Docker configs.
---
## Contributing
1. Fork → feature branch → reference the [dev guide](.github/copilot-instructions.md) → PR
2. Run `make tsc` and `pnpm lint` before submitting
---
## Credits
- **Original Game**: Kingsoft (西山居) — *剑侠情缘外传:月影传说* (2001)
- [JxqyHD](https://github.com/mapic91/JxqyHD) — C# reimplementation of the game engine, provided invaluable reference for game logic, magic systems, and data formats
- [JXQY-all-in-one](https://github.com/Upwinded/JXQY-all-in-one) — Cross-platform C++ engine, provided secondary verification and calibration for game mechanics
> This is a fan-made learning project. Game assets and IP belong to their original creators.
## License
The engine source code is licensed under the [MIT License](LICENSE).
Game resources (images, audio, maps, scripts, etc.) are **not** covered by this license and remain the property of their original creators (Kingsoft / 西山居).
---
**⚔️ Sword spirit spans thirty thousand miles ⚔️**
*Recreating classic wuxia with modern web technology*