- 2D 小游戏或原型:渲染接口就是 save/translate/fillRect 那套,从 Canvas 手写代码迁过来几乎不用重学,换 Phaser 得重新适应 Scene/GameObject 体系
- 需要同时出 2D 和 3D 场景的项目:Tiled 地图和 glTF/GLB 走同一个 level.load(),不用为了一个 3D 关卡再引入 three.js 并自己接物理
- 目标设备跨度大的发布:AUTO 模式在 WebGPU 不可用时降级到 WebGL2,再不行退到 Canvas,老设备至少能跑起来而不是白屏
- 不想管音频素材的 jam 项目:内置 WebAudio 空间音频和程序化音效,省掉采样资源这一环
melonJS
melonjsMIT活跃维护 有专家点评面向 JavaScript/TypeScript 的现代 HTML5 游戏引擎,支持 2D/3D、WebGL/WebGPU 渲染,内置物理、Tiled 地图、glTF 与 WebAudio,省去自己搭底层。
A modern & fully featured HTML5 game engine (2D & 3D) for JavaScript/TypeScript, with WebGL/WebGPU rendering, built-in physics, Tiled map & glTF support and WebAudio.
专家点评
上手难度:进阶更新于 2026-11-11一个 API 仿 Canvas2D 的 HTML5 引擎,用一套代码同时覆盖 WebGPU、WebGL2 和 Canvas 三条渲染路径
适用场景
踩坑
- 只支持 WebGL1 的设备:20.0 起 WebGL1 路径已移除,这类设备在 AUTO 下会掉到 Canvas 渲染,2D 还能看,3D 场景基本没法用,发布前得确认目标机型分布
- 模块化/工程化接入:issue 里 ES6 class 定义与 module 支持、npm 依赖管理这两类被反复提出且讨论量大,说明把它塞进现代打包流程不是开箱即用的事,动手前先读构建相关文档
- Tiled 工作流:Infinite Map 支持是仍在被提的 feature request,如果你的关卡设计依赖无限地图,得先确认当前版本行为再决定是否换关卡格式
同类对比
2D 且要成熟生态和大量教程时选 Phaser;3D 为主、需要完整 PBR 与编辑器时选 three.js 或 Babylon.js,melonJS 的 3D 是够用而非深入
npm install melonjsREADME 未说明具体显存或 GPU 型号要求;引擎运行在支持 WebGPU、WebGL 2 或 Canvas2D 的浏览器上,WebGL 1 设备在 AUTO 模式下回退到 Canvas 渲染器。
项目说明
由 README 自动翻译提炼面向独立开发者的开源 HTML5 2D/3D 游戏引擎,一次编写即可在 WebGPU、WebGL 2 或 Canvas 上运行
面向 JavaScript/TypeScript 的现代 HTML5 游戏引擎,支持 2D/3D、WebGL/WebGPU 渲染,内置物理、Tiled 地图、glTF 与 WebAudio,省去自己搭底层。
核心能力
- 渲染 API 仿 Canvas2D,提供 save、translate、fillRect 等熟悉接口
- AUTO 模式启动时优先使用 WebGPU,不可用时回退到 WebGL 2 或 Canvas
- 用 level.load(name) 一行加载 Tiled 2D 地图或 glTF/GLB 3D 场景
- 内置 SAT 多边形物理,2D 用 QuadTree、3D 用 Octree 做宽相检测
- 内置 WebAudio 3D 空间音频与程序化音效,无需采样素材
容易踩的坑
自 20.0 起 WebGL 1 路径已移除,仅支持 WebGL 1 的设备会回退到 Canvas 渲染;音频后端为内置定制版 howler.js 衍生代码,不是运行时依赖,无需另行安装;README 未说明默认端口等其他配置项。
- 20.13.0v20.13.0最新正式版2026-11-06 · 2026-11-06 10:24 UTC
Added
- Particles: a
depthsetting, so particles under aCamera3dspawn through a volume instead of on a flat sheet - Text:
paintOrder, to draw the outline behind the glyphs ("stroke", the default) or over them ("fill") - Text:
lineJoinandmiterLimit, the shape of the outline's corners ("round"by default,"bevel"or"miter") - UI:
UICheckbox, a checkbox with its label, that becomes a radio button with agroup(#1722) - UI:
UISlider, a horizontal or vertical slider withmin,maxandstep, moved by dragging, clicking the track or the mouse wheel - UI:
UIPanellays out its children ("column","row"or"grid"), scrolls them, and takes a nine-sliceskin, atitleanddraggable - UI:
UITextButtontakesonClick, askinandpressedColor, and the size, font and color settings the other widgets use;labelis its text object - UI: every
UIBaseElementhasenabled,cancelPress()/onCancel(), and a hover and press bounce (hoverScale,pressScale) - UI: keyboard and gamepad navigation between widgets, once one takes the focus with
focus() - UI: drop shadows on
UIPanelandProgressBar(shadowColor,shadowOffsetX,shadowOffsetY,shadowBlur) - UI:
UISpriteElementis aUIBaseElementholding aSprite, so it has everything the other widgets have;Spritemembers other than its region and animation methods are onelement.sprite - UI: the widgets' settings and skin types are exported,
ProgressBar'sonChangealso gets the bar, andContainer#clipContains(x, y)tells whether a container shows its children at a point
Deprecated
- UI:
UITextButton#bitmapText, forlabel
Fixed
- Rendering: a translucent
setColor()no longer fades what is drawn after it, nor changesgetGlobalAlpha() - Rendering: on WebGL and WebGPU, a clip inside a clip is their overlap, as on Canvas (#1816)
- WebGL:
fillArc()handles an end angle before the start, andsave()/restore()keeplineWidthandlineJoin, as on Canvas - Camera: post effects on a camera no longer wipe out the cameras drawn before it; a minimap with effects shows the view behind where it draws nothing
- Camera:
worldToLocal()/localToWorld()are right on a minimap or zoomed camera when the level is centred - Camera:
fadeIn()andfadeOut()always callonComplete(#1810) - Container:
reset(), and solevel.load, keeps the pending sort of the persistent children it keeps - Container:
onDeactivateEventis called once per child of a removed container, not twice or more - Container:
enableChildBoundsUpdatefollows moving children, so the camera no longer culls the group - Container: physics bodies of nested children are added and removed at any depth; moving a group with bodies to another container no longer throws
- World: a persistent container's children keep their physics bodies through a state change or
level.load - Sprite: redefining the playing animation no longer crashes the next update when it is shorter
- Sprite: a
flicker()callback gets the sprite asthis, after the flicker has ended - Particles: particles follow their emitter's
visibilityLayer - Particles: an emitter without an
imageno longer leaks a texture per emitter orreset(); all share the renderer's white pixel (#1807) - Text: an outline no longer hides thin letters or gets cut off at the edges. Its corners are round by default and only its outer half shows: double
lineWidthto keep its weight, or usepaintOrder: "fill"andlineJoin: "miter"for the old look - Text: word wrap keeps
\nline breaks, and no longer adds a blank first line for a long first word (Text and BitmapText) - Text: multi-line text is placed by
textBaselineas one block, as BitmapText is, so it stays inside its bounds
- Particles: a
- 20.12.0v20.12.02026-10-30 · 2026-10-30 03:00 UTC
查看更新说明
Added
- State: a
"pixelate"transition,state.transition("pixelate", color, duration, size): the screen breaks into growing blocks and comes back sharp. Skipped on Canvas
Fixed
- Rendering: with
subPixeloff, each draw snaps to whole pixels once, where it lands, the same on Canvas, WebGL and WebGPU (#1765). Something at a fractional position can draw one pixel over - Camera: with
subPixeloff,worldView,localToWorldandworldToLocaluse the view position rounded to a whole pixel, where the world is drawn from - Geometry: the methods taking a point (
translate,shift,contains) take any object withxandy, such as a renderable'spos, instead of returningNaN - Physics: a body with several shapes has the union of its shapes as its bounds; each shape added shifted them
- Physics:
Body#setVerticeson one of several shapes keeps the body's bounds andpseudoInertiaright (set it again if you set it by hand), and throws aRangeErrorpast the last shape - Physics: a body that drops its last
Box3dorSphereno longer resolves along z - Loader: the default loading screen shows its progress bar and logo in the same frame
- ImageLayer: a repeating layer keeps covering the screen however far the camera moves, instead of sliding off
- Animation: a one-frame animation played once completes, so
onCompletefires andnextis followed - Audio:
audio.setListener(app.viewport)hears from the center of the camera's view, not its top-left corner - Input:
input.bindPointerkeeps its bindings once the mouse enters the canvas - Audio: a clip with
preload: falseis fetched and streamed when first played; it never played (#1784) - State: the first frame of a new stage or level shows its world, not the background alone
- State: a
state.change()asked for during a transition is made by that transition, instead of leaving the screen black (#1787) - Effects: the built-in effects that place a pattern (sway, wave, vignette, gradient, scanline, shine) place it on the sprite, also cut from an atlas or with other effects after it (#1407)
- Effects: a renderable drawn through two or more effects keeps its opacity on WebGL and WebGPU (#1791)
- Effects: through two or more effects, widths, offsets and sizes are in the renderable's own pixels at any scale or zoom, as with one effect. Check widths tuned on a scaled renderable
- Effects: a renderable drawn through two or more effects stays inside its clip, such as a minimap camera's view, instead of spilling over the canvas
- Effects: on WebGPU, effects read their capture at the game's texture filter, as on WebGL, so pixel-art games stay crisp
- Input: a gamepad unplugged while a button or the stick is held lets go of the keys bound to them, instead of leaving them pressed
- Input: a stick with only one side bound lets go of its key when it returns to the centre or rests just past it, instead of holding it until pushed that way again
- Lighting: a
Light2d's glow and the darkness around it compile when the light is added to the world, not inside the first frame that draws it - Typings:
TextureAtlas#createAnimationFromNamedeclares both of its arguments optional, as they are at runtime, and is documented again
Full Changelog: https://github.com/melonjs/melonJS/compare/20.11.0...20.12.0
- State: a
- 20.11.0v20.11.02026-10-27 · 2026-10-27 10:13 UTC
查看更新说明
Added
- Effects: colors take an alpha. Every color option and
setColortakes[r, g, b]or[r, g, b, a], the alpha being the color's opacity ([r, g, b]stays opaque),RadialGradientEffecthonours the alpha of itsColor, andDropShadowEffectgains thesetColorthe others have. Four values used to break an effect (WebGL rejected them, and on WebGPU the fourth overwrote the next setting, such as an outline's width); a color of any other length now throws aTypeErrorsaying so - Effects:
SwayEffectleans a sprite or a layer from side to side, as if in a breeze, its top swaying most and everything from its anchor row down staying put. It is aWaveEffect, which now takes ananchorfor the row its wave fades out at, and it wraps its own time so it never stutters on a phone's medium-precision floats - Rendering:
Renderable#sortOffsetlines a renderable up as if it stood that many pixels lower (or higher) when its container sorts on"y"or"x", without moving it: a torch drawn over the wall it hangs on, dust sorted with the door it rises from, or art of different heights sorted by its base - Lighting:
Light2d#softness(0 to 1) fades the edge of a light's hole in the stage's ambient darkness, from a hard rim (0, the default, as before) to a darkness that thins straight from the center (1), along the same falloff as the glow. Hard and soft lights mix in one scene, overlapping lights multiply, and it works on every renderer - Particles:
tintOverLifegives the tints a particle takes over its life, evenly spaced and blended from the first at birth to the last at the end, such as a flame turning from white hot to deep red. It sets the color only (the fade still sets the opacity), and the blend is taken in 32 steps, which keeps the Canvas renderer to 32 tinted copies of the texture - Animation:
setCurrentAnimation(name, { onFrame })calls back with each frame index as playback reaches it, from frame 0 when the animation starts, so a footstep or a hit can land on its frame. It applies to the frame animations of aSpriteor aSprite3d - Audio:
playTrack(name, { volume, sprite })plays one region of a clip as the track and loops on it. A region of a loop's exact length keeps an MP3 loop gapless in Safari, which plays the padding an MP3 encoder adds to the end of the file - Tiled: a tile object with no class of its own is built by the class registered under its tile's class, as Tiled shows it, so a class set once on a tile in the tileset applies to every object placed from it, and
TMXTileset#getTileClass()returns that class. The object's own class or registered name still comes first, and a tile classed like a built-in (Sprite,Trigger,shape...) stays a plain tile - Tiled:
TMXObject#resolvedProperty()andresolvedProperties()return an object's custom properties together with those it inherits from its tile, the object's own winning, as the methods of the same names in Tiled's scripting API do. Only the object's own properties are set on it by name, as before - Tiled: a tileset, a tile of an image collection or an image layer whose image has a transparent color (
transin TMX,transparentcolorin JSON) is drawn with that color transparent, as Tiled shows it. The color is cleared once when the map loads, on a copy of the image - Typings:
TMXObject, the settings a class registered withregisterTiledObjectClass(or a factory registered withregisterTiledObjectFactory) receives, is exported as a type, and both functions are typed with it. A class or factory typed with its own settings still compiles - Rendering:
textureFilter: "linear-mipmap"samples a texture's mipmaps wherever it is drawn smaller than its size, so a zoomed-out camera, a minimap or a scaled-down sprite no longer shimmers as it moves; it covers sprites, text and tile layers on WebGL and WebGPU. A sprite cut from an atlas or a tileset keeps its samples inside its own frame, so its neighbours no longer streak along i
- Effects: colors take an alpha. Every color option and
- 20.10.1v20.10.12026-10-21 · 2026-10-21 09:53 UTC
查看更新说明
Fixed
- Rendering: entering a state no longer stalls its first frame compiling the shaders its stage had only started preparing. That frame now waits until they are ready, for at most 1.5s, and
STATE_CHANGEand a transition's reveal come with it, so the reveal plays in full instead of running its course behind the stall - Rendering: the first frame of a scene whose camera uses fog no longer stalls compiling its meshes. The warm-up read the fog of whichever camera drew last, which after a state change is the previous screen's, so it prepared the variant without fog and the first frame then built the fogged one on the spot
- Rendering: a camera's
colorMatrix, and on WebGPU anInstancedMeshand its ground shadow, are now prepared when the stage is built, like every other renderable since 20.10, instead of compiling in the first frame that draws them - WebGL: an
ImageLayerwith a single post effect now draws through it. The layer's repeated image was drawn with the default shader, so the effect did nothing on WebGL while WebGPU applied it
Full Changelog: https://github.com/melonjs/melonJS/compare/20.10.0...20.10.1
- Rendering: entering a state no longer stalls its first frame compiling the shaders its stage had only started preparing. That frame now waits until they are ready, for at most 1.5s, and
- 20.10.0v20.10.02026-10-20 · 2026-10-20 01:27 UTC
查看更新说明
Highlights: a game no longer stutters the first time an effect, a blend mode or a new kind of object appears. Shaders, pipelines and render targets are now prepared when the object is added, or during
loader.preload(), rather than in the frame that first draws it, where a single first draw was measured at up to 140ms. Dropping a batch of meshes no longer stalls the frame either.Added
- Effects:
SquishEffectbulges a sprite's middle out, or pinches it in, while its top and bottom rows stay where they are. Paired with a change of height it gives squash and stretch that keeps the sprite's volume, and it draws past the sprite's own frame, so the art needs no transparent margin
Fixed
- WebGL: releasing retained geometry no longer stalls the frame, a stutter whenever a scene dropped a batch of meshes. Teardown asked a blocking
is*query about every buffer and vertex array before deleting it, a round trip per object, and now only asks once the context has been lost, the only case where the answer can be no - WebGL: a
ShaderEffecthosted on aMeshis compiled duringloader.preload(), on every call including a later one that brings a level's own effects, rather than on the frame it first draws. Preloading the shader built its standalone program, but a mesh host splices the body into the mesh shader, which is a different program again, so a game that declared every effect in its manifest still stuttered the first time each one appeared - WebGPU: a post effect's pipelines are compiled when the effect is attached (
addPostEffect, assigningshader, or the tone curvesetToneMappingbuilds), off the frame, rather than in the frame that first draws it, a stutter measured at up to 140ms per effect on desktop. Attaching effects when a pool or a stage is built, rather than when the object is shown, gives the compile time to finish - WebGL: a
ShaderEffecthosted on aMeshis also linked when it is attached, so an effect built inline and never preloaded no longer stalls its first draw either. The links are spread over the following frames, so a stage building a pool of effect-carrying meshes does not pay for all of them in one - Effects: switching a disabled effect on no longer creates a render target in that frame. A chain gets its targets for every effect attached to it, enabled or not, when the effect is attached
- Rendering: adding a renderable to the world starts preparing its draw, so one added before it is first shown no longer pays for it on that frame. On WebGPU that is the pipeline for its blend mode, culling and transparency, compiled off the frame, and on both GPU backends an advanced blend mode (
overlay,differenceand the rest) gets its blend shader and offscreen target, a first draw measured at up to 136ms - WebGL and WebGPU: the shader warm-up runs again after a lost context or device is restored. It never did, so every shader went back to compiling on the frame it next drew
- WebGPU: the mip-chain generator is built during
loader.preload()when textures are filtered (textureFilterresolving to"linear"), instead of on the frame the first mipmapped texture is uploaded - Sprite: a video the browser refuses to start, as it does before the user has interacted with the page, no longer logs an uncaught error on every frame. The refusal is handled once, and playback starts on the next click, touch or key press
- WebGL: ETC1 compressed textures no longer draw black. They were uploaded with a call ETC1 does not accept, which left the texture empty; they now upload as ETC2, which reads the same data, or level by level when the browser only supports ETC1
- WebGPU: the smallest mip levels of a compressed texture (the 2×2 and 1×1 levels of a 4×4-block format) are no longer rejected and left empty. Each upload now covers whole blocks, as WebGPU requires
- Tween: a tween stopped while the game updates (replaced by a new one, say) no longer takes one more step and fires its
onCompletein that same f
- Effects:
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
melonJS 2

A modern & fully featured HTML5 game engine
melonJS is an open-source 2D & 3D game engine designed for indie developers — perspective and orthogonal cameras, GPU-accelerated tilemap rendering, post-processing effects, custom shaders, 3D mesh support, polygon-accurate physics, modern Tiled workflows, and high performance. Runs on WebGPU, WebGL 2 or Canvas2D with automatic fallback. The whole engine is ~250 KB minzipped of vanilla JS/TS, with no dependencies and no toolchain lock-in. Built with modern class syntax and bundled with esbuild.
melonJS is licensed under the MIT License and actively maintained by the team at AltByte in Singapore.
Why melonJS
melonJS is designed so you can focus on making games, not on graphics plumbing.
-
Canvas2D-inspired rendering API — If you've used the HTML5 Canvas, you already know melonJS. The rendering API (
save,restore,translate,rotate,setColor,fillRect, ...) follows the same familiar patterns — no render graphs, no shader pipelines, no instruction sets to learn. -
True renderer abstraction — Write your game once, run it on WebGPU, WebGL 2 or Canvas2D with zero code changes. The engine handles all GPU complexity behind a unified API: the default
AUTOmode tries WebGPU first at startup, falls back to WebGL 2 if that is unavailable, and ultimately to Canvas, and the entire feature set renders identically on both GPU backends. -
Complete engine, minimal footprint — Physics, tilemaps, audio, input, cameras, tweens, particles, UI — a full game stack in a single tree-shakeable ES module. No dependency sprawl, no library stitching.
-
Scenes, loaded in one call —
level.load(name)brings an authored scene straight into your world. Tiled is a first-class citizen for 2D — orthogonal, isometric, hexagonal & staggered maps, animated tilesets, collision shapes, object properties, compressed formats, with GPU-accelerated tile rendering on the GPU backends — and glTF / GLB is the equivalent for 3D scenes: author in Blender (or any DCC tool), export a.glb, and the whole scene — meshes, materials, cameras, lights, and node animation — loads under aCamera3d, no per-mesh wiring. Animated models play back through the same animation API as a 2DSprite. -
Batteries included, hackable by design — Get started in minutes with minimal setup. When you need to go deeper: modern class-based architecture, a plugin system for engine extensions, and a clean architecture that's easy to extend without fighting the framework.
About melonJS
melonJS is a fully featured game engine :
Compatibility
- Standalone library (does not rely on anything else, except a HTML5 capable browser)
- Compatible with all major browsers (Chrome, Safari, Firefox, Opera, Edge) and mobile devices
Graphics
- Fast GPU renderers (WebGPU and WebGL 2) for desktop and mobile devices, with fallback to Canvas rendering
- Since 20.0 the aging WebGL 1 path is retired and WebGL 2 is the baseline; devices offering only WebGL 1 fall back to the Canvas renderer under
AUTO - Extensible batcher system for custom rendering pipelines, with backend-neutral vertex formats and draw topologies (declare a layout once, describe it to either GPU backend)
- High DPI resolution & Canvas advanced auto scaling
- Sprite with 9-slice scaling option and frame animation
- Built-in effects such as tinting, masking, and the full set of CSS-style blend modes (normal, none, additive, multiply, screen, exclusion, darken, lighten, overlay, hard-light, color-dodge, color-burn, soft-light, difference) — all of them supported identically on WebGPU, WebGL 2 and Canvas since 20.2
- Standard spritesheet, single and multiple Packed Textures support
- Compressed texture support (DDS, KTX, KTX2, PVR, PKM) with automatic format detection and fallback
- Hardware antialiasing (
antiAlias: true) on both GPU backends — and it survives post-processing, since the offscreen targets a post-effect chain renders into are multisampled to match - Procedural noise textures:
Noise(simplex/perlin/value/cellular, fBm / ridged / ping-pong fractal, domain warp; renderer-free and CPU-samplable for heightmaps, terrain, and spawn jitter) andNoiseTexture2d— bake a noise field into a sprite image, aGradientcolor ramp, or a live-animated tangent-space normal map for lit water and surfaces; unified under a newTexture2dbase alongsideTextureAtlas - 3D mesh rendering with OBJ/MTL model loading, multi-material support, hardware depth testing, and perspective projection via
Camera3d— retained-mode geometry, uploaded to the GPU once and re-drawn from there, so moving, rotating, scaling or re-tinting a mesh costs no vertex upload and no per-vertex CPU work at all - Mipmapped and anisotropically-filtered mesh textures on both GPU backends, so distant and grazing-angle geometry stops shimmering (opt out per mesh with
textureFilter: "nearest"for crisp pixel-art models) - Lighting, in 2D and 3D:
- 2D —
Light2das a first-classRenderable(multiple dynamic lights, radial-gradient falloff, illumination-only mode, procedural rendering viadrawLight), plus optional per-pixel normal-map shading on sprites for 3D-looking dynamic lights - 3D —
Light3ddirectional, point, spot and ambient lights, added to the world likeLight2d(half-Lambert diffuse + ambient fill, quadratic falloff and cone angles on the punctual types, runtime-manipulable for day/night), auto-loaded from a glTF scene's authored suns and lamps - up to 32 simultaneous lights on either path, with light data travelling in a uniform buffer — a static light rig costs zero GPU state changes per frame
- 2D —
- Ground ("blob") shadows for 3D objects — on by default — so characters and props read as standing on the floor instead of hovering. An ellipse matching the caster's own footprint and rotation, shrinking and fading with height above the ground; one extra draw per object, and one for an entire
InstancedMeshscatter regardless of instance count. Shaped by the light rather than always straight down:shadowLightpoints a blob at aLight3dand is re-read every draw, so a day/night cycle carries every shadow with it, whileshadowOffsetslides it out from the caster's feet,shadowStretchlengthens it for a low sun,shadowScalesizes it independently of the object, andshadowGroundNormallays it on a floor that is not level. All of them work on anInstancedMeshtoo, as one value for the whole set. Controllable per object, per glTF scene (level.load(name, { castGroundShadow, shadowLight, … })) or application-wide. Deliberately not shadow mapping: it answers "where is this standing", which is what a 2.5D scene needs - Built-in shader effects (Flash, Outline, Glow, Bloom, ToneMapping, Dissolve, CRT, Hologram, etc.) with multi-pass chaining via
addPostEffect(), plus custom shader support on both GPU backends:ShaderEffectfor per-sprite fragment effects (GLSL and/or WGSL bodies) and complete custom mesh shader programs viamesh.shader(a dual-languageGLShader: GLSL pair and/or WGSL module) - Optional HDR rendering (
hdr: true) — the camera's render targets become half-float, so additive light stops saturating on every write andBloomEffectthresholds on genuinely over-bright pixels instead of merely bright ones, withToneMappingEffectand atoneMappingsetting to compress the result back into display range when your art calls for it. Off by default and additive: nothing is inserted into your effect chain, so turning it on does not restyle a game whose look is built on clipped whites. On WebGPU it goes one step further —hdrOutput: truepresents the frame in the display's own dynamic range rather than clamping it to SDR at the last step - Trail renderable for fading, tapering ribbons behind moving objects (speed lines, sword slashes, magic trails)
- System & Bitmap Text with built-in typewriter effect
- Video sprite playback
Sound
- Web Audio support with 3D spatial audio and stereo panning, plus a procedural tone/noise API. The playback backend began as howler.js and is now a customized derivative vendored into the engine, so this is not a runtime dependency (see
src/audio/backend/LICENSE) - Built-in procedural audio primitives (envelope-shaped oscillators, white/pink/brown noise) for SFX without sample assets
- Direct
AudioContext/ master-gain accessors for custom WebAudio graphs that mix with the engine's master volume / mute
Physics
- Polygon (SAT) based collision algorithm for accurate detection and response
- Fast broad-phase collision detection using spatial partitioning (QuadTree in 2D, Octree in 3D, swapped automatically)
- Raycast, AABB and sphere region queries with precise entry geometry, including 3D raycasts (
raycast3d) - Collision lifecycle hooks on every
Renderable - Collision filtering for optimized automatic collision detection
- Multiple shapes per body for complex hitboxes
- Pluggable
PhysicsAdapterinterface for custom physics via official adapters
Input
- Mouse and Touch device support (with mouse emulation)
- Gamepad support with button and axes binding
- Keyboard event handling with key binding system
- Device motion & accelerometer support
Camera
- Multi-camera support (split-screen, minimaps, multiple viewports)
- Optional perspective camera (
Camera3d) with frustum culling for 2.5D / 3D-projected scenes — drop-in subclass of the default 2D camera - Camera follow with configurable deadzone and frame-rate-independent damping
- Built-in shake, fade, flash, and mask-based transition effects
- Per-camera post-processing pipeline with stackable shader effects and color grading (ColorMatrix)
UI
UIBaseElement/UISpriteElementcontainers for clickable, hoverable and holdable elements with full pointer-event wiringDraggable/DropTargetfor drag-and-drop with configurable overlap or contains-checkUITextButtontext button with hover, press, and key-bind support — built onBitmapText
Scenes
- Load a scene in one call with
level.load(name)— 2D Tiled maps and 3D glTF scenes alike, auto-registered on preload - Tiled map format up to 1.12 built-in support for easy level design
- GPU-accelerated tile rendering for orthogonal maps on the GPU backends (WebGL 2 and WebGPU) — each layer draws as a single quad with no per-tile loop, ~5–8× faster than the legacy CPU renderer on dense maps. Honors animated tiles, flip bits, per-layer opacity/tint/blend, and oversized bottom-aligned tiles; falls back transparently to the CPU renderer on isometric/staggered/hexagonal layers or under the Canvas renderer
- Uncompressed and compressed Plain, Base64, CSV and JSON encoded XML tilemap loading
- Orthogonal, Isometric, Hexagonal (both normal and staggered) and Oblique maps
- Multiple layers with per-layer alpha, tinting and blend modes (multiple background/foreground, collision and Image layers)
- Parallax scrolling via Image layers, with parallax origin support
- Animated and multiple Tileset support, tile sub-rectangles, embedded base64 images
- Native
.aseprite/.asetileset images (Tiled 1.11+ qaseprite plugin workflow), with frame tags auto-mapped to per-tile animations — no PNG export step required - Tileset transparency settings
- Rectangle, Ellipse, Polygon, Polyline and Capsule (round-rect) object shapes
- Tiled Objects with custom properties (string, number, boolean, color, file, object, list/array and class-typed)
- Per-object opacity and visibility
- Concave collision polygons auto-decomposed via earcut triangulation
- Flipped & rotated Tiles
- Dynamic Layer and Object/Group ordering
- Dynamic Entity loading via an extensible object factory registry — register custom handlers for any Tiled class name without modifying engine code
- Shape based Tile collision support
- glTF / GLB 3D scenes — load an authored 3D scene with
level.load(...), the same one call as a Tiled map- The whole scene loads at once — meshes, materials, cameras and lights — viewed under a
Camera3d - Automatically lit by the scene's authored lights — the sun plus any point/spot lamps set up in the authoring tool, each keeping its authored name so
getChildByName("Sun")finds it for runtime tuning - Textured, solid-colored, and vertex-colored materials
- Node animation — walk/idle/sprint characters, spinning pickups, doors, lifts — played through the same
setCurrentAnimation/play/pause/stopAPI as a 2DSprite .glband.gltffiles, with embedded or external buffers & textures- Works with any glTF authoring tool (Blender, Maya, 3ds Max, Cinema 4D, …)
- The whole scene loads at once — meshes, materials, cameras and lights — viewed under a
Assets
- Asynchronous asset loading with progress tracking
- A fully customizable preloader
- Support for images, JSON, TMX/TSX, glTF / GLB 3D scenes,
.aseprite/.asebinary, audio, video, binary and fonts
Core
Applicationclass as the modern entry point — construct it, thenawait app.init()(asynchronous since 20.0, so the WebGPU device can be acquired) — with built-in pause, resume, andfreeze()(hit-stop) primitives- A state manager (to easily manage loading, menu, options, in-game state)
- Tween effects with multiple easing functions (Quadratic, Cubic, Elastic, Bounce, etc.) and Bezier/Catmull-Rom interpolation
- Transition effects
- Pooling support for object recycling
- Particle system with
ParticleEmitter(emission rate, lifetime, velocity, gravity, blend modes), and a reference space so particles can be measured from the emitter, from the world, or from any container — a moving emitter leaves a trail instead of dragging its cloud along, and a Particle Editor to tune effects live and copy the code - EventEmitter based event system
- Persistent data storage (save/load via localStorage)
- Plugin system for extending engine capabilities
Tools integration
melonJS is supporting the below tools and frameworks natively or through our official plugin(s) :
Tools integration and usage with melonJS is documented in our Wiki.
Getting Started
The fastest way to create a new game:
npm create melonjs my-game
cd my-game
npm install
npm run dev
This scaffolds a ready-to-run project with TypeScript, Vite, and the debug plugin. It also works with plain JavaScript — just rename .ts files to .js.
You can also start from the boilerplate directly, or follow the step-by-step Platformer Tutorial.
For more details, check the wiki Details & Usage guide.
Examples
- Platformer (source)
- Isometric Dungeon (source)
- SVG Shapes (source)
- Particle Editor (source) — tune a
ParticleEmitterlive in 2D or 3D, from presets or a random roll, and copy the matching code or JSON - Graphics (source)
- Hello World (source)
- Hello WebGPU (source) — the WebGPU backend in action, with the renderer negotiation surfaced on screen
- Whac-A-Mole (source)
- Compressed Textures (source)
- Aquarium (source) — screen-space water refraction with
renderer.toFrameTexture(): fish swim across a seabed, then a water surface captures the live frame on the GPU and re-samples it through a scrollingNoiseTexture2dflow map (the standard screen-texture / opaque-frame-copy pattern) - Heat Haze (source) —
renderer.toFrameTexture()over a lit scene: normal-mapped tiles under a movingLight2d, distorted by a rising heat-haze that captures and ripples the lit frame - 3D Mesh (source)
- 3D Mesh Material (source)
- AfterBurner Clone (source) —
Camera3d+ 3D Mesh arcade shooter - Earth Defender (source) — a Stardust tribute: a
Camera3dposed from the player's own surface point, blob shadows lying on a sphere throughshadowGroundPoint, mesh-hostedShaderEffectshields, spatial audio and HDR bloom - Ground Shadows (source) — every blob-shadow setting side by side under an orbiting sun, including blobs lying on a slope and a 70-instance scatter that costs one extra draw
- Jungle Rabbit (source) — a low-poly river endless runner: Blender-modelled props through
InstancedMesh, a rigged paddling boat, a procedural valley and shadows laid on its banks - glTF Scene (source) — a Blender-authored, lit 3D scene loaded with
level.load - glTF Animated Model (source) — a rigged character (Kenney Blocky Characters) with node animation, driven by the Sprite-aligned
setCurrentAnimation/play/pause/stopAPI - Trail (source)
- Shader Effects (source)
- Spine (source)
Browse all examples here
Basic Hello World Example
import { Application, Text } from "https://cdn.jsdelivr.net/npm/melonjs/+esm";
// create a new melonJS application
const app = new Application(1218, 562, {
parent: "screen",
scale: "auto",
backgroundColor: "#202020",
});
// initialize it (builds the renderer and appends the canvas)
await app.init();
// set a gray background color
app.world.backgroundColor.parseCSS("#202020");
// add a font text display object
app.world.addChild(new Text(609, 281, {
font: "Arial",
size: 160,
fillStyle: "#FFFFFF",
textBaseline: "middle",
textAlign: "center",
text: "Hello World !",
}));
Simple hello world using melonJS
Note: since version 20.0,
await app.init()is required after constructing theApplication. The WebGPU backend, whichAUTOtries first on browsers that support it, acquires its GPU device asynchronously, so initialization had to become an async step; it resolves without suspending on the WebGL and Canvas backends. On 19.x the call is optional and this example works without it.
Documentation
Plugins
melonJS provides a plugin system allowing to extend the engine capabilities.
Here is the list of official plugins maintained by the melonJS team:
- debug-plugin - a debug panel for inspecting game objects
- tiled-inflate-plugin - enable loading and parsing of zlib, gzip and zstd compressed Tiled maps
- spine-plugin - Spine runtime integration to render Spine skeletal animations, with a dedicated batcher on each renderer (WebGPU, WebGL and Canvas)
- gamepad-plugin - on-screen touch controls (sticks and buttons) reported as a gamepad, so the same bindings cover a real pad and a touch screen
- capacitor-plugin - bridges Capacitor's native lifecycle (pause/resume, hardware back-button, orientation lock, splash) for melonJS games wrapped as iOS / Android apps
If you wish to develop your own plugin, we also provide a plugin template to help you get started.
Physics Adapters
Since 19.5, melonJS exposes a PhysicsAdapter interface so the same game code can run on either the built-in SAT physics (default) or a third-party rigid-body engine, selected via the physic option on Application. Two official adapter packages, maintained by the melonJS team:
- @melonjs/matter-adapter — matter-js integration. Rotational dynamics, constraints (springs / hinges / pins), sleeping bodies, continuous collision detection, raycasts. Showcased by the Matter Platformer and Pool (Matter) examples.
- @melonjs/planck-adapter — planck.js integration (faithful Box2D 2.3.0 port). Native joints, CCD bullet flag, sleeping bodies, native raycasts, per-body gravity scale. Showcased by the Neon Plinko (Planck) example.
See the Migrating to the Physics Adapter API, Switching Physics Adapters, and BuiltinAdapter Quirks wiki pages for migration guides and the per-adapter behaviour table.
Installation
melonJS is distributed as a tree-shakeable ES module with TypeScript declarations included. The published bundle targets ES2022 — transpile it if you need to support older browsers.
Install via npm :
npm install melonjs
Then import what you need in your project :
import { Application, Sprite, loader } from 'melonjs';
Or use it directly via jsDelivr CDN :
<!-- load the ES module bundle of melonJS v20.x -->
<script type="module" src="https://cdn.jsdelivr.net/npm/melonjs@20/+esm"></script>
<!-- omit the version completely to get the latest one -->
<!-- you should NOT use this in production -->
<script type="module" src="https://cdn.jsdelivr.net/npm/melonjs/+esm"></script>
Note: the debug plugin is available separately as
@melonjs/debug-plugin
Supporting older browsers
The published bundle targets ES2022, so it uses private class members and other modern syntax. A browser that predates those cannot parse the file at all — this fails at load, not at the point a feature is used.
To support older browsers, transpile melonJS together with your own code.
The usual trap is that build setups skip node_modules by default, which leaves
melonJS untouched no matter how low you set your target.
With Vite :
// vite.config.js
export default {
build: {
// esbuild downlevels dependencies too, melonjs included
target: "es2020",
},
};
With Babel, make sure melonJS is not excluded :
// webpack.config.js
{
test: /\.js$/,
// the default `exclude: /node_modules/` would skip melonjs
exclude: /node_modules\/(?!melonjs)/,
use: "babel-loader",
}
Transpiling costs some size and speed: private class members become WeakMap
lookups, which are on hot paths in the renderer. Only reach for it if you have
users on browsers that need it.
Building with AI agents
melonJS ships skills — guidance files that teach AI coding assistants the engine's conventions and, more usefully, the mistakes that fail silently rather than raising an error.
They are versioned with the engine and ship inside the package, at
node_modules/melonjs/skills/ — so a copy matching the exact release you are
running is always on disk.
Install them into whatever assistant you use, with one command :
npx skills add https://github.com/melonjs/melonJS/tree/master/packages/melonjs/skills
It detects the assistants in your project and installs one copy of each skill
under .agents/skills/<skill-name>, then symlinks that copy into every
assistant's own directory, so .claude/skills/melonjs points back at
.agents/skills/melonjs and there is nothing to place by hand. It reports the
assistants it covers as it runs (77 of them at the time of writing). Add
-a claude-code -a cursor to target specific ones.
The set includes an AGENTS.md for anything following that convention (Codex,
Cursor, Gemini CLI); GitHub Copilot reads the same content from
.github/copilot-instructions.md. It names the three rules that produce code
which runs and is wrong, and links the API index below — if you already keep an
AGENTS.md, paste its sections into yours.
The command installs from master. To pin to the release you are running,
point it at that tag instead of master in the URL above.
The skills are plain markdown and can be read by any agent, or by a human.
For anything the skills do not cover, the complete API is indexed for agents at llms.txt — every exported class, function and type with a one-line summary and a link to its reference page, regenerated on every docs build.
Community
Join us and get help or share your projects :
Contributing
We welcome contributions! Please read our Contributing Guide before submitting changes or new features.
Sponsors
Support the development of melonJS by becoming a sponsor. Get your logo in our README with a link to your site or become a backer and get your name in the BACKERS list. Any level of support is really appreciated and goes a long way !





