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;