Native games from TypeScript.
Write TypeScript. Ship native games — and now the web too. Bloom compiles your game to Metal, DirectX 12, Vulkan, OpenGL, and WebGPU — one codebase for every platform.
Inspired by raylib. Bloom models its public API on raylib's — in our view one of the best API designs in gamedev. Bloom is an independent implementation, not a port — how Bloom relates to raylib »
npm install @bloomengine/engineOr with your preferred package manager:
bun add @bloomengine/engine
pnpm add @bloomengine/engine
yarn add @bloomengine/engineThe npm package ships the TypeScript API alongside the engine's Rust sources and the bundled JoltPhysics C++ shim, so a single install is enough — there's no separate native download step.
You'll also need:
- Perry — the TypeScript AOT compiler that turns your game into a native binary or WASM module. It also drives the engine's native build.
- Rust toolchain (rustup.rs) — Perry invokes Cargo to compile the engine's platform crate the first time you build for each target.
- For web builds only: wasm-pack (
cargo install wasm-pack).
import{initWindow,windowShouldClose,beginDrawing,endDrawing,clearBackground,drawText,Colors}from"@bloomengine/engine";initWindow(800,450,"My Game");while(!windowShouldClose()){beginDrawing();clearBackground(Colors.SNOW);drawText("Hello, Bloom!",190,200,20,Colors.DARKGRAY);endDrawing();}Use runGame() for code that works on both native and web:
import{initWindow,runGame,clearBackground,drawText,Colors}from"@bloomengine/engine";initWindow(800,450,"My Game");runGame((dt)=>{clearBackground(Colors.SNOW);drawText("Hello, Bloom!",190,200,20,Colors.DARKGRAY);});Build for web:
./native/web/build.sh main.ts
cd dist/web && python3 -m http.server 8080- Simple API — Functions, not classes. The entire API fits on a cheatsheet. (design rationale)
- True native — Compiles to Metal, DirectX 12, Vulkan, OpenGL, and WebGPU via wgpu.
- Ship everywhere — macOS, Windows, Linux, iOS, tvOS, Android, and Web from one codebase.
- Unified 2D/3D — Shapes, textures, text, 3D models, and audio in one engine.
- Zero magic — Explicit game loops, no hidden framework overhead.
Bloom's public API is heavily inspired by raylib.
raylib's API is, in our opinion, one of the best in the gamedev space — a flat library
of plain functions, no classes, small enough to learn from a cheatsheet — so we model
ours on it. You'll recognize the shape immediately: initWindow, beginDrawing,
clearBackground, drawText, and modules named core / shapes / textures / text /
audio / models.
That's where the relationship ends. Bloom's implementation is entirely independent — it does not link against, embed, or call raylib. Bloom compiles TypeScript directly to native code via Perry, our LLVM-based AOT compiler, and renders through wgpu (Metal, DirectX 12, Vulkan, OpenGL, WebGPU). It is not a port or a binding — just an engine that admires raylib's API design. Thanks to Ramon Santamaria (@raysan5) and the raylib community for setting the bar. (full design rationale)
| Module | Import | Description |
|---|---|---|
| Core | @bloomengine/engine/core | Window, game loop, input, timing |
| Shapes | @bloomengine/engine/shapes | 2D drawing + collision detection |
| Textures | @bloomengine/engine/textures | Image loading, sprite batching |
| Text | @bloomengine/engine/text | TTF/OTF font loading and rendering |
| Audio | @bloomengine/engine/audio | Sound effects + music streaming |
| Models | @bloomengine/engine/models | 3D model loading (glTF, OBJ), skeletal animation |
| Math | @bloomengine/engine/math | Vectors, matrices, quaternions, easing |
| Scene | @bloomengine/engine/scene | Retained scene graph, frame callbacks, lighting |
| Physics | @bloomengine/engine/physics | Jolt-backed rigid + soft bodies, character, vehicles (docs) |
| VFX | @bloomengine/engine/vfx | GPU particle systems + decals |
| World | @bloomengine/engine/world | .world.json loading, validation, instantiation (docs) |
| Mobile | @bloomengine/engine/mobile | Virtual joystick/buttons, touch-input helpers |
| Platform | Graphics API | Input |
|---|---|---|
| macOS | Metal | Keyboard + mouse |
| Windows | DirectX 12 | Keyboard + mouse |
| Linux | Vulkan / OpenGL | Keyboard + mouse |
| iOS | Metal | Touch + gamepad |
| tvOS | Metal | Siri Remote + gamepad |
| watchOS | SwiftUI Canvas (2D) + SceneKit (3D) | Digital Crown + taps (docs) |
| Android | Vulkan / OpenGL ES | Touch + gamepad |
| Web | WebGPU / WebGL | Keyboard + mouse + touch + gamepad |
src/ TypeScript API
core/ Window, input, game loop
shapes/ 2D shapes + collision
textures/ Image loading, sprites
text/ Font rendering
audio/ Sound + music
models/ 3D models
math/ Vectors, matrices, easing
native/ Rust implementations
shared/ Cross-platform core (wgpu, fontdue, gltf)
macos/ Metal + AppKit + Core Audio
ios/ Metal + UIKit + Core Audio
tvos/ Metal + UIKit + GCController
windows/ DirectX 12 + Win32 + WASAPI
linux/ Vulkan/OpenGL + X11 + ALSA
android/ Vulkan/OpenGL ES + NativeActivity + AAudio
web/ WebGPU/WebGL + Canvas + Web Audio (WASM)
examples/
pong/ Complete working example (~170 lines)
Plain interfaces, no classes:
interfaceVec2{x: number;y: number}interfaceVec3{x: number;y: number;z: number}interfaceColor{r: number;g: number;b: number;a: number}interfaceRect{x: number;y: number;width: number;height: number}interfaceCamera2D{offset: Vec2;target: Vec2;rotation: number;zoom: number}interfaceCamera3D{position: Vec3;target: Vec3;up: Vec3;fovy: number;projection: number}interfaceTexture{handle: number;width: number;height: number}interfaceSound{handle: number}interfaceModel{handle: number}Launch your game in fullscreen by passing true as the fourth argument to initWindow:
initWindow(800,450,"My Game",true);// launches fullscreeninitWindow(800,450,"My Game");// windowed (default)Toggle fullscreen at runtime:
if(isKeyPressed(Key.F11)){toggleFullscreen();}Fullscreen is supported on macOS (native AppKit fullscreen), Windows (borderless fullscreen), and Linux (EWMH/X11). The width and height you pass are used as the windowed dimensions when exiting fullscreen.
Bloom supports GPU-accelerated skeletal animation via glTF/GLB models. The pipeline uses 4-bone linear blend skinning with a 128-joint uniform buffer, running entirely on the GPU.
import{loadModel,loadModelAnimation,updateModelAnimation,drawModel,getTime,Colors}from"@bloomengine/engine";constcharacter=loadModel("assets/models/character.glb");constanim=loadModelAnimation("assets/models/character.glb");// In your game loop:updateModelAnimation(anim,0,getTime(),1.0,0,0,0);drawModel(character,{x: 0,y: 0,z: 0},1.0,Colors.WHITE);Key functions:
loadModel(path)-- loads GLB with skin data (JOINTS_0, WEIGHTS_0)loadModelAnimation(path)-- loads skeleton + animation channels from GLBupdateModelAnimation(handle, animIndex, time, scale, px, py, pz)-- samples animation, computes joint matricesdrawModel(model, position, scale, tint)-- renders with GPU skinning
For the full pipeline (Blender export, pitfalls, architecture), see docs/skeletal-animation.md.
Bloom Jump — our first shipped game and a proof point for the engine. A free retro pixel platformer with five hand-crafted levels, 60 FPS, and an original chiptune soundtrack, built entirely with Bloom from one TypeScript codebase running natively on every target.
- App Store — iOS, macOS, tvOS, and visionOS
- App Store (watchOS) — Apple Watch
- Google Play — Android