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

Spawning Entities

Every entity in Nightshade comes out of spawn_entities. The call takes a component flag mask and a count, and returns a Vec<Entity> of newly-created entities living in the archetype defined by that mask.

#![allow(unused)]
fn main() {
let entity = world.spawn_entities(
    LOCAL_TRANSFORM | GLOBAL_TRANSFORM | RENDER_MESH | MATERIAL_REF,
    1
)[0];
}

Components start at their Default values. Write the actual values with the per-component setters.

#![allow(unused)]
fn main() {
world.core.set_local_transform(entity, LocalTransform {
    translation: Vec3::new(0.0, 1.0, 0.0),
    rotation: Quat::identity(),
    scale: Vec3::new(1.0, 1.0, 1.0),
});

world.core.set_render_mesh(entity, RenderMesh {
    name: "cube".to_string(),
    id: None,
});

world.core.set_material_ref(entity, MaterialRef::new("Default"));
}

Spawning many at once

count > 1 batches the work. The archetype table is located once, every component vec grows once, and the entity allocator hands back a packed range of handles.

#![allow(unused)]
fn main() {
let entities = world.spawn_entities(LOCAL_TRANSFORM | GLOBAL_TRANSFORM, 100);

for (index, entity) in entities.iter().enumerate() {
    world.core.set_local_transform(*entity, LocalTransform {
        translation: Vec3::new(index as f32 * 2.0, 0.0, 0.0),
        ..Default::default()
    });
}
}

For thousands of entities the batch form is meaningfully faster than a loop of individual spawns.

Helper functions

Common entity shapes have spawn helpers in the prelude. Each helper picks the right component mask, spawns the entity, and writes sensible defaults so the caller does not have to know which components a given object actually needs.

Cameras

#![allow(unused)]
fn main() {
let camera = spawn_camera(world, Vec3::new(0.0, 5.0, 10.0), "Main Camera".to_string());
world.resources.active_camera = Some(camera);

let orbit_camera = spawn_pan_orbit_camera(
    world,
    Vec3::zeros(),
    10.0,
    0.5,
    0.4,
    "Orbit Camera".to_string(),
);
}

spawn_camera makes a perspective camera at the given position. spawn_pan_orbit_camera adds the PAN_ORBIT_CAMERA component so the pan-orbit controller can drive it. The active-camera resource holds the entity the renderer pulls from each frame.

Lights

#![allow(unused)]
fn main() {
let sun = spawn_sun(world);
}

spawn_sun creates a directional light with reasonable defaults. Point and spot lights are spawned by hand because the Light struct carries the type-discriminating fields.

#![allow(unused)]
fn main() {
let light = world.spawn_entities(
    LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LIGHT,
    1,
)[0];

world.core.set_local_transform(light, LocalTransform {
    translation: Vec3::new(0.0, 3.0, 0.0),
    ..Default::default()
});

world.core.set_light(light, Light {
    light_type: LightType::Point,
    color: Vec3::new(1.0, 0.8, 0.6),
    intensity: 5.0,
    range: 10.0,
    cast_shadows: true,
    shadow_bias: 0.005,
    inner_cone_angle: 0.0,
    outer_cone_angle: 0.0,
});
}

Primitives

#![allow(unused)]
fn main() {
spawn_cube_at(world, Vec3::new(0.0, 1.0, 0.0));
spawn_sphere_at(world, Vec3::new(2.0, 1.0, 0.0));
spawn_plane_at(world, Vec3::zeros());
spawn_cylinder_at(world, Vec3::new(4.0, 1.0, 0.0));
spawn_cone_at(world, Vec3::new(6.0, 1.0, 0.0));
spawn_torus_at(world, Vec3::new(8.0, 1.0, 0.0));
}

Each primitive helper picks up the matching mesh from the built-in mesh cache and applies the default material.

Physics objects

Physics spawn helpers live in nightshade::ecs::physics::commands rather than the prelude, because they are only available with the physics feature on.

