My personal, minimal game template for creating cross-platform raylib games in Luau, with a C++17 host.
- raylib (6.0) and Luau (0.732), built from source and version pinned
- Write your whole game in Luau, with live hot reload while developing
- Cross-platform builds (Linux-x64, Windows-x64 & Web)
- Native code generation on desktop, with an interpreter fallback on web
- Autogenerated raylib bindings and Luau definitions
- Automatic asset packing/loading using a zip pak + virtual filesystem
- Persistent
save/directory on all platforms, IndexedDB backed on web
- Linux or WSL2
git,zip, and Zig 0.16pkg-config, OpenGL, X11, and Wayland development libraries- Emscripten for web builds
curland Python 3 for./build.sh bindgen- Optional: luau-lsp for editor support
Tip
See the system dependencies page for installation commands.
git clone --depth 1 https://github.com/ingur/raylib-starter.git
cd raylib-starter
./build.sh runNote
The first build downloads and compiles raylib and Luau. Later builds are incremental. Build output goes to zig-out/.
Useful commands:
./build.sh dev
./build.sh run release
./build.sh helprun defaults to a debug build. Other build commands default to release.
local rl = raylib
-- top level runs once at boot, the window is already open.
-- define update(), it runs every frame (return true to quit)
function update()
rl.BeginDrawing()
rl.ClearBackground(rl.RAYWHITE)
rl.DrawText("Congrats! You created your first window!", 190, 200, 20, rl.LIGHTGRAY)
rl.EndDrawing()
endgame/main.luauis the entry point- Define
update()to run code every frame - Return
truefromupdate()to quit game/window.luaucontains startup settingsrequire("name")loads and cachesgame/name.luau, nested paths work asrequire("module/path")- Debug builds set the
DEVglobal before_reload()may return a table of plain dataafter_reload(state)receives that table after a successful reload- Reload state holds booleans, numbers, strings, vectors, buffers and nested tables. Anything else is reported and the state is dropped
- Hot reload does not release GPU or audio resources. Unload them in
before_reload() - Reloads run
main.luauagain, so guard one-time setup likerl.InitAudioDevice()withrl.IsAudioDeviceReady() rl.SetShapesTexturesurvives reloads, reset it withrl.SetShapesTexture(tex, rl.Rectangle())before unloading the texture
Use the global table through a local name:
local rl = raylibVector2andVector3use Luau's nativevectortypeColoris a packed0xRRGGBBAAnumber- raymath is bound, so
rl.MatrixTranslateandrl.QuaternionSlerphandle 3D transforms - Use the
vectorlibrary and operators for vector maths, they allocate nothing - Other supported raylib structs use userdata with field access
- Resource handles have no script constructor
- Enums and colors are flat values such as
rl.KEY_SPACEandrl.RAYWHITE rl.LoadShadertakes no nil, pass""to keep raylib's default shader for that slot- Functions that cannot be exposed safely are absent
./build.sh bindgenregenerates the bindings and the unsupported report
The template does not bind every raylib function.
assets/andgame/are packed intoassets.pak, which is a zip file- Reads see loose files first, then the pak, so a loose file overrides a packed one
- Writes go wherever you point them, raylib's own rules apply
save/is the one directory the web build keeps, so put saves there to stay portablerl.LoadFileTextreturns a string,rl.LoadFileDatareturns a buffer- Both loaders raise an error when the file is missing
rl.SaveFileTextandrl.SaveFileDatacreate parent directories on the way- Exports and
rl.TakeScreenshotfollow the same rules, except.bmpand.qoiimages, which write straight to disk and never persist on web rl.FileExists,rl.DirectoryExists,rl.GetFileLength,rl.GetFileModTimeandrl.LoadDirectoryFilesanswer for loose files and the pak- Everything else raylib offers for files is bound as raylib defines it
rl.ChangeDirectorymoves loose reads, writes and the dev watcher with it, capturerl.GetWorkingDirectory()and restore it- raylib 6.0's
FileMovedeletes the source when the copy fails, userl.FileCopythenrl.FileRemove
rl.LoadModelandrl.LoadModelAnimationsread through the virtual filesystemrl.LoadModelAnimationsreturns a table of handles, pass it back torl.UnloadModelAnimations- glTF, GLB and IQM load fully from the pak, including their animations
- An obj that declares materials must stay a loose file, raylib opens the mtl and its textures itself
rl.SetMaterialTexturehands the texture to the material,rl.UnloadMaterialfrees it, do not alsorl.UnloadTextureit- Textures a model file loads stay allocated until exit, scripts cannot reach them to unload
- Debug builds abort on m3d, raylib's bundled parser reads unaligned and Zig traps that
Every case is listed with its reason in the report at the end of src/bind/raylib_bind.cpp.
- The audio stream callback and the mixed processors. They run on raylib's audio thread and one Luau VM is not thread safe
SetTraceLogCallback, a variadic C callback, and the four file callback setters, which are how the virtual filesystem is installed- The window lifecycle and the automation events. The host owns the window, and recording needs a list pointer raylib keeps past the call
- Calls taking an array of values, the splines and the triangle strips. Loop the bound single element calls,
DrawSplineSegment*,DrawLineV,DrawTriangle DrawMeshInstanced, which has no single element equivalent- Writing into a raylib buffer,
UpdateAudioStream,UpdateSound,UpdateMeshBuffer,UpdateTextureRec,SetShaderValueV. Scripts cannot synthesise audio or stream vertices - Calls returning an owning pointer,
LoadImageColorsandLoadCodepointsamong them. Luau'sbuffer,stringandutf8cover the usual reasons to want them - The
Text*helpers that build a new string,TextFormat,TextSplit,TextToUpperand the rest. Luau'sstringandutf8do this natively, the ones that only read, such asTextLengthandTextSubtext, are bound - The compression, encoding and hashing helpers, which Luau has no equivalent for
- VS Code uses the committed
.vscode/settings.json - Neovim uses the committed
.nvim.luaaftervim.o.exrc = true - Other editors can pass
--definitions:@raylib=<repo>/types/raylib.d.luauto luau-lsp - Set luau-lsp
platform.typetostandard - Check scripts without an editor with
luau-lsp analyze --definitions=types/raylib.d.luau game/*.luau
- Change the binary name in
build.zig - Change dependency versions in
build.zig.zon - Run
./build.sh bindgenafter changing the raylib version
