SeedEngine is a custom Rust game engine developed alongside a solo indie game project. Every system ships because the game needs it, not because the spec says so.
Overview
What SeedEngine is, what it can do, and who it is for.
Language
Rust stable
GPU API
Vulkan 1.3
License
MPL-2.0
Since
Feb 2026
What it is
SeedEngine is a custom Cargo workspace game engine written in Rust. It is purpose-built for a single game project and developed as both projects evolve together — the engine only grows in directions the game actually exercises.
The workspace is structured as a set of focused crates with strictly enforced dependency direction. The renderer targets Vulkan 1.3 via ash. Shaders are written in HLSL and compiled to SPIR-V with dxc. The render pipeline uses MBOIT transparency, AgX tonemapping, and an NPR shading model aimed at Japanese animation aesthetics.
Core systems
ECS
Archetype / SoA layout with rayon parallel scheduling. Fixed-step accumulation driven by EngineClock.
NavMesh tiles, pulse spatial queries, SLAM occupancy maps, raycasting against geometry and entities.
AI
MAH / HTN / BT / HFSM decision layers. FlowField crowd navigation. Shared perception layer across agents.
Virtual File System
.rsgd game archives, .rssd save files with diff-chain compression. ChaCha20-Poly1305 encryption.
Scripting
Rhai runtime with engine bindings. Lua has been fully removed and will not return.
Who it is for
SeedEngine is open source under MPL-2.0, but it is not a general-purpose engine. There is no editor, no asset pipeline GUI, and no plugin system. If you are looking for a complete engine, Bevy or Godot will serve you better.
What it offers is a clear, well-documented Rust codebase that demonstrates how to wire together Vulkan, ECS, physics, audio, and AI into a working game loop — with every architectural decision made under real production pressure.
Create your first project
Add a new crate to the workspace and run a minimal engine loop with a camera, ECS world, and Vulkan renderer.
SeedEngine targets Windows with an RTX-class GPU (Vulkan 1.3 required). Vulkan drivers, the Vulkan SDK, and dxc must be installed. Rust stable toolchain is sufficient.
1
Clone the repository
The workspace root is SeedEngine/. All engine crates live under crates/.
git clone https://github.com/kuriminanya/SeedEngine.git
cd SeedEngine
2
Add a new crate to the workspace
Your game lives as a crate inside the same Cargo workspace. Create it alongside the engine crates.
cargo new --bin crates/hello_engine
Then register it in the root Cargo.toml:
# Cargo.toml (workspace root)
[workspace]
members = [
"crates/clock",
"crates/ecs",
# ... other crates ...
"crates/hello_engine", # <-- add this
]
3
Declare dependencies
In crates/hello_engine/Cargo.toml, depend on whichever engine crates you need:
SeedEngine uses a two-thread architecture: winit stays on the main thread (OS requirement) and sends events over a channel; the game thread owns the ECS world and drives the renderer.
// The core pattern — see the full hello_engine example for details.let (renderer_tx, renderer_rx) = std::sync::mpsc::sync_channel::<Renderer>(1);
let (event_tx, event_rx) = std::sync::mpsc::sync_channel::<SeedEvent>(256);
let (ctrl_tx, ctrl_rx) = std::sync::mpsc::sync_channel::<ControlMsg>(8);
// game thread: wait for renderer, then run the loop
std::thread::spawn(move || {
let mut renderer = renderer_rx.recv().unwrap();
let mut clock = EngineClock::new(DEFAULT_FIXED_STEP);
let mut world = setup_world();
loop {
for event in event_rx.try_iter() { /* handle input / resize / quit */ }
clock.tick();
for _ in 0..clock.fixed_tick_count() { world.run(&ctx); }
let scene = extract_render_scene(&world);
submit_frame(&mut renderer, &scene);
}
});
// main thread: run winit event loop
graphics::frontend::winit::init::run(renderer_tx, event_tx, ctrl_rx, window_mode, "hello_engine")?;
5
Register a camera and spawn entities
Insert a SceneManager resource, spawn a camera entity, and set it as active. The renderer reads this every frame to compute the view-projection matrix.
A debug build is fastest for iteration. Set RUST_LOG=info to see the ECS heartbeat log.
set RUST_LOG=info
cargo run -p hello_engine
A black window is the expected output — no meshes are loaded in the minimal example. Add RenderObjects to the RenderScene to see geometry.
Full example
The complete hello_engine example (including input mapping, audio init, aspect-ratio sync, and all render passes) is available in the repository at examples/hello_engine/src/main.rs. It mirrors the two-thread architecture of the main game project without any game-specific content.
About SeedEngine
Built by 賽菲莉西亞之花
Version
0.1.0
Language
Rust stable
License
MPL-2.0
Last updated
—
What is this
SeedEngine is a custom Rust game engine developed by a solo indie studio. It exists for one reason: to ship a game. Every system in the engine is there because the game needs it.
The engine is open source under MPL-2.0. You are free to use, fork, and learn from the code.
Aesthetic direction
The rendering pipeline is built around Japanese semi-realistic animation aesthetics — the atmospheric restraint of Makoto Shinkai films crossed with the precision of NieR. NPR shading goals: SDF face shadow, Kajiya-Kay hair, back-face outline, AgX tonemapping, MBOIT transparency.
Delta time, fixed tick accumulator, render alpha · crates/clock
API overview
EngineClockstructMain engine clock — call tick() once per frame.▾
Tracks scaled delta time, fixed-step accumulation, render alpha, and both game/real time totals. Create once; call tick() at the top of each frame loop.
use clock::EngineClock;
let mut clock = EngineClock::new(1.0 / 60.0);
// in game loop: clock.tick(); let dt = clock.delta(); // scaled delta (0 when paused) let alpha = clock.alpha(); // render interpolation [0, 1]
EngineClock::newfnCreate a clock with a fixed step in seconds.▾
Pass the desired fixed-simulation step, e.g. 1.0 / 60.0 for 60 Hz physics.
pub fn new(fixed_step: f64) -> Self
EngineClock::tickfnAdvance time state — call once per frame.▾
Reads the wall clock, computes raw delta (clamped to 250 ms), and updates all derived values.
pub fn tick(&mut self)
EngineClock::deltafnScaled delta time for this frame.▾
Returns zero when paused. Multiply movement and physics by this value.
pub fn delta(&self) -> f32
EngineClock::fixed_tick_countfnHow many fixed ticks to run this frame.▾
Run your physics/game-logic loop exactly this many times per frame.
pub fn fixed_tick_count(&self) -> u32
EngineClock::alphafnRender interpolation factor.▾
Value in [0.0, 1.0] — use to interpolate between the previous and current physics state for smooth rendering.
pub fn alpha(&self) -> f32
EngineClock::set_time_scalefnSet the time-scale multiplier.▾
Values above 1.0 speed up game time; below 1.0 slow it down. Clears the paused state.
pub fn set_time_scale(&mut self, scale: f32)
EngineClock::pause / resumefnPause and resume game time.▾
pause() freezes delta() and game_time() while real_time() keeps ticking. resume() restores the previous time-scale.
pub fn pause(&mut self) pub fn resume(&mut self)
Entity Component System
Archetype / SoA layout, rayon scheduling · crates/ecs
API overview
WorldstructCentral ECS container.▾
Owns all entities, components, resources, and the scheduler. One World per application.
use ecs::World;
let mut world = World::new(); let e = world.entity().with(Position { x: 0.0, y: 0.0 }).spawn(); world.run(&ctx);
World::entityfnBegin building a new entity.▾
Returns an EntityBuilder. Chain .with::<T>(component) calls, then call .spawn().
Camera, world transform, origin shifting · crates/scene
API overview
CamerastructCinematic camera with optical parameters.▾
Manages lens, sensor format, aperture, exposure, and depth-of-field. Can be stored as an ECS component. Produces a CameraOutput snapshot each frame for the renderer.
use scene::{Camera, SensorFormat};
let cam = Camera::new(SensorFormat::FullFrame, 35.0); // or use cinematic defaults: let cam = Camera::cinematic_default();
WorldTransformstructTRS transform for a single object in world space.▾
Stores translation (Vec3), rotation (Quat), and scale (Vec3). Can be used as an ECS component. Provides helpers to derive forward/right/up axes and model matrices.
use scene::WorldTransform;
let t = WorldTransform::from_translation(Vec3::new(0.0, 1.0, 0.0)); let model = t.to_mat4();
Automatically recenters the render origin when the player drifts beyond a threshold distance, preventing f32 precision loss at large coordinates.
use scene::FloatingOrigin;
let mut origin = FloatingOrigin::new(1000.0); if origin.needs_rebase(player_world_pos) { let offset = origin.rebase(player_world_pos); // shift all objects by `offset` }
SceneManagerstructActive camera & viewport tracker (ECS Resource).▾
Insert as a resource into the ECS World. Set the active camera entity each frame so the render pass knows which camera to use.
Holds the Vulkan context, swapchain, and resource pools. The caller owns the render loop: call begin_render, add passes via FrameBuilder, then call end_render.
use graphics::{Renderer, RenderScene};
let mut renderer = Renderer::new(&window)?;
// each frame: let mut fb = renderer.begin_render(&scene)?; fb.add_pass(my_pass) .add_pass(post_process); fb.end_render()?;
Renderer::begin_renderfnStart a frame — returns a FrameBuilder.▾
Returns Err(RendererError::NeedRetry) on transient swapchain issues (skip the frame and retry next tick).
Taffy Flexbox layout, MSDF fonts, DrawCommand IR · crates/uisystem
API overview
UiEnginestructMain UI engine — layout and draw-command generation.▾
Maintains a tree of Nodes. Call update(viewport) each frame to recompute layout, then build_draw_commands() to get an IR list for your rendering backend.
use uisystem::{UiEngine, Style, NodeContent};
let mut ui = UiEngine::new(); let btn = ui.create_node(style, NodeContent::Text("Start".into())); ui.append_child(ui.root(), btn);
ui.update([1920.0, 1080.0]); let cmds = ui.build_draw_commands();
UiEngine::create_nodefnAdd a new node to the UI tree.▾