Add a component
This guide shows you how to define a new ECS component, have it self-register for serialization and the editor inspector, and query it from a system.
Prerequisites: you can build the workspace and have read ECS — CRPECS.
Step 1 — Define the struct with #[derive(Component)]
Components are plain data. Deriving Component generates the Component impl, a
SerializableX mirror with From conversions, and an inventory registration so
the type wires itself into the scene pipeline and the editor inspector — no manual
list to edit.
Declare the component’s semantic domain with #[component(domain = …)]. The domain
drives change-epoch tracking (which Flows must re-project when this component
changes). Valid domains: Spatial, Render, Audio, Physics, Ui.
#![allow(unused)]
fn main() {
use khora_macros::Component;
use khora_core::math::Vec3; // engine math types only — never raw glam
/// Per-entity wind influence, sampled by the foliage system.
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
#[component(domain = Spatial)]
pub struct WindAffected {
pub direction: Vec3,
pub strength: f32,
}
}
A Default impl is required: the editor’s “add component” action and deserialization
of skipped fields both rely on it.
Step 2 — Mark fields that must not be serialized
Use field attributes when a field cannot or should not round-trip through the serializer:
#[component(skip)]— exclude a single field (e.g. a GPU handle or runtime cache). It is omitted from theSerializablemirror and filled withDefault::default()on load.#[component(no_serializable)]— applied to the struct, suppresses the generated mirror entirely. Use this only when you hand-write the serialization yourself.
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Default, Component)]
#[component(domain = Render)]
pub struct DecalProjector {
pub size: f32,
/// Runtime-only GPU handle — never serialized.
#[component(skip)]
pub texture: Option<u64>,
}
}
Step 3 — Place the file and re-export it
Put the file next to the other components in
crates/khora-data/src/ecs/components/ and add a pub mod/pub use line in that
module’s mod.rs, matching the existing entries. The inventory registration fires
at startup automatically — there is nothing else to wire.
Step 4 — Verify it works
Build, then confirm the component spawns and reads back:
#![allow(unused)]
fn main() {
let mut world = World::new();
let e = world.spawn(WindAffected { direction: Vec3::X, strength: 2.0 });
assert_eq!(world.get_component::<WindAffected>(e).unwrap().strength, 2.0);
}
cargo test --workspace
The component now appears in the editor inspector and survives scene save/load with no further code.
Related
- ECS — CRPECS — storage model, queries, semantic domains.
- Add a flow — project this component into a View for lanes.
- Conventions — one primary type per file, naming.