Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  • State trait
  • World struct
  • Entity type
  • 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:

MethodPurpose
initializeSetup at startup
run_systemsGame logic each frame
uiegui-based UI
on_keyboard_inputKey press/release
on_mouse_inputMouse button events
on_gamepad_eventGamepad input
configure_render_graphCustom rendering
next_stateState 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:

  1. Creates the window
  2. Initializes the renderer
  3. Calls initialize on your state
  4. Runs the game loop
  5. Handles input events
  6. Calls run_systems each 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_path and spawn_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.