use mirage_engine::prelude::*;
meshes! { enum Only { Cube } }
#[derive(InputButtonAction, Clone, Copy, Debug, Eq, PartialEq)]
enum Verb {
Fire,
Pause,
}
#[derive(InputAxisAction, Clone, Copy, Debug, Eq, PartialEq)]
enum Lever {
Throttle,
}
#[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,
}),
]
}
}
struct Controls;
impl InputActions for Controls {
type Button = Verb;
type Axis = Lever;
type Axis2 = Heading;
}
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()
);
}