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.
import { initWindow, windowShouldClose, beginDrawing,
endDrawing, clearBackground, drawText, Colors } from "bloom";
initWindow(800, 450, "My Game");
while (!windowShouldClose()) {
beginDrawing();
clearBackground(Colors.RAYWHITE);
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 "bloom";
initWindow(800, 450, "My Game");
runGame((dt) => {
clearBackground(Colors.RAYWHITE);
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.
| Module | Import | Description |
|---|---|---|
| Core | bloom/core |
Window, game loop, input, timing |
| Shapes | bloom/shapes |
2D drawing + collision detection |
| Textures | bloom/textures |
Image loading, sprite batching |
| Text | bloom/text |
TTF/OTF font loading and rendering |
| Audio | bloom/audio |
Sound effects + music streaming |
| Models | bloom/models |
3D model loading (glTF, OBJ), skeletal animation |
| Math | bloom/math |
Vectors, matrices, quaternions, easing |
| Physics | bloom/physics |
Jolt-backed rigid + soft bodies, character, vehicles (docs) |
| 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 |
| 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 + XAudio2
linux/ Vulkan/OpenGL + X11/Wayland + PulseAudio
android/ Vulkan/OpenGL ES + NativeActivity + AAudio
web/ WebGPU/WebGL + Canvas + Web Audio (WASM)
examples/
pong/ Complete working example (~170 lines)
Plain interfaces, no classes:
interface Vec2 { x: number; y: number }
interface Vec3 { x: number; y: number; z: number }
interface Color { r: number; g: number; b: number; a: number }
interface Rect { x: number; y: number; width: number; height: number }
interface Camera2D { offset: Vec2; target: Vec2; rotation: number; zoom: number }
interface Camera3D { position: Vec3; target: Vec3; up: Vec3; fovy: number; projection: number }
interface Texture { handle: number; width: number; height: number }
interface Sound { handle: number }
interface Model { handle: number }Launch your game in fullscreen by passing true as the fourth argument to initWindow:
initWindow(800, 450, "My Game", true); // launches fullscreen
initWindow(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 "bloom";
const character = loadModel("assets/models/character.glb");
const anim = 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.