#![allow(unused)]
fn main() {
use nightshade::ecs::physics::commands::*;

let dynamic_cube = spawn_dynamic_physics_cube_with_material(
    world,
    Vec3::new(0.0, 5.0, 0.0),
    Vec3::new(1.0, 1.0, 1.0),
    1.0,
    Material {
        base_color: [0.8, 0.2, 0.2, 1.0],
        ..Default::default()
    },
);

let floor = spawn_static_physics_cube_with_material(
    world,
    Vec3::new(0.0, -0.5, 0.0),
    Vec3::new(50.0, 1.0, 50.0),
    Material::default(),
);
}

The full set is spawn_static_physics_cube_with_material, spawn_dynamic_physics_cube_with_material, spawn_dynamic_physics_sphere_with_material, and spawn_dynamic_physics_cylinder_with_material.

To roll a physics entity by hand, combine the rendering flags with RIGID_BODY and COLLIDER directly.

#![allow(unused)]
fn main() {
let entity = world.spawn_entities(
    LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY
    | RENDER_MESH | MATERIAL_REF | RIGID_BODY | COLLIDER,
    1,
)[0];

world.core.set_local_transform(entity, LocalTransform {
    translation: Vec3::new(0.0, 5.0, 0.0),
    ..Default::default()
});

world.core.set_render_mesh(entity, RenderMesh {
    name: "cube".to_string(),
    id: None,
});

world.core.set_material_ref(entity, MaterialRef::new("Default"));

world.core.set_rigid_body(entity, RigidBodyComponent {
    body_type: RigidBodyType::Dynamic,
    ..Default::default()
});
}

Character controllers

#![allow(unused)]
fn main() {
let (player_entity, camera_entity) = spawn_first_person_player(world, Vec3::new(0.0, 2.0, 0.0));
}

spawn_first_person_player returns the player entity and the camera entity attached to it. The player owns the character controller. The camera is a child entity at eye height.

Loading models

#![allow(unused)]
fn main() {
use nightshade::ecs::prefab::*;

let model_bytes = include_bytes!("../assets/character.glb");
let result = import_gltf_from_bytes(model_bytes)?;

for (name, (rgba_data, width, height)) in result.textures {
    world.queue_command(WorldCommand::LoadTexture {
        name,
        rgba_data,
        width,
        height,
    });
}

for (name, mesh) in result.meshes {
    mesh_cache_insert(&mut world.resources.mesh_cache, name, mesh);
}

for prefab in result.prefabs {
    let entity = spawn_prefab_with_animations(
        world,
        &prefab,
        &result.animations,
        Vec3::new(0.0, 0.0, 0.0),
    );
}
}

Textures are queued as LoadTexture commands because GPU uploads cannot run inline. Meshes go straight into the mesh cache because they are CPU-side data. spawn_prefab_with_animations walks the prefab tree, spawns one entity per node, wires up the Parent links, and attaches the animations to the root.

Adding components after spawn

#![allow(unused)]
fn main() {
world.core.add_components(entity, AUDIO_SOURCE);
world.core.set_audio_source(entity, AudioSource::new("music").playing());
}

add_components migrates the entity to the archetype current_mask | AUDIO_SOURCE. The existing components ride along. The audio source starts at Default::default() and the next line writes the real value.

Despawning

#![allow(unused)]
fn main() {
world.despawn_entities(&[entity]);

despawn_recursive_immediate(world, entity);
}

The first removes a single entity. The second removes the entity and every descendant in its transform hierarchy. For a deferred variant that waits until the next command-processing point, queue WorldCommand::DespawnRecursive instead.

Spawning a child

A child is an entity with a Parent component pointing at the parent's handle. The transform system handles the matrix propagation.

#![allow(unused)]
fn main() {
let parent = world.spawn_entities(LOCAL_TRANSFORM | GLOBAL_TRANSFORM, 1)[0];
let child = world.spawn_entities(LOCAL_TRANSFORM | GLOBAL_TRANSFORM | PARENT, 1)[0];

world.core.set_parent(child, Parent(Some(parent)));
world.core.set_local_transform(child, LocalTransform {
    translation: Vec3::new(0.0, 1.0, 0.0),
    ..Default::default()
});
}