Your First Application
A Nightshade application is a struct that implements the State trait. The engine calls initialize once at startup and run_systems every frame. The rest of the methods on the trait are input handlers and lifecycle hooks. That is the whole interface.
The minimum viable application
use nightshade::prelude::*; fn main() -> Result<(), Box<dyn std::error::Error>> { nightshade::launch(MyGame::default()) } #[derive(Default)] struct MyGame; impl State for MyGame { fn initialize(&mut self, world: &mut World) { world.resources.window.title = "My First Game".to_string(); } fn run_systems(&mut self, world: &mut World) { } }
This compiles and runs. It opens a window and renders nothing useful. There is no camera, no light, no geometry. The rest of this chapter fills in those pieces, one at a time, before assembling them into a single example.
A camera
The camera determines what gets drawn. Spawn one and mark it active.
#![allow(unused)] fn main() { fn initialize(&mut self, world: &mut World) { let camera = spawn_camera( world, Vec3::new(0.0, 5.0, 10.0), "Main Camera".to_string(), ); world.resources.active_camera = Some(camera); } }
spawn_camera returns an Entity handle. The renderer reads from whichever entity is stored in world.resources.active_camera. A scene with no active camera renders the clear color and nothing else.
A light
A directional light models the sun. Without one, every PBR surface in the scene is black.
#![allow(unused)] fn main() { fn initialize(&mut self, world: &mut World) { spawn_sun(world); } }
spawn_sun creates a directional light entity with sensible defaults and parents it to the world root. Replace it with spawn_point_light or spawn_spot_light for those flavors.
The grid and the sky
The grid is a development aid. The atmosphere fills the background and feeds image-based lighting.
#![allow(unused)] fn main() { fn initialize(&mut self, world: &mut World) { world.resources.graphics.show_grid = true; world.resources.graphics.atmosphere = Atmosphere::Sky; } }
Both fields live on world.resources.graphics. Toggling show_grid adds an infinite ground grid drawn at y = 0. The Atmosphere enum selects the procedural skybox (sky, nebula, or one of the other variants).
Geometry
spawn_cube_at is the simplest way to put something in the scene. It places a unit cube at the given position with default material properties.
#![allow(unused)] fn main() { fn initialize(&mut self, world: &mut World) { spawn_cube_at(world, Vec3::new(0.0, 1.0, 0.0)); } }
The prelude exposes equivalent helpers for spheres and other primitives. For real geometry, load a glTF file.
Camera controls
The fly camera reads mouse and keyboard input and moves the active camera each frame. Add it to run_systems.
#![allow(unused)] fn main() { fn run_systems(&mut self, world: &mut World) { fly_camera_system(world); escape_key_exit_system(world); } }
escape_key_exit_system sets world.resources.window.should_exit = true when the escape key is pressed. The engine reads that flag at the top of each frame and shuts the window down cleanly.
The whole example
use nightshade::prelude::*; fn main() -> Result<(), Box<dyn std::error::Error>> { nightshade::launch(MyGame::default()) } #[derive(Default)] struct MyGame; impl State for MyGame { fn initialize(&mut self, world: &mut World) { world.resources.window.title = "My First Game".to_string(); world.resources.graphics.show_grid = true; world.resources.graphics.atmosphere = Atmosphere::Sky; let camera = spawn_camera( world, Vec3::new(0.0, 5.0, 10.0), "Main Camera".to_string(), ); world.resources.active_camera = Some(camera); spawn_sun(world); spawn_cube_at(world, Vec3::new(0.0, 1.0, 0.0)); spawn_cube_at(world, Vec3::new(3.0, 0.5, 0.0)); spawn_cube_at(world, Vec3::new(-2.0, 1.5, 2.0)); } fn run_systems(&mut self, world: &mut World) { fly_camera_system(world); escape_key_exit_system(world); } }
Controls
The fly camera binds the following inputs.
| Key | Action |
|---|---|
| W/A/S/D | Move forward/left/back/right |
| Space | Move up |
| Shift | Move down |
| Mouse | Look around |
| Escape | Exit |
Next steps
The next chapters cover loading real meshes from glTF, customizing materials, and adding physics simulation.