Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Your First Project

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

The Hub — new, open, and recent projects

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, Mat3 and Mat4 come through kooch::prelude, and the whole glam crate is reachable as kooch::glam. Adding your own glam dependency is the one thing to avoid: a Quat from 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:

  1. Select an entity in World (or spawn one).
  2. Add ComponentGameplaySpinner.
  3. Set speed in the Inspector — try 90.
  4. 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.

If something did not work

SymptomCause
The component is not in the Add Component menuRegister Scripts not pressed, or the project not rebuilt and reopened
A field is not in the InspectorIt is private, has #[reflect(skip)], or is a type reflection does not support yet (#649)
The derive does not compileA field’s type is not supported — Vec<T>, HashMap, your own enums. Mark it #[reflect(skip)]
The system never runsIt 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 minutesThe old local-Play path (#633)