game_toolkit_platformer/sim/tuning.rs
1//! [`Tuning`]: every number that shapes how the hero moves. Units and seconds
2//! (the movement's space: pixels at a scale of 1; see `docs/scale.md`).
3//!
4//! The defaults ([`Preset::Hybrid`]) sit near Celeste's (same 8-unit tiles: run, jump,
5//! dash, climb), with Hollow Knight's heavier fall, jump cut, double jump, dive and landing
6//! roll mixed in. The other [`Preset`]s lean all the way toward one game's feel; abilities
7//! a preset doesn't have are switched off (zero counts, false flags, unreachable drops).
8
9/// A named starting point for [`Tuning`].
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum Preset {
12 /// Everything on: the default.
13 Hybrid,
14 /// Precise and floaty: 8-way dash, climbing with stamina, no air jump, no jump cut
15 /// (tuned toward the feel of Celeste).
16 Precise,
17 /// Snappy: instant run, hard jump cut, double jump, horizontal dash, wall cling that
18 /// slips, dive, super dash, hard landings (tuned toward the feel of Hollow Knight).
19 Snappy,
20 /// Fast and slippery: walk or hold to run, momentum, strong wall jumps, wall stick,
21 /// nothing else (tuned toward the feel of Super Meat Boy).
22 Momentum,
23 /// Snappy with a double jump that leads into a glide while jump is held; sideways dash,
24 /// wall jumps, no dive (tuned toward the feel of Hollow Knight: Silksong).
25 Drifter,
26}
27
28impl Preset {
29 /// Every preset, in cycling order.
30 pub const ALL: [Preset; 5] = [
31 Preset::Hybrid,
32 Preset::Precise,
33 Preset::Snappy,
34 Preset::Drifter,
35 Preset::Momentum,
36 ];
37
38 /// Display name.
39 pub fn name(self) -> &'static str {
40 match self {
41 Preset::Hybrid => "hybrid",
42 Preset::Precise => "precise",
43 Preset::Snappy => "snappy",
44 Preset::Momentum => "momentum",
45 Preset::Drifter => "drifter",
46 }
47 }
48
49 /// The next preset, wrapping.
50 pub fn next(self) -> Preset {
51 let i = Self::ALL.iter().position(|&p| p == self).unwrap_or(0);
52 Self::ALL[(i + 1) % Self::ALL.len()]
53 }
54
55 /// The tuning.
56 pub fn tuning(self) -> Tuning {
57 let base = Tuning::default();
58 let never = i32::MAX;
59 match self {
60 Preset::Hybrid => base,
61 Preset::Precise => Tuning {
62 fall_gravity: 1.0,
63 jump_cut: 1.0,
64 air_jumps: 0,
65 dive: false,
66 roll_drop: never,
67 hard_land_drop: never,
68 wall_stick_time: 0.0,
69 // Only the ground (and crystals) give the dash back.
70 wall_refills_dashes: false,
71 glide: false,
72 run_roll: false,
73 sprint: false,
74 super_dash: false,
75 ..base
76 },
77 Preset::Snappy => Tuning {
78 run_speed: 83.0,
79 run_accel: 4000.0,
80 turn_accel: 4000.0,
81 run_decel: 4000.0,
82 air_control: 1.0,
83 jump_cut: 0.15,
84 apex_gravity: 0.75,
85 dash_eight_way: false,
86 dash_end_speed: 40.0,
87 grab: false,
88 wall_slide_speed: 60.0,
89 wall_jump_lock: 0.2,
90 wall_climb_speed: 80.0,
91 wall_climb_lock: 0.08,
92 roll_drop: never,
93 wings: true,
94 glide: false,
95 run_roll: false,
96 sprint: false,
97 ..base
98 },
99 Preset::Drifter => Tuning {
100 dive: false,
101 super_dash: false,
102 glide: true,
103 run_roll: false,
104 wings: false,
105 ..Preset::Snappy.tuning()
106 },
107 Preset::Momentum => Tuning {
108 // Walk, and hold the run button to run, as in Super Meat Boy.
109 run_speed: 90.0,
110 sprint_speed: 160.0,
111 run_accel: 600.0,
112 turn_accel: 1400.0,
113 run_decel: 900.0,
114 overspeed_decel: 200.0,
115 air_control: 0.9,
116 max_fall: 220.0,
117 fast_fall: 300.0,
118 jump_speed: 120.0,
119 var_jump_time: 0.18,
120 jump_cut: 0.5,
121 coyote_time: 0.06,
122 air_jumps: 0,
123 dashes: 0,
124 grab: false,
125 dive: false,
126 wall_slide_speed: 50.0,
127 wall_jump_speed: 170.0,
128 wall_jump_lock: 0.05,
129 wall_stick_time: 0.12,
130 wall_climb_speed: 90.0,
131 wall_climb_lock: 0.05,
132 roll_drop: never,
133 hard_land_drop: never,
134 glide: false,
135 run_roll: false,
136 super_dash: false,
137 ..base
138 },
139 }
140 }
141}
142
143/// The movement feel. All lengths in units, speeds units/s, accelerations units/s², times
144/// seconds.
145#[derive(Clone, Copy, Debug, PartialEq)]
146pub struct Tuning {
147 /// The hitbox's width and height in units (default 6x12, [`super::player::W`] x
148 /// [`super::player::H`]). The other numbers stay in units and don't scale with it:
149 /// tuned for a 6x12 body on 8-unit tiles, a much bigger body may want a higher jump.
150 pub size: (i32, i32),
151
152 /// Top running speed.
153 pub run_speed: f32,
154 /// A sprint button exists.
155 pub sprint: bool,
156 /// Top speed while sprinting.
157 pub sprint_speed: f32,
158 /// Acceleration toward the stick's speed on the ground.
159 pub run_accel: f32,
160 /// Acceleration when the stick points against the motion (turning around).
161 pub turn_accel: f32,
162 /// Deceleration with the stick released.
163 pub run_decel: f32,
164 /// Deceleration toward top speed when moving faster than it (after a roll jump or a
165 /// dash), so momentum carries.
166 pub overspeed_decel: f32,
167 /// Multiplier on all horizontal acceleration in the air.
168 pub air_control: f32,
169
170 /// Gravity.
171 pub gravity: f32,
172 /// Gravity multiplier while falling, for a heavier way down.
173 pub fall_gravity: f32,
174 /// Below this vertical speed (either way) with jump held, the hero is "at the apex".
175 pub apex_threshold: f32,
176 /// Gravity multiplier at the apex with jump held: the hangtime.
177 pub apex_gravity: f32,
178 /// Terminal falling speed.
179 pub max_fall: f32,
180 /// Terminal falling speed with down held.
181 pub fast_fall: f32,
182
183 /// Upward speed of a ground jump.
184 pub jump_speed: f32,
185 /// Horizontal speed added by a ground jump in the stick's direction.
186 pub jump_boost: f32,
187 /// Seconds after a jump during which holding jump keeps the full upward speed.
188 pub var_jump_time: f32,
189 /// Releasing jump while rising multiplies the upward speed by this (1: no cut).
190 pub jump_cut: f32,
191 /// Seconds after leaving a ledge during which a ground jump still works.
192 pub coyote_time: f32,
193 /// Seconds a jump press waits for the jump to become possible.
194 pub jump_buffer: f32,
195 /// Seconds after dropping through a platform during which platforms don't hold.
196 pub drop_through_time: f32,
197
198 /// Jumps available in the air before landing again.
199 pub air_jumps: u8,
200 /// Upward speed of an air jump.
201 pub double_jump_speed: f32,
202 /// Hold window of an air jump, like [`Tuning::var_jump_time`].
203 pub double_var_jump_time: f32,
204 /// Falling with ground this many units below, a jump press waits for the landing
205 /// (a queued ground jump) instead of spending the air jump.
206 pub land_jump_lookahead: i32,
207
208 /// Dashes available before touching ground or a crystal again (0: no dash).
209 pub dashes: u8,
210 /// Dashes go eight ways; otherwise only sideways.
211 pub dash_eight_way: bool,
212 /// Speed during a dash.
213 pub dash_speed: f32,
214 /// Length of a dash.
215 pub dash_time: f32,
216 /// Speed handed over when a dash ends.
217 pub dash_end_speed: f32,
218 /// Multiplier on the handed-over speed of upward dashes.
219 pub dash_end_up: f32,
220 /// Seconds after a dash starts before another one can.
221 pub dash_cooldown: f32,
222 /// Seconds a dash press waits for a dash to become possible.
223 pub dash_buffer: f32,
224 /// Seconds into a dash before the ground refills dashes (so dashing along the floor
225 /// isn't free).
226 pub dash_recharge_after: f32,
227 /// Seconds before a used crystal comes back.
228 pub crystal_respawn: f32,
229
230 /// Grabbing walls and climbing is possible at all.
231 pub grab: bool,
232 /// Holding still on a wall slides down at this speed: 0 holds firm, more is a grip
233 /// that slips.
234 pub slip_speed: f32,
235 /// Below this stamina even a firm grip slips, at [`Tuning::tired_slip_speed`].
236 pub tired_stamina: f32,
237 /// How fast a tired grip slips.
238 pub tired_slip_speed: f32,
239 /// Stamina when full; climbing spends it, the ground refills it.
240 pub stamina: f32,
241 /// Climbing speed up a wall.
242 pub climb_up_speed: f32,
243 /// Climbing speed down a wall.
244 pub climb_down_speed: f32,
245 /// Stamina per second climbing up.
246 pub climb_up_cost: f32,
247 /// Stamina per second holding still on a wall.
248 pub climb_still_cost: f32,
249 /// Stamina for a jump straight up a wall.
250 pub climb_jump_cost: f32,
251 /// Upward speed of the hop over the top of a wall.
252 pub climb_hop_speed: f32,
253 /// Terminal falling speed pressing into a wall without grabbing.
254 pub wall_slide_speed: f32,
255 /// Horizontal speed of a wall jump.
256 pub wall_jump_speed: f32,
257 /// Seconds after a wall jump during which the stick can't pull back to the wall.
258 pub wall_jump_lock: f32,
259 /// A wall this many units away still counts for a wall jump.
260 pub wall_jump_reach: i32,
261 /// Horizontal speed of a wall jump made holding toward the wall: small, so the hero
262 /// arcs back onto the same wall (Hollow Knight, Super Meat Boy). Equal to
263 /// [`Tuning::wall_jump_speed`] to turn it off.
264 pub wall_climb_speed: f32,
265 /// Stick lock of such a wall jump. Between toward and away, speed and lock blend.
266 pub wall_climb_lock: f32,
267 /// How much stick up or down tilts a wall jump's upward speed (0.15: +-15 %).
268 pub wall_jump_aim: f32,
269 /// Touching a wall (sliding or holding on) gives the air jumps back.
270 pub wall_refills_air_jumps: bool,
271 /// Touching a wall gives the dashes back too.
272 pub wall_refills_dashes: bool,
273 /// Sliding down a wall, pressing away keeps the hero stuck to it this long, so
274 /// "away + jump" becomes a wall jump instead of a fall.
275 pub wall_stick_time: f32,
276
277 /// Diving is possible at all.
278 pub dive: bool,
279
280 /// Gliding: a jump pressed in the air and held (after the air jump, if one is left)
281 /// opens a cloak once falling.
282 pub glide: bool,
283 /// Terminal falling speed gliding.
284 pub glide_fall_speed: f32,
285
286 /// Super dash (Hollow Knight's Crystal Heart): hold to charge on the ground or on a
287 /// wall, release when charged to fly straight and level until a wall or a cancel.
288 pub super_dash: bool,
289 /// Seconds to charge it.
290 pub super_charge: f32,
291 /// Flying speed.
292 pub super_speed: f32,
293 /// Horizontal speed kept when the flight is cancelled (jump or the button again).
294 pub super_cancel_speed: f32,
295
296 /// Tapping down while running at speed rolls.
297 pub run_roll: bool,
298 /// Horizontal speed needed for a roll from a run.
299 pub run_roll_speed: f32,
300
301 /// Presentation: air jumps flap wings (Hollow Knight's Monarch Wings) instead of a
302 /// flip. Read by the hero's animation driver, not by the movement.
303 pub wings: bool,
304
305 /// Seconds hanging in the air before a dive plunges.
306 pub dive_windup: f32,
307 /// Speed of a dive.
308 pub dive_speed: f32,
309 /// How long a dive's impact takes away control.
310 pub dive_land_time: f32,
311
312 /// Fall height in units from which a landing while moving becomes a roll.
313 pub roll_drop: i32,
314 /// Roll speed (the roll keeps any faster speed it lands with).
315 pub roll_speed: f32,
316 /// Length of a roll.
317 pub roll_time: f32,
318 /// Seconds into a roll before it can be cancelled into a jump.
319 pub roll_jump_after: f32,
320 /// Fall height in units from which a landing without moving is a hard landing.
321 pub hard_land_drop: i32,
322 /// How long a hard landing takes away control.
323 pub hard_land_time: f32,
324
325 /// Units the head may be nudged sideways around a ceiling corner.
326 pub corner_correction: i32,
327
328 /// How much of the speed of moving ground (or a moving wall held) a jump off it keeps:
329 /// 1 all of it, 0 none. Kept for [`Tuning::coyote_time`] after leaving it.
330 pub lift_boost: f32,
331}
332
333impl Default for Tuning {
334 fn default() -> Self {
335 Self {
336 size: (super::player::W, super::player::H),
337
338 run_speed: 90.0,
339 sprint: true,
340 sprint_speed: 140.0,
341 run_accel: 1000.0,
342 turn_accel: 1600.0,
343 run_decel: 1200.0,
344 overspeed_decel: 400.0,
345 air_control: 0.65,
346
347 gravity: 900.0,
348 fall_gravity: 1.15,
349 apex_threshold: 40.0,
350 apex_gravity: 0.5,
351 max_fall: 160.0,
352 fast_fall: 240.0,
353
354 jump_speed: 105.0,
355 jump_boost: 40.0,
356 var_jump_time: 0.2,
357 jump_cut: 0.6,
358 coyote_time: 0.1,
359 jump_buffer: 0.1,
360 drop_through_time: 0.15,
361
362 air_jumps: 1,
363 double_jump_speed: 100.0,
364 double_var_jump_time: 0.12,
365 land_jump_lookahead: 6,
366
367 dashes: 1,
368 dash_eight_way: true,
369 dash_speed: 240.0,
370 dash_time: 0.15,
371 dash_end_speed: 160.0,
372 dash_end_up: 0.75,
373 dash_cooldown: 0.2,
374 dash_buffer: 0.08,
375 dash_recharge_after: 0.1,
376 crystal_respawn: 2.5,
377
378 grab: true,
379 slip_speed: 0.0,
380 tired_stamina: 20.0,
381 tired_slip_speed: 20.0,
382 stamina: 110.0,
383 climb_up_speed: 45.0,
384 climb_down_speed: 80.0,
385 climb_up_cost: 45.0,
386 climb_still_cost: 10.0,
387 climb_jump_cost: 27.5,
388 climb_hop_speed: 120.0,
389 wall_slide_speed: 40.0,
390 wall_jump_speed: 130.0,
391 wall_jump_lock: 0.16,
392 wall_jump_reach: 2,
393 wall_climb_speed: 70.0,
394 wall_climb_lock: 0.07,
395 wall_jump_aim: 0.15,
396 wall_refills_air_jumps: true,
397 wall_refills_dashes: true,
398 wall_stick_time: 0.1,
399
400 dive: true,
401
402 glide: true,
403 glide_fall_speed: 30.0,
404
405 super_dash: true,
406 super_charge: 0.8,
407 super_speed: 280.0,
408 super_cancel_speed: 90.0,
409
410 run_roll: true,
411 run_roll_speed: 100.0,
412
413 wings: false,
414
415 dive_windup: 0.08,
416 dive_speed: 320.0,
417 dive_land_time: 0.2,
418
419 roll_drop: 48,
420 roll_speed: 120.0,
421 roll_time: 0.32,
422 roll_jump_after: 0.05,
423 hard_land_drop: 96,
424 hard_land_time: 0.3,
425
426 corner_correction: 3,
427 lift_boost: 1.0,
428 }
429 }
430}