Minimal Example
The simplest possible Nightshade application.
Complete Code
use nightshade::prelude::*; struct MinimalGame; impl State for MinimalGame { fn initialize(&mut self, world: &mut World) { let camera = spawn_camera(world, Vec3::new(0.0, 5.0, 10.0), "Camera".to_string()); world.resources.active_camera = Some(camera); spawn_cube_at(world, Vec3::new(0.0, 0.0, -5.0)); spawn_sun(world); } fn run_systems(&mut self, world: &mut World) { fly_camera_system(world); } } fn main() -> Result<(), Box<dyn std::error::Error>> { nightshade::launch(MinimalGame) }
Step-by-Step Breakdown
1. Import the Prelude
#![allow(unused)] fn main() { use nightshade::prelude::*; }
The prelude exports all commonly used types:
StatetraitWorldstructEntitytype- Math types (
Vec3,Vec4,Mat4, etc.) - Component flags (
LOCAL_TRANSFORM,RENDER_MESH, etc.) - Common functions (
spawn_cube_at,spawn_camera, etc.)
2. Define Your Game State
#![allow(unused)] fn main() { struct MinimalGame; }
Your game state struct holds all game-specific data. It can be empty for simple demos or contain complex game logic:
#![allow(unused)] fn main() { struct MinimalGame { score: u32, player: Option<Entity>, enemies: Vec<Entity>, } }
3. Implement the State Trait
#![allow(unused)] fn main() { impl State for MinimalGame { fn initialize(&mut self, world: &mut World) { // Called once at startup } } }
The State trait has many optional methods:
| Method | Purpose |
|---|---|
initialize | Setup at startup |
run_systems | Game logic each frame |
ui | egui-based UI |
on_keyboard_input | Key press/release |
on_mouse_input | Mouse button events |
on_gamepad_event | Gamepad input |
configure_render_graph | Custom rendering |
next_state | State transitions |
4. Set Up the Scene
#![allow(unused)] fn main() { fn initialize(&mut self, world: &mut World) { let camera = spawn_camera(world, Vec3::new(0.0, 5.0, 10.0), "Camera".to_string()); world.resources.active_camera = Some(camera); spawn_cube_at(world, Vec3::new(0.0, 0.0, -5.0)); spawn_sun(world); } }
5. Run the Application
fn main() -> Result<(), Box<dyn std::error::Error>> { nightshade::launch(MinimalGame) }
The launch function:
- Creates the window
- Initializes the renderer
- Calls
initializeon your state - Runs the game loop
- Handles input events
- Calls
run_systemseach frame
Adding Cargo.toml
[package]
name = "minimal-game"
version = "0.1.0"
edition = "2024"
[dependencies]
nightshade = { git = "https://github.com/user/nightshade", features = ["engine", "wgpu"] }
Running
cargo run --release
Release mode is recommended for better performance.
Controls
The fly camera uses standard controls:
- WASD - Move horizontally
- Space/Shift - Move up/down
- Mouse - Look around
- Escape - Release cursor
Extending the Example
Add More Objects
#![allow(unused)] fn main() { fn initialize(&mut self, world: &mut World) { let camera = spawn_camera(world, Vec3::new(0.0, 5.0, 10.0), "Camera".to_string()); world.resources.active_camera = Some(camera); spawn_plane_at(world, Vec3::zeros()); for index in 0..5 { spawn_cube_at(world, Vec3::new(index as f32 * 2.0 - 4.0, 0.5, -5.0)); } spawn_sun(world); } }
Add Animation
#![allow(unused)] fn main() { struct MinimalGame { cube: Option<Entity>, time: f32, } impl State for MinimalGame { fn initialize(&mut self, world: &mut World) { let camera = spawn_camera(world, Vec3::new(0.0, 5.0, 10.0), "Camera".to_string()); world.resources.active_camera = Some(camera); self.cube = Some(spawn_cube_at(world, Vec3::new(0.0, 0.0, -5.0))); spawn_sun(world); } fn run_systems(&mut self, world: &mut World) { let dt = world.resources.window.timing.delta_time; self.time += dt; if let Some(cube) = self.cube { if let Some(transform) = world.core.get_local_transform_mut(cube) { transform.rotation = nalgebra_glm::quat_angle_axis( self.time, &Vec3::y(), ); } } } } }
Add Input Handling
#![allow(unused)] fn main() { impl State for MinimalGame { fn on_keyboard_input(&mut self, world: &mut World, key: KeyCode, state: ElementState) { if state == ElementState::Pressed { match key { KeyCode::Escape => std::process::exit(0), KeyCode::Space => self.spawn_cube(world), _ => {} } } } } }
What's Next
From this foundation, you can:
- Add physics with rigid bodies and colliders
- Load 3D models with
import_gltf_from_pathandspawn_prefab_with_animations - Add skeletal animation
- Implement game logic in
run_systems - Create UI with egui
- Add audio with Kira
See the other examples for complete implementations of these features.