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.52"§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::*;§A member world for game components
A game that outgrows plain entities declares its own components with the
freecs::dynamic_schema! macro and registers them as a third member world
of the engine group, sharing entity identity with the render entities. The
engine World owns rendering and transforms while the
member world holds movement, behavior, and rules on the same entities, so
there is no link component and one recursive despawn tears down both. Push
game state into the engine with ordinary facade calls like
set_position; freecs comes from the prelude,
so use nightshade_api::prelude::* is enough to define and drive the
member world. The member_world example builds the whole pattern end to
end.
§Web apps
For a browser app the engine runs in a web worker on an OffscreenCanvas
and a Leptos page drives it from the main thread. Both halves of that seam
are features of this crate: offscreen is the worker side
(offscreen::WorkerHostPlugin hosts the composed App in the worker,
owning the canvas, the render loop, and the page conversation, with
run_offscreen as the closure form), and leptos is the page side (the
web module, an engine-free Leptos component library with a worker-backed
EngineViewport, usable with default-features = false). The shared wire
types are in wire. The template_leptos and template_api_leptos
directories are the working references.
§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;pub use leptos;
Modules§
- editor
- Types the standalone editor shares across its processes (the page, the
engine worker, and the desktop bridge), behind the
protocolfeature. The agent surface is gated separately behindprotocol-agent. - prelude
- Everything in one import.
- transfer
- Envelope encoding shared by the page and the worker: every
postMessageis a plain object whose fields are thecrate::wire::MESSAGE_KEYcontrol message or thecrate::wire::APP_KEYapplication message, plus optionalcrate::wire::ATTACHMENTS_KEYbinary payloads whose buffers ride the transfer list. - web
- Leptos (CSR) components for building the page around an engine viewport, plus a general set of themed UI patterns: panels, forms, menus, tables, trees, a command palette, keybindings, a code editor, toasts, a status bar, drag and drop, and a resizable app shell, all driven from one set of CSS custom properties.
- wire
- Leptos-free wire types spoken between a page and its render worker,
compiled under either the
leptosor theoffscreenfeature. Pixel quantities are physical surface pixels (CSS pixels times the device pixel ratio), origin at the canvas top-left.