2D + 3D Game Engine

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.

Ruzit logo

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

FlagPulls inUnlocks
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:

BinaryRole
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

ruzit init [path]

Scaffold a new project. Path defaults to the current directory. Fetches the latest types.d.luau from master so autocomplete tracks the running engine.

ruzit initpackage [path]

Scaffold a Managed package folder (ManagedInfo.toml + init.luau) you can drop next to a project as a DLC.

ruzit scaffold [path]

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 = "./".

ruzit test [path]

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

ruzit build [path] [-o out]

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.

ruzit package [folder] [-o out]

Package a single Managed folder into <id>.scripts.managed + <id>.assets.managed. Used to publish standalone Workshop / mod packages.

ruzit fetch-deps / refresh-types

Pull the Steam SDK redistributable (fetch-deps) or re-download types.d.luau (refresh-types) from configured URLs. See CLI.

ruzit update

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:

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

Where to next? Start with Primitives, the math types every other API is built on. Then work your way through the sidebar.