Your First Project
- 1. Open the Hub
- 2. Write a component
- 3. Write a system
- 4. Register and build
- 5. Use it
- What to read next
- If something did not work
A complete pass through the loop: create a project, write a component and a system, and watch them run. Roughly fifteen minutes, most of it the first compile.
1. Open the Hub
cargo run -p kooch_editor

Create a project, or open one you have. The first build of a new project compiles the engine too — several minutes, once.
A project made with an older editor is migrated on open. You do not have to do anything, but that first build will also be a full one.
2. Write a component
New Component from the editor, named Spinner, then open src/spinner.rs and fill it in:
#![allow(unused)]
fn main() {
use kooch::kooch_ecs::Reflect;
use kooch::kooch_ecs::component::Component;
/// Makes an entity rotate. Attach it and set the speed in the Inspector.
#[derive(Default, Reflect)]
#[reflect(category = "Gameplay")]
pub struct Spinner {
/// Degrees per second around the Y axis.
pub speed: f32,
}
impl Component for Spinner {}
}
speed is public, so the Inspector will draw a drag value for it. Nothing else is needed —
see Writing a Component for the attributes that change how it is drawn.
3. Write a system
This one touches Transform, whose fields are glam types. The prelude re-exports them, so
there is nothing to add to Cargo.toml:
Vec2,Vec3,Vec4,Quat,Mat3andMat4come throughkooch::prelude, and the wholeglamcrate is reachable askooch::glam. Adding your ownglamdependency is the one thing to avoid: aQuatfrom a different version is a different type, and the compiler error names two types spelled identically.
New System, named spin, then open src/spin.rs:
#![allow(unused)]
fn main() {
use kooch::kooch_ecs::Query;
use kooch::kooch_ecs::transform::Transform;
use kooch::prelude::*;
use crate::spinner::Spinner;
/// Rotates every entity that has a `Spinner`.
pub fn spin(resources: &mut Resources) {
let dt = resources
.get::<Time>()
.map(|t| t.delta_secs())
.unwrap_or(1.0 / 60.0);
let query = Query::<(&Spinner, &mut Transform)>::new(resources);
query.for_each(|(spinner, transform)| {
transform.rotation *= Quat::from_rotation_y(spinner.speed.to_radians() * dt);
});
}
}
The query matches only entities that have both components, so a Spinner on an entity
with no Transform is simply skipped rather than being an error.
4. Register and build
Press Register Scripts. The editor scans src/, finds impl Component for Spinner and
pub fn spin(_: &mut Resources), and rewrites registrations.rs with both.
Then build. Today that means a terminal:
cargo build
and reopening the editor, because the project’s library is loaded once when the project opens. A build button and a live reload are #158 and #648; until they land, this step is manual and it is the slow part of the loop.
5. Use it
With the project reopened:
- Select an entity in World (or spawn one).
- Add Component → Gameplay →
Spinner. - Set
speedin the Inspector — try90. - Press Play.
It spins. Press Stop and the world returns exactly as you authored it — Play snapshots before it starts and restores on stop, so testing never costs you your scene.
What to read next
- Writing a Component — every field type the Inspector can draw, and the attributes that control it
- Writing a System — queries, stages, spawning
- Creating a Project — what each generated file is for
- The Editor — the panels, and what is not built yet
If something did not work
| Symptom | Cause |
|---|---|
| The component is not in the Add Component menu | Register Scripts not pressed, or the project not rebuilt and reopened |
| A field is not in the Inspector | It is private, has #[reflect(skip)], or is a type reflection does not support yet (#649) |
| The derive does not compile | A field’s type is not supported — Vec<T>, HashMap, your own enums. Mark it #[reflect(skip)] |
| The system never runs | It is in Update behind the Playing gate; press Play. Or its signature does not match pub fn f(_: &mut Resources) exactly, so the scanner missed it |
| Play opens a second window and takes minutes | The old local-Play path (#633) |