Skip to main content

game_toolkit_platformer/
lib.rs

1//! A pluggable platforming controller for [game-toolkit](https://github.com/sunsided/game-toolkit):
2//! fluid, snappy movement with feel presets, and an animation state graph that follows it.
3//!
4//! The quickest start is [`Platformer`]: pick a preset, call `update` each frame with your
5//! world as a [`sim::Terrain`] (anything that can say what's solid; [`sim::Level`] is a
6//! ready-made one), and draw `sprite()` at `position()` (or `draw_debug` until there is
7//! art). Underneath, three
8//! layers you can also use on their own:
9//!
10//! - [`sim`]: the movement. A fixed-step, deterministic body: an [`sim::Intent`] in per
11//!   step, [`sim::Event`]s out. Every feel number and ability switch is in [`sim::Tuning`];
12//!   [`sim::Preset`]s set them toward the feel of Celeste, Hollow Knight, Silksong or
13//!   Super Meat Boy (an independent project, not affiliated with those games' makers).
14//! - [`anim`]: the animation controller, independent of any game: parameters, clips,
15//!   conditions, a graph spec compiled into a validated graph, the animator, drivers.
16//! - [`hero`]: the binding between them for one character: the built-in graph, its sprite
17//!   numbers, and the driver that turns movement into graph parameters.
18//!
19//! With the `toolkit` feature (on by default), [`controls`] reads game-toolkit's keyboard
20//! and gamepad through per-preset control schemes. Without it, nothing here depends on
21//! anything but std.
22//!
23//! A complete game with a playable character:
24//!
25//! ```no_run
26//! # #[cfg(feature = "toolkit")] {
27//! use game_toolkit::prelude::*;
28//! use game_toolkit_platformer::prelude::*;
29//!
30//! struct MyGame {
31//!     level: Level,
32//!     hero: Platformer,
33//! }
34//!
35//! impl Game for MyGame {
36//!     fn init(_ctx: &mut Context) -> Result<Self> {
37//!         let level = Level::demo();
38//!         let hero = Platformer::on(&level, Preset::Precise);
39//!         Ok(Self { level, hero })
40//!     }
41//!
42//!     fn update(&mut self, ctx: &mut Context, dt: f32) {
43//!         self.hero.update(&ctx.input, &mut self.level, dt);
44//!         for event in self.hero.drain_events() {
45//!             match event {
46//!                 Event::Jumped { .. } => { /* a sound */ }
47//!                 Event::Landed { drop, .. } if drop > 48 => { /* a thud, some dust */ }
48//!                 _ => {}
49//!             }
50//!         }
51//!     }
52//!
53//!     fn render(&mut self, ctx: &mut Context, frame: &mut Frame) {
54//!         let mut p = frame.painter(&mut ctx.gfx);
55//!         p.clear([0.05, 0.04, 0.12, 1.0]);
56//!         self.level.draw_debug(&mut p, [0.0, 0.0], 3.0);
57//!         self.hero.draw_debug(&mut p, [0.0, 0.0], 3.0);
58//!     }
59//! }
60//!
61//! run::<MyGame>(AppConfig::default()).unwrap();
62//! # }
63//! ```
64//!
65//! Configured through the builder:
66//!
67//! ```
68//! # #[cfg(feature = "toolkit")] {
69//! use game_toolkit_platformer::prelude::*;
70//!
71//! let hero = Platformer::builder()
72//!     .preset(Preset::Snappy)
73//!     .tune(|t| {
74//!         t.air_jumps = 2;
75//!         t.dash_speed = 260.0;
76//!     })
77//!     .controls(Scheme::PRECISE)
78//!     .spawn(Spot::TopLeft(40, 160))
79//!     .respawn(false)
80//!     .build()
81//!     .unwrap();
82//! assert_eq!(hero.tuning().air_jumps, 2);
83//! # }
84//! ```
85
86pub mod anim;
87#[cfg(feature = "toolkit")]
88pub mod controls;
89#[cfg(feature = "toolkit")]
90mod debug_draw;
91pub mod hero;
92pub mod party;
93pub mod platformer;
94pub mod sim;
95
96pub use party::Party;
97pub use platformer::{Animate, Builder, Platformer};
98
99/// Everything a game needs to get started: `use game_toolkit_platformer::prelude::*;`.
100pub mod prelude {
101    pub use crate::anim::{AnimEvent, GraphSpec};
102    #[cfg(feature = "toolkit")]
103    pub use crate::controls::{Controls, Custom, Scheme};
104    pub use crate::party::Party;
105    pub use crate::platformer::{Animate, Builder, Platformer};
106    pub use crate::sim::{
107        CustomId, CustomTile, Event, Grid, GridTerrain, Intent, JumpKind, Level, Preset, Rect,
108        Spot, Surface, Terrain, Tile, Tuning,
109    };
110}
111
112/// The README's code samples, compiled as doc tests.
113#[cfg(all(doctest, feature = "toolkit"))]
114#[doc = include_str!("../README.md")]
115struct ReadmeDoctests;
116
117/// The hazards recipe's code, compiled as a doc test.
118#[cfg(doctest)]
119#[doc = include_str!("../docs/hazards.md")]
120struct HazardsDoctests;
121
122/// The surfaces recipe's code, compiled as a doc test.
123#[cfg(doctest)]
124#[doc = include_str!("../docs/surfaces.md")]
125struct SurfacesDoctests;
126
127/// The moving-platforms recipe's code, compiled as a doc test.
128#[cfg(doctest)]
129#[doc = include_str!("../docs/moving-platforms.md")]
130struct MovingPlatformsDoctests;
131
132/// The movement doc's code, compiled as a doc test.
133#[cfg(doctest)]
134#[doc = include_str!("../docs/movement.md")]
135struct MovementDoctests;