Skip to main content

Crate nightshade_api

Crate nightshade_api 

Source
Expand description

§nightshade-api

A procedural high level API over the nightshade engine. Write a full 3d scene or a small game as straight-line code with free functions and plain data. No trait to implement, no callbacks to wire up, no ECS knowledge required to get started.

A spinning cube:

use nightshade_api::prelude::*;

fn main() {
    let mut app = open();
    let cube = spawn_cube(&mut app.world, vec3(0.0, 0.5, 0.0));
    while frame(&mut app) {
        let step = delta_time(&app.world);
        rotate(&mut app.world, cube, Vec3::y(), step);
    }
}

Add to Cargo.toml:

nightshade-api = "0.49"

§What you get for free

prelude::open gives you a window with a sky, a sun with shadows, a reference grid, an orbit camera focused on the origin, prototype textures like "checkerboard", and escape to exit. Every program starts from a lit, navigable scene. Override any of it with one call: prelude::set_background, prelude::show_grid, prelude::fly_camera, prelude::set_sun.

§The two entry points

Own the loop (native only). Setup is ordinary code before the loop, state is ordinary locals across loop iterations:

let mut app = open();
let mut score = 0;
while frame(&mut app) {
    score += 1;
}

Or hand the engine the loop with run, which also works on wasm. Setup returns your state, the update closure receives it back every frame:

run(
    |world| spawn_cube(world, vec3(0.0, 0.5, 0.0)),
    |world, cube| {
        let step = delta_time(world);
        rotate(world, *cube, Vec3::y(), step);
    },
)
.unwrap();

run returns a Result, so a real main returns it. For several per-frame jobs, the run! macro takes any number of update systems:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    run!(setup, handle_input, move_player, check_collisions)
}

For immediate-mode UI, enable the egui feature, call enable_egui once in setup, then draw from your update closure by pulling the frame’s context with egui_context. No extra closure: it composes with run and run! as is. egui is re-exported from the prelude.

run(
    |world| { enable_egui(world); spawn_cube(world, vec3(0.0, 0.5, 0.0)) },
    |world, cube| {
        rotate(world, *cube, Vec3::y(), delta_time(world));
        if let Some(ctx) = egui_context(world) {
            egui::Window::new("Inspector").show(&ctx, |ui| ui.label(format!("{cube:?}")));
        }
    },
)
.unwrap();

§Vocabulary

Two verbs carry the lifetime rules. spawn_ is retained: the thing exists until you prelude::despawn it. draw_ is immediate: visible for exactly one frame, redraw it every frame you want it on screen.

§Reading the scene back

The setters have readers, which is what a tool that edits a scene rather than just building one needs. prelude::describe_entity gathers an entity’s whole editable state, prelude::color and friends read one field, prelude::scene_tree and prelude::children walk the hierarchy, prelude::list_materials reads the shared material registry, and prelude::bounds_of with prelude::frame_entities measure and frame a selection. prelude::save_scene and prelude::load_scene round-trip the whole world to bytes. Components are added, removed, and snapshotted for undo by prelude::ComponentKind, the surface the standalone editor is built on.

§Commands

Every call also has a data form. prelude::Command is a serde enum with one variant per function, prelude::submit_command runs one, and prelude::submit_commands runs a batch where a later command can name an entity an earlier one produced with prelude::Ref::Result, so one batch builds and wires up a scene. The enum is the wire format a binding targets: build Command values, read prelude::CommandReply back, with the json schema from prelude::command_schema. The free functions stay the real implementations and the dispatch forwards to them. See the commands example and docs/COMMAND_API.md.

§Dropping down to the engine

Every function here takes the real engine prelude::World and bottoms out in normal nightshade calls. Nothing is hidden behind a wrapper type, so when a program outgrows the facade you replace one call site at a time. The full engine is re-exported at nightshade, one path away:

use nightshade_api::nightshade::prelude::*;

§Two worlds

A game that outgrows plain entities can keep its own ECS world beside the engine’s. Declare a second world with the freecs::ecs! macro and let the engine World own rendering and transforms while your world holds movement, behavior, and rules as plain components and systems. Link the two by storing the engine Entity in an EngineEntity component on each game entity, then once a frame push your game state into the engine with ordinary facade calls like set_position. Both freecs and EngineEntity come from the prelude, so use nightshade_api::prelude::* is enough to define and drive the second world. The engine side stays plain data and remains the same command surface every other call uses, while the game side is ordinary Rust. The dual_world example builds the whole pattern end to end.

§Examples

The examples/ directory is the tour. Run one with just run-example solar_system from the repo root, or cargo run -r -p nightshade-api --example solar_system. Every example also runs in the browser with just run-example-wasm solar_system, which serves it through trunk.

Re-exports§

pub use nightshade;

Modules§

prelude
Everything in one import.

Macros§

run
Runs a program from a setup expression and one or more per-frame update expressions, the variadic form of run. Setup runs once and returns your state; each update runs every frame, in the order given, and receives that state. Returns the same Result as run, so main can return it.