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

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.

KeyAction
W/A/S/DMove forward/left/back/right
SpaceMove up
ShiftMove down
MouseLook around
EscapeExit

Next steps

The next chapters cover loading real meshes from glTF, customizing materials, and adding physics simulation.