Skip to content

luckyyyyy/miu2d

Repository files navigation

Miu2D Logo

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.

Legend of Yue Ying


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.

Swords of Legends 2


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.

New Swords of Legends


Mobile & Editor Screenshots

Mobile — virtual joystick + touch controls:

Mobile

Map Editor — visual tilemap editing, collision zones:

Map Editor

ASF Editor — sprite animation frame viewer & debugger:

ASF Editor

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

Real-time Lighting


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 managementImageDataWebGLTexture 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 (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.

-- 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

git clone https://github.com/nicologies/miu2d.git
cd miu2d
pnpm install
pnpm dev            # → http://localhost:5173

Full Stack (with backend + database)

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/ for production Docker configs.


Contributing

  1. Fork → feature branch → reference the dev guide → PR
  2. Run make tsc and pnpm lint before submitting

Credits

  • Original Game: Kingsoft (西山居) — 剑侠情缘外传:月影传说 (2001)

This is a fan-made learning project. Game assets and IP belong to their original creators.


⚔️ Sword spirit spans thirty thousand miles ⚔️

Recreating classic wuxia with modern web technology

About

Miu2D is a 2D RPG game engine built with Rust + TypeScript + React + Canvas, designed for the Web platform. 剑侠情缘2/月影传说/新剑侠情缘网页复刻版

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors