mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
//! What the action derives answer to, and what a context does with them,
//! seen the way a game sees it.

use mirage_engine::prelude::*;

meshes! { enum Only { Cube } }

/// What the player can do, each over a key and a pad button.
#[derive(InputButtonAction, Clone, Copy, Debug, Eq, PartialEq)]
enum Verb {
    Fire,
    Pause,
}

/// One thing the player leans on, over a trigger or two keys.
#[derive(InputAxisAction, Clone, Copy, Debug, Eq, PartialEq)]
enum Lever {
    Throttle,
}

/// One direction the player heads in, over a stick or four keys.
#[derive(InputAxis2Action, Clone, Copy, Debug, Eq, PartialEq)]
enum Heading {
    Walk,
}

impl InputButtonAction for Verb {
    fn bindings(&self) -> Vec<ButtonBinding> {
        match self {
            Self::Fire => vec![Key::Space.into(), Pad::South.into()],
            Self::Pause => vec![Key::Escape.into(), Pad::Start.into()],
        }
    }
}

impl InputAxisAction for Lever {
    fn bindings(&self) -> Vec<AxisBinding> {
        vec![
            AxisBinding::pad(PadAxis::RightTrigger),
            AxisBinding::from(ButtonAxis {
                negative: Key::S,
                positive: Key::W,
            }),
        ]
    }
}

impl InputAxis2Action for Heading {
    fn bindings(&self) -> Vec<Axis2Binding> {
        vec![
            Stick::Left.into(),
            Axis2Binding::from(ButtonAxis2 {
                left: Key::A,
                right: Key::D,
                down: Key::S,
                up: Key::W,
            }),
        ]
    }
}

/// The three seats one game fills.
struct Controls;

impl InputActions for Controls {
    type Button = Verb;
    type Axis = Lever;
    type Axis2 = Heading;
}

/// A game that asks its contexts everything they answer about input, so that
/// every one of those calls is compiled.
struct Pilot {
    listening: bool,
}

impl Game for Pilot {
    type Meshes = Only;
    type Sounds = NoSounds;
    type InputActions = Controls;
    type Skyboxes = NoSkyboxes;
    type SurfaceStyles = NoSurfaceStyles;
    type PostEffects = NoPostEffects;

    fn tick(&mut self, ctx: &mut TickContext<'_, Pilot>) {
        self.listening = ctx.pressed(Verb::Pause) || !ctx.down(Verb::Fire);
        let aimed = ctx.axis(Lever::Throttle) + ctx.axis2(Heading::Walk).x + ctx.pointer().x;
        self.listening &= aimed.is_finite() && !ctx.released(Verb::Pause);
    }

    fn frame(&mut self, ctx: &mut FrameContext<'_, Pilot>) {
        if self.listening {
            if let Some(binding) = ctx.actuated_button() {
                ctx.rebind(Verb::Fire, vec![binding]);
            }
            if let Some(binding) = ctx.actuated_axis() {
                ctx.rebind(Lever::Throttle, vec![binding.deadzone(0.2).invert()]);
            }
            if let Some(binding) = ctx.actuated_axis2() {
                ctx.rebind(Heading::Walk, vec![binding.scale(0.5)]);
            }
        }
        ctx.rebind(Verb::Pause, vec![Key::P.into(), Pad::Guide.into()]);

        let shown: Vec<String> = ctx
            .bindings(Verb::Pause)
            .iter()
            .map(ToString::to_string)
            .collect();
        ctx.draw(Cube.at(Vec3::splat(shown.len() as f32)));
    }
}

#[test]
fn a_vocabulary_is_enumerated_and_named_by_what_its_variants_are_called() {
    assert_eq!(Verb::all(), vec![Verb::Fire, Verb::Pause]);
    assert_eq!(Verb::Pause.name(), "Pause");
    assert_eq!(Verb::from_name("Fire"), Some(Verb::Fire));
    assert_eq!(Verb::from_name("fire"), None, "names are matched exactly");
    assert_eq!(Verb::from_name("Duck"), None);
}

#[test]
fn the_declared_bindings_are_what_a_vocabulary_starts_at() {
    assert_eq!(
        Verb::Fire.defaults(),
        vec![
            ButtonBinding::Key(Key::Space),
            ButtonBinding::Pad(Pad::South)
        ]
    );
    assert_eq!(Lever::Throttle.defaults().len(), 2);
    assert_eq!(Heading::Walk.defaults(), Heading::Walk.bindings());
}

#[test]
fn a_binding_reads_as_the_text_a_controls_menu_shows() {
    let shown: Vec<String> = Verb::Pause
        .defaults()
        .iter()
        .map(ToString::to_string)
        .collect();

    assert_eq!(shown, ["Escape", "Start"]);
    assert_eq!(Heading::Walk.defaults()[0].to_string(), "Left Stick");
    assert_eq!(Lever::Throttle.defaults()[1].to_string(), "S / W");
}

#[test]
fn a_game_with_no_verbs_of_a_kind_leaves_that_seat_empty() {
    assert!(NoInputButtons::all().is_empty());
    assert!(NoInputAxes::from_name("Throttle").is_none());
    assert!(NoInputAxes2::all().is_empty());
}

#[test]
fn a_prototype_binds_every_key_to_itself() {
    assert_eq!(Key::W.defaults(), vec![ButtonBinding::Key(Key::W)]);
    assert_eq!(Key::from_name("Space"), Some(Key::Space));
    assert!(
        Key::all().contains(&Key::Escape),
        "every key is one of them"
    );
}

#[test]
fn a_game_names_the_vocabulary_of_each_kind() {
    let pilot = Pilot { listening: false };

    assert!(!pilot.listening);
    assert_eq!(
        <<Pilot as Game>::InputActions as InputActions>::Button::all(),
        Verb::all()
    );
}