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.
- Scene content:
prelude::spawn_cube,prelude::spawn_sphere,prelude::spawn_floor,prelude::spawn_model,prelude::spawn_object - Crowds:
prelude::spawn_objects,prelude::spawn_instanced - Tags:
prelude::tag,prelude::tagged,prelude::for_each_tagged - Looks:
prelude::set_color,prelude::set_metallic_roughness,prelude::set_emissive,prelude::set_texture,prelude::set_normal_texture - Placement:
prelude::set_position,prelude::rotate,prelude::set_scale,prelude::set_parent - Cameras:
prelude::orbit_camera,prelude::fly_camera,prelude::first_person,prelude::fixed_camera,prelude::set_field_of_view,prelude::set_orthographic - Input:
prelude::key_down,prelude::wasd,prelude::mouse_clicked,prelude::clicked_entity - Time:
prelude::delta_time,prelude::set_time_scale,prelude::pause,prelude::Timer,prelude::Stopwatch - Immediate drawing:
prelude::draw_cube,prelude::draw_sphere,prelude::draw_line - Text:
prelude::spawn_text,prelude::set_text,prelude::spawn_label - UI:
prelude::spawn_panel,prelude::panel_button,prelude::panel_label,prelude::button_clicked; plus the full widget set:prelude::panel_data_grid,prelude::panel_tree_view,prelude::panel_property_grid,prelude::panel_color_picker,prelude::panel_date_picker,prelude::panel_command_palette,prelude::panel_modal,prelude::panel_virtual_list - Animation:
prelude::play_animation,prelude::play_animation_named,prelude::blend_to_animation,prelude::set_animation_speed - Post-processing:
prelude::set_ssao,prelude::set_ssr,prelude::set_tonemap,prelude::set_color_grading - Live physics tuning:
prelude::set_friction,prelude::set_restitution,prelude::set_mass,prelude::set_gravity_scale - Layout containers:
prelude::panel_row,prelude::panel_grid,prelude::panel_scroll; world-space UI:prelude::spawn_world_panel - Animation extras:
prelude::add_animation_event,prelude::add_animation_layer,prelude::reach_to,prelude::aim_at - Prefabs and undo:
prelude::make_prefab,prelude::spawn_prefab,prelude::UndoStack
§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.