Groot is a fast-iteration 2D/3D game engine powered by wgpu (modern GPU rendering)
with GoScript — an embeddable Go-syntax scripting VM written in pure Rust.
Game logic lives in .gos files under assets/scripts/ and hot-reloads on save;
visuals and prefab hierarchies live in RON asset files under assets/prefabs/ and assets/scenes/.
Groot is data-driven: scripts own behavior and data; the host engine owns representation and rendering. Visuals are declared as RON prefab data.
assets/scenes/*.scene.ron ──► hecs ECS (3D Meshes, Lights, Sprites, UI)
assets/prefabs/*.prefab.ron ──► RonAssetWatcher (Visual Hot Reloading)
assets/scripts/*.gos ──► GoScript VM (Logic Hot Reloading) ──► Entity Data
▲ │
└── groot.* host bindings ──────────┘
assets/config.ron— project config & render settings.assets/prefabs/*.prefab.ron— 2D/3D prefabs (sprites, text, 3D PBR meshes, lights, colliders, parent-child hierarchies).assets/scenes/*.scene.ron— scene layout, environment settings, cameras, entity initializers.src/main.rs— initializes winit window and wgpu render context, runs main event loop.src/platform/— platform abstraction layer (desktop event loop, Android entry).src/render/— pure wgpu rendering engine (3D meshes, 2D sprites, text, gizmos).src/ecs/— hecs-based ECS components and queries.src/assets/— asset loading, encryption, archive packing (see Asset Security).src/script/— GoScript VM host, input tracking, script execution.
Groot ships a built-in asset protection pipeline that prevents plaintext inspection of scripts, prefabs, and scenes in release builds.
All assets are encrypted with a ChaCha20-inspired XOR stream cipher keyed
by a compile-time secret and an asset-path nonce. A CRC32 integrity header
detects tampering. The default key is baked in at compile time; production
builds can override it at compile time by setting the GROOT_ENCRYPTION_KEY
environment variable before building:
GROOT_ENCRYPTION_KEY="MyStrongKey32BytesExactly______!" cargo build --release| Function | Description |
|---|---|
encrypt_asset(path, data) |
Encrypt bytes with a path-derived nonce |
decrypt_asset(path, data) |
Decrypt bytes; falls back to raw if not encrypted (debug) |
minify_goscript(source) |
Strip // and /* */ comments + blank lines from .gos |
Assets can be packed into a single encrypted .gpak binary archive,
similar to Unreal .pak or Unity asset bundles.
Archive format:
[GPAK] 4 bytes magic
[u16] 2 bytes version
[u32] 4 bytes entry count
per entry:
[u16] path length
[N] UTF-8 relative path
[u32] data length
[M] encrypted payload
Pack via the CLI (see CLI):
groot pack assets/ assets.gpakOpen a .gpak from Rust:
let archive = GpakArchive::open("assets.gpak")?;
let bytes = archive.entries.get("scripts/player.gos");| Build mode | Strategy |
|---|---|
cargo run (debug, desktop) |
Raw .gos read from disk — hot-reload preserved |
cargo build --release |
Decrypt + minify in memory via HotReloadEngine::from_str — no temp file |
| Android | Same as release — fully in-memory |
When a physical path is required (debug hot-reload watcher), prepare_script_path
writes a minified, hashed .goc file to /tmp/.groot_cache/ with 0600
permissions and registers a panic hook that deletes the cache on process exit.
# Install the CLI (binary is now `groot`)
cargo install --path . # installs `groot` to ~/.cargo/bin
# Scaffold a new project folder
groot new my-game # or: cargo run --bin groot -- new my-game
# Run the current Groot game
groot run # or: cargo run --bin groot -- run
# Build a release bundle
groot build # or: cargo run --bin groot -- build
# Pack and encrypt the assets directory into a .gpak archive
groot pack assets/ assets.gpak
# Scaffold prefabs, scripts, and scenes
groot generate prefab enemy
groot generate script player
groot generate scene arena # alias: groot g <type> <name>
# Diagnose toolchain (Rust, cargo-apk, Android SDK/NDK, Vulkan)
groot doctor
# Shell completions
groot completions bash # bash|zsh|fish
# or: groot completions zsh > ~/.zsh/completions/_grootDesktop and Android builds share a single --target flag on run and build (defaults to desktop):
# Desktop (current host)
groot run
groot build --target desktop
# Android APK (requires rustup target aarch64-linux-android + cargo-apk)
groot run --target android # cargo apk run (deploys to a connected device via adb)
groot build --target android # cargo apk build --releaseOn Android, groot run --target android auto-detects connected devices with
adb; if several are present it lists them and lets you pick one, or you can
pass --device <serial> (or --device <index>) to select directly.
Assets (assets/) are compiled into the APK using rust-embed with the
debug-embed feature, so games work on-device in both debug and release
builds. On desktop debug builds, asset files are still read from disk first
so .gos and .prefab.ron edits hot-reload while developing.
rustup target add aarch64-linux-androidcargo install cargo-apk- Android SDK with platform
android-34(or settarget_sdk_version/min_sdk_versionunder[package.metadata.android.sdk]inCargo.toml)
cargo runThe Flappy Groot demo starts up: a neon 2D side-scroller where you flap a bird through pipe gaps, avoiding solid ground and ceiling. Save any .gos or .prefab.ron file to see live hot reloading!
2D sprites are drawn back-to-front by their layer field (background → pipes → ground/ceiling → bird) and scaled to their world-space size from the prefab's size. Sprite textures (bird, pipes, ground, ceiling, grid) are generated procedurally at startup and cached as PNG bind groups.
type Player struct { Speed float64 }
var self = Player{Speed: 5.0}
func OnUpdate(dt float64) {
var pos = groot.GetSelfPosition()
var moveX = groot.GetAxis("Horizontal")
var moveZ = -groot.GetAxis("Vertical")
groot.SetSelfPosition(pos[0] + moveX*self.Speed*dt, pos[1], pos[2] + moveZ*self.Speed*dt)
groot.SetSelfCollider(1.0, 1.0, 1.0)
groot.Log("Hello from Groot 3D GoScript!")
}winit0.29 - Cross-platform windowing and inputwgpu0.19 - Modern GPU rendering (Vulkan/Metal/DX12)glam0.27 - Fast 3D/2D math libraryhecs0.10 - Minimalist archetype ECSbytemuck1.14 - Safe casting for GPU bufferspollster0.3 - Block on async operationsron0.8 - Rusty Object Notation for assetsserde1 - Serialization frameworkgoscript(git:github.com/johnesleyer/goscript) - GoScript VM
Engineering deep-dives from the engine's development, including the Android bring-up: