Skip to content

Commit 5ba69d1

Browse files
feat(platformer): momentum movement core with grappling hook
Replace the bunnyhop model with surface momentum. Slopes are frictionless and accelerate you downhill from gravity projected along the ground normal, flats run with friction, jumps preserve all horizontal speed, and air control redirects momentum without adding to it. There is no hard speed cap, so a clean ski line through ramps and kickers earns speed. Add a grappling hook as a persistent tool on a cooldown: it fires along the look ray from the eye, anchors to the first surface, reels you toward it, and draws a rope while active. Keep wall running and wall jumping. Strip the strafe assist, the coach hud, and the settings screen entirely. Rework the first level into a ski flow course (drop in, carry speed across a valley, launch a kicker, grapple a tower to the goal).
1 parent f6cb4df commit 5ba69d1

16 files changed

Lines changed: 781 additions & 865 deletions

File tree

apps/platformer/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
levels/
22
maps/
3+
settings.json

apps/platformer/src/ecs.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ freecs::ecs! {
2020
levels: LevelLibrary,
2121
prop_assets: PropAssets,
2222
editor: EditorState,
23-
settings: Settings,
2423
ui_handles: UiHandles,
2524
}
2625
}

apps/platformer/src/ecs/resources.rs

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,6 @@ pub enum Screen {
1717
Paused,
1818
LevelComplete,
1919
Editor,
20-
Settings,
21-
}
22-
23-
pub struct Settings {
24-
pub assist_level: f32,
25-
}
26-
27-
impl Default for Settings {
28-
fn default() -> Self {
29-
Self { assist_level: 6.0 }
30-
}
31-
}
32-
33-
impl Settings {
34-
pub fn assist_factor(&self) -> f32 {
35-
(self.assist_level / 10.0).clamp(0.0, 1.0)
36-
}
3720
}
3821

3922
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
@@ -114,7 +97,6 @@ impl Default for EditorState {
11497
#[derive(Default)]
11598
pub struct ScreenState {
11699
pub current: Screen,
117-
pub settings_return: Screen,
118100
}
119101

120102
#[derive(Default)]
@@ -129,6 +111,7 @@ pub struct PlayerState {
129111
pub overview_camera: Option<Entity>,
130112
pub sun: Option<Entity>,
131113
pub floor: Option<Entity>,
114+
pub grapple_rope: Option<Entity>,
132115
}
133116

134117
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
@@ -138,14 +121,6 @@ pub enum Locomotion {
138121
Airborne,
139122
}
140123

141-
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
142-
pub enum TurnHint {
143-
#[default]
144-
None,
145-
Left,
146-
Right,
147-
}
148-
149124
#[derive(Clone, Default)]
150125
pub struct ActiveLevel {
151126
pub name: String,
@@ -174,13 +149,16 @@ pub struct RunState {
174149
pub level_entities: Vec<Entity>,
175150
pub prop_entities: Vec<Entity>,
176151
pub play_level: Option<ActiveLevel>,
177-
pub prev_yaw: f32,
178-
pub has_prev_yaw: bool,
179152
pub testing_from_editor: bool,
180-
pub autohop_armed: bool,
181-
pub last_speed: f32,
182-
pub speed_delta: f32,
183-
pub turn_hint: TurnHint,
153+
pub wall_run_active: bool,
154+
pub wall_run_timer: f32,
155+
pub wall_run_cooldown: f32,
156+
pub wall_run_normal: Vec3,
157+
pub wall_run_tilt: f32,
158+
pub grappling: bool,
159+
pub grapple_anchor: Vec3,
160+
pub grapple_cooldown: f32,
161+
pub grapple_time: f32,
184162
}
185163

186164
#[derive(Default)]
@@ -190,24 +168,14 @@ pub struct TitleHandles {
190168
pub custom_buttons: Vec<Entity>,
191169
pub custom_names: Vec<String>,
192170
pub editor_button: Entity,
193-
pub settings_button: Entity,
194171
pub quit_button: Entity,
195172
}
196173

197-
#[derive(Default)]
198-
pub struct SettingsHandles {
199-
pub root: Entity,
200-
pub assist_slider: Entity,
201-
pub assist_value: Entity,
202-
pub back_button: Entity,
203-
}
204-
205174
#[derive(Default)]
206175
pub struct PauseHandles {
207176
pub root: Entity,
208177
pub resume_button: Entity,
209178
pub restart_button: Entity,
210-
pub settings_button: Entity,
211179
pub menu_button: Entity,
212180
pub quit_button: Entity,
213181
}
@@ -220,11 +188,6 @@ pub struct HudHandles {
220188
pub timer_label: Entity,
221189
pub level_label: Entity,
222190
pub crosshair: Entity,
223-
pub turn_left: Entity,
224-
pub turn_right: Entity,
225-
pub coach_label: Entity,
226-
pub key_a: Entity,
227-
pub key_d: Entity,
228191
}
229192

230193
#[derive(Default)]
@@ -253,5 +216,4 @@ pub struct UiHandles {
253216
pub hud: HudHandles,
254217
pub complete: CompleteHandles,
255218
pub editor: EditorHandles,
256-
pub settings: SettingsHandles,
257219
}

apps/platformer/src/state.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::ecs::{PlatformerWorld, Screen};
2-
use crate::systems::screens::{complete, editor as editor_screen, hud, pause, settings, title};
2+
use crate::systems::screens::{complete, editor as editor_screen, hud, pause, title};
33
use crate::systems::world::editor::EditorAction;
44
use crate::systems::world::{cutscene, editor, movement};
55
use crate::systems::{input, lifecycle};
@@ -22,13 +22,13 @@ impl State for Platformer {
2222
title::handle_input(&mut self.platformer_world, world);
2323
pause::handle_input(&mut self.platformer_world, world);
2424
complete::handle_input(&mut self.platformer_world, world);
25-
settings::handle_input(&mut self.platformer_world, world);
2625

2726
match self.platformer_world.resources.screen.current {
2827
Screen::Overview if cutscene::update(world) => {
2928
lifecycle::begin_play(&mut self.platformer_world, world);
3029
}
3130
Screen::Playing => {
31+
movement::pre_camera_roll(&self.platformer_world, world);
3232
first_person_camera_look_system(world);
3333
movement::update(&mut self.platformer_world, world);
3434
if self.platformer_world.resources.run.timing {
@@ -60,6 +60,5 @@ impl State for Platformer {
6060

6161
hud::update(&self.platformer_world, world);
6262
complete::update(&self.platformer_world, world);
63-
settings::update(&mut self.platformer_world, world);
6463
}
6564
}

apps/platformer/src/systems/input.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,5 @@ pub fn handle_global(platformer_world: &mut PlatformerWorld, world: &mut World)
3636
}
3737
Screen::LevelComplete => {}
3838
Screen::Editor => {}
39-
Screen::Settings => {}
4039
}
4140
}

apps/platformer/src/systems/lifecycle.rs

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::ecs::{ActiveLevel, PlatformerWorld, PlayMode, Screen};
2-
use crate::systems::screens::{complete, editor as editor_screen, hud, pause, settings, title};
2+
use crate::systems::screens::{complete, editor as editor_screen, hud, pause, title};
33
use crate::systems::world::{cutscene, editor, levels, player, props, textures};
44
use nightshade::prelude::*;
55
use std::collections::HashSet;
@@ -29,9 +29,6 @@ pub fn initialize(platformer_world: &mut PlatformerWorld, world: &mut World) {
2929
props::load(world, &mut platformer_world.resources.prop_assets);
3030

3131
platformer_world.resources.levels.names = levels::list_level_names();
32-
if let Some(assist) = settings::load_assist() {
33-
platformer_world.resources.settings.assist_level = assist;
34-
}
3532
build_ui(platformer_world, world);
3633

3734
enter(platformer_world, world, Screen::Title);
@@ -58,20 +55,12 @@ fn build_ui(platformer_world: &mut PlatformerWorld, world: &mut World) {
5855
let hud_handles = hud::build(&mut tree);
5956
let complete_handles = complete::build(&mut tree);
6057
let editor_handles = editor_screen::build(&mut tree);
61-
let settings_handles =
62-
settings::build(&mut tree, platformer_world.resources.settings.assist_level);
6358
tree.finish();
6459
platformer_world.resources.ui_handles.title = title_handles;
6560
platformer_world.resources.ui_handles.pause = pause_handles;
6661
platformer_world.resources.ui_handles.hud = hud_handles;
6762
platformer_world.resources.ui_handles.complete = complete_handles;
6863
platformer_world.resources.ui_handles.editor = editor_handles;
69-
platformer_world.resources.ui_handles.settings = settings_handles;
70-
}
71-
72-
pub fn open_settings(platformer_world: &mut PlatformerWorld, world: &mut World, from: Screen) {
73-
platformer_world.resources.screen.settings_return = from;
74-
enter(platformer_world, world, Screen::Settings);
7564
}
7665

7766
fn refresh_custom_map_list(platformer_world: &mut PlatformerWorld, world: &mut World) {
@@ -95,6 +84,7 @@ fn persistent_entities(platformer_world: &PlatformerWorld) -> HashSet<Entity> {
9584
player.overview_camera,
9685
player.sun,
9786
player.floor,
87+
player.grapple_rope,
9888
platformer_world.resources.editor.camera,
9989
]
10090
.into_iter()
@@ -115,13 +105,15 @@ pub fn start_level(platformer_world: &mut PlatformerWorld, world: &mut World, in
115105
return;
116106
};
117107

118-
let prop_entities = props::decorate(
108+
let mut prop_entities = props::decorate(
119109
world,
120110
&platformer_world.resources.prop_assets,
121111
&platformer_world.resources.run.level_entities,
122112
index,
123113
);
114+
prop_entities.extend(levels::spawn_level_pools(world, index));
124115
platformer_world.resources.run.prop_entities = prop_entities;
116+
levels::apply_theme(world, index);
125117

126118
platformer_world.resources.run.play_mode = PlayMode::Builtin(index);
127119
platformer_world.resources.run.play_level = Some(active);
@@ -185,11 +177,19 @@ pub fn begin_play(platformer_world: &mut PlatformerWorld, world: &mut World) {
185177
platformer_world.resources.run.run_time = 0.0;
186178
platformer_world.resources.run.timing = true;
187179
platformer_world.resources.run.top_speed = 0.0;
188-
platformer_world.resources.run.has_prev_yaw = false;
189-
platformer_world.resources.run.autohop_armed = false;
180+
reset_movement_state(platformer_world);
190181
enter(platformer_world, world, Screen::Playing);
191182
}
192183

184+
fn reset_movement_state(platformer_world: &mut PlatformerWorld) {
185+
let run = &mut platformer_world.resources.run;
186+
run.wall_run_active = false;
187+
run.wall_run_timer = 0.0;
188+
run.wall_run_tilt = 0.0;
189+
run.grappling = false;
190+
run.grapple_cooldown = 0.0;
191+
}
192+
193193
pub fn restart(platformer_world: &mut PlatformerWorld, world: &mut World) {
194194
let spawn = platformer_world
195195
.resources
@@ -202,8 +202,7 @@ pub fn restart(platformer_world: &mut PlatformerWorld, world: &mut World) {
202202
platformer_world.resources.run.run_time = 0.0;
203203
platformer_world.resources.run.timing = true;
204204
platformer_world.resources.run.top_speed = 0.0;
205-
platformer_world.resources.run.has_prev_yaw = false;
206-
platformer_world.resources.run.autohop_armed = false;
205+
reset_movement_state(platformer_world);
207206
}
208207

209208
fn at_goal(platformer_world: &PlatformerWorld, world: &World) -> bool {
@@ -276,7 +275,7 @@ pub fn check_fall(platformer_world: &mut PlatformerWorld, world: &mut World) {
276275
.map(|level| level.spawn)
277276
.unwrap_or(DEFAULT_SPAWN);
278277
player::place_at_spawn(platformer_world, world, spawn);
279-
platformer_world.resources.run.autohop_armed = false;
278+
reset_movement_state(platformer_world);
280279
}
281280
}
282281

@@ -328,13 +327,6 @@ pub fn enter(platformer_world: &mut PlatformerWorld, world: &mut World, screen:
328327
gamepad_nav: false,
329328
focus: None,
330329
},
331-
Screen::Settings => ScreenConfig {
332-
cursor_locked: false,
333-
cursor_visible: true,
334-
physics: false,
335-
gamepad_nav: true,
336-
focus: Some(handles.settings.assist_slider),
337-
},
338330
};
339331

340332
set_cursor_locked(world, config.cursor_locked);
@@ -379,11 +371,6 @@ pub fn apply_visibility(platformer_world: &PlatformerWorld, world: &mut World) {
379371
matches!(screen, Screen::Playing),
380372
);
381373
ui_set_visible(world, handles.editor.root, matches!(screen, Screen::Editor));
382-
ui_set_visible(
383-
world,
384-
handles.settings.root,
385-
matches!(screen, Screen::Settings),
386-
);
387374
}
388375

389376
pub fn next_level_index(platformer_world: &PlatformerWorld, current: usize) -> usize {
@@ -414,6 +401,7 @@ pub fn play_custom_map(platformer_world: &mut PlatformerWorld, world: &mut World
414401
goal_radius: levels::GOAL_RADIUS,
415402
});
416403
platformer_world.resources.run.testing_from_editor = false;
404+
levels::apply_neutral_theme(world);
417405
world.resources.mesh_render_state.request_full_rebuild();
418406

419407
cutscene::begin(platformer_world, world);
@@ -455,6 +443,7 @@ pub fn enter_editor(platformer_world: &mut PlatformerWorld, world: &mut World) {
455443
}
456444
platformer_world.resources.run.play_level = None;
457445
platformer_world.resources.run.timing = false;
446+
levels::apply_neutral_theme(world);
458447
editor::enter(platformer_world, world);
459448
enter(platformer_world, world, Screen::Editor);
460449
}
@@ -473,6 +462,7 @@ pub fn test_play(platformer_world: &mut PlatformerWorld, world: &mut World) {
473462
});
474463
platformer_world.resources.run.play_mode = PlayMode::Custom(map.name.clone());
475464
platformer_world.resources.run.testing_from_editor = true;
465+
levels::apply_neutral_theme(world);
476466
world.resources.mesh_render_state.request_full_rebuild();
477467
begin_play(platformer_world, world);
478468
}

apps/platformer/src/systems/screens.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ pub mod editor;
33
pub mod hud;
44
pub mod menu_button;
55
pub mod pause;
6-
pub mod settings;
76
pub mod title;

0 commit comments

Comments
 (0)