Ruzit
A Luau-scripted 2D and 3D game engine written in Rust,
similar in spirit to love2d, but with Luau, a Roblox
style API, real wgpu rendering, and a single
ruzit build command that bundles your project
into a self-contained launcher.
What is Ruzit?
Ruzit is a single-file game engine you script in Luau. The engine itself is a Rust binary that ships every system a game needs, 3D renderer, 2D overlay primitives, audio, networking, asset loading, packaging, behind a Lua API designed to feel familiar if you've used Roblox Studio.
Both ends of the spectrum are first-class: build a flat 2D platformer
with GUI.Basic primitives, a fully-3D scene with
Renderable parts and models, or any mix,
2D primitives always render on top of the
3D scene. Same idea as LÖVE / love2d in
that the engine stays out of your way; same idea as Roblox in that
every API is typed and discoverable from one
types.d.luau file.
You write your game as .luau files in a folder. The
engine boots them as the entry point of a virtual scene graph; the
same source runs unchanged inside a packaged executable shipped to
players.
Luau scripting
Full Luau
Lua with autocomplete and type-checking
via types.d.luau.
3D rendering
wgpu-backed renderer. Vulkan / Metal / DX12 transparently; user shaders authored in WGSL with engine preludes injected.
2D primitives
Squares / circles / triangles / images / text rendered as a screen-space overlay. Same shader hook system as 3D parts, so every primitive can pick up a custom fragment shader.
One-command shipping
ruzit build packages scripts, assets, and a
launcher into a single executable. Cross-platform launchers
on Windows / Linux / macOS.
DLC & Packages
Drop a folder with a ManagedInfo.toml next to
your project; it ships as a separate, encrypted
.managed bundle the launcher mounts at runtime.
Steam built in
Achievements, leaderboards, Workshop, lobby, rich presence,
launch parameters, all via import("Steam").
VR & controllers
Native gamepad support via gilrs and a typed VR API
(import("VR")) with controllers, haptics, and
head-pose tracking.
Build flags
Ruzit's runtime crate ships with three optional Cargo features.
The default ruzit CLI build enables only the bare
minimum; opt in to what your game uses by passing
--features at compile time (or by listing them in your
build.toml's features array when building
a launcher).
| Flag | Pulls in | Unlocks |
|---|---|---|
voice |
cpal + opus |
Microphone capture. Required for
SoundByte.GetVoiceChannel
and the legacy Voice
module. Check SoundByte.VoiceFlagEnabled at
runtime before allocating channels.
|
steam |
steamworks + steamworks-sys |
Steam P2P, lobbies, achievements, Workshop, Steam Cloud.
Required for the
Steam module.
Steam runtime initialization happens at launcher
start; if absent, Steam calls error early.
|
vr |
indite (OpenXR ↔ WGPU) |
VR pose tracking, controller input, and the legacy
VR module. Without
this flag, the
VirtualReality
import still loads but
VirtualReality.HasVrFlag is
false and LinkCamera() is a
no-op — gate any VR code on
HasVrFlag for clean degradation. Requires
rustc ≥ 1.92 (indite pulls in
wgpu 28).
|
Building from source with everything:
cargo build --release --features voice,steam
Or for a packaged game via the CLI:
ruzit build --features voice
Features that aren't compiled in fail loudly at the import boundary
with a clear message (e.g. "voice feature is disabled in this
build"), so you can ship a slimmer runtime without any
silent-no-op surprises.
Quick Start
Scaffold a new project and run it in a handful of commands:
# Drop the ruzit + ruzitrun binaries side-by-side somewhere on your PATH, then:
ruzit init MyGame
cd MyGame
ruzit scaffold # regenerate .luaurc aliases (run after adding/removing packages)
ruzit test
ruzit init writes a starter project to the named folder
(build.toml, Main.luau, an empty
assets/, a .luaurc with
Game = "./", and .vscode/settings.json)
and fetches a fresh types.d.luau from the master repo
so autocomplete is always current. The scaffolded
Main.luau opens a window, draws a cube, and exits on
close — replace it with your game.
ruzit scaffold walks the project tree for any folder
containing a ManagedInfo.toml and writes one
.luaurc alias per package, pointing at the entry
file. Run it whenever you add, remove, or rename a package so
require("@PkgId") autocompletes in the editor.
The two binaries
Ruzit ships as a Cargo workspace producing two distinct binaries that you install side-by-side:
| Binary | Role |
|---|---|
ruzit |
Dev tool. Tiny binary (~3.5 MB) that handles
scaffolding (init /
initpackage), running tests
(test spawns the runtime), packaging
(build / package), and
tooling (fetch-deps,
refresh-types,
update). Doesn't link the engine. |
ruzitrun |
The runtime engine. Big binary (~22 MB) with
wgpu, mlua, all the libraries. Doubles as the
launcher template that
ruzit build appends a project trailer
to when shipping. Normally not invoked directly. |
See CLI for the full command reference.
The eight commands
Scaffold a new project. Path defaults to the current
directory. Fetches the latest types.d.luau
from master so autocomplete tracks the
running engine.
Scaffold a Managed package folder
(ManagedInfo.toml + init.luau)
you can drop next to a project as a DLC.
Regenerate .luaurc aliases by walking the
project tree. Every folder with a
ManagedInfo.toml becomes an alias
(<ID> = "./path/to/folder") so
require("@PkgId") resolves to the entry file
inside, and require("@PkgId/sub") picks up
submodules. Always re-emits Game = "./".
Run a project from source. The CLI spawns
ruzitrun --run <path> as a subprocess
so the engine starts in dev mode (no packaging, no
encryption, edits hit the next test).
Produce Generated/<exe> +
Generated/Managed/*.managed. The launcher is
a copy of the ruzitrun binary with the
metadata appended as a trailer; ship the whole
Generated/ folder to players.
Package a single Managed folder into
<id>.scripts.managed +
<id>.assets.managed. Used to publish
standalone Workshop / mod packages.
Pull the Steam SDK redistributable
(fetch-deps) or re-download
types.d.luau (refresh-types)
from configured URLs. See CLI.
Refresh the local ruzitrun binary from the
configured release host (defaults to the project's
GitHub Releases). The ruzit CLI itself is
left alone, swap it manually if you want a newer CLI.
Hello world
The smallest meaningful Ruzit program:
-- Main.luau
local Window = import("Window")
local Renderable = import("Renderable")
local Vector = import("Primitives").Vector
local win = Window.Open({ Title = "Hello, Ruzit", Width = 1280, Height = 720 })
local cube = Renderable.BasePart("Cube")
cube.Size = Vector.new(2, 2, 2)
import is the global library accessor, it returns a
typed handle to one of the engine subsystems. The Luau LSP follows the
return type through every import("X") call, so hover docs
and autocomplete work everywhere.
Architecture
Three layers, top to bottom:
| Layer | What lives here |
|---|---|
.luau game code |
Your scripts. Run inside a single Luau VM. The entry
script boots; Process.Heartbeat drives
per-frame logic.
|
import("X") libraries |
Renderable, GUI, Asset, Window, Net, SFX, Steam, VR, Gamepad, Actor, IO, Process, Signal, Primitives, Serde, Voice, Managed. |
| Rust engine | wgpu, winit, cpal/rodio, mlua, fbxcel-dom, steamworks, gilrs, wired up so the Luau side only sees the abstractions. |
A packaged game is the same engine binary with a JSON trailer pointing
at Managed/*.managed. The launcher detects the trailer at
startup and boots the bundled scripts instead of the CLI dispatcher,
so the engine binary and the user's launcher are literally the same
file.