From cab0d6e99d96f621e6efcf1ed6b5537cf122ad96 Mon Sep 17 00:00:00 2001 From: iamcheeseman <[hidden email]> Date: Sun, 15 Feb 2026 13:17:36 -0500 Subject: Wall sliding/jumping --- src/player.odin | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 8 deletions(-) (limited to 'src/player.odin') diff --git a/src/player.odin b/src/player.odin index 2f82e2f..7a0ee45 100644 --- a/src/player.odin +++ b/src/player.odin @@ -10,6 +10,8 @@ import "fw" Player_State :: enum { Default, Dash, + Wall_Slide, + Wall_Jump, } // there will only ever be one player, so just make it a global :) @@ -25,6 +27,8 @@ player: struct { coyote_time: f32, dash_cooldown: f32, dash_timer: f32, + wall_jump_timer: f32, + wall_slide_dir: f32, state: Player_State, outside_room: bool, has_double_jumped: bool, @@ -81,6 +85,28 @@ _get_input_dir :: proc() -> f32 { return input } +@(private = "file") +_can_wallslide :: proc(input: f32) -> bool { + pos := phys.get_position(player.body) + rect := phys.get_rect(player.body) + rect.start += pos + + top_start := rect.start + {rect.size.x * 0.5, 0} + top_end := top_start + {input * 8, 0} + + bot_start := rect.start + {rect.size.x * 0.5, rect.size.y} + bot_end := bot_start + {input * 8, 0} + + top_rc := phys.make_raycast(top_start, top_end) + bot_rc := phys.make_raycast(bot_start, bot_end) + + top_touching := phys.is_colliding(top_rc) + bot_touching := phys.is_colliding(bot_rc) + + vel := phys.get_velocity(player.body) + return vel.y > 0 && top_touching && bot_touching +} + @(private = "file") _default_state :: proc(dt: f32) { input := _get_input_dir() @@ -119,6 +145,10 @@ _default_state :: proc(dt: f32) { case: set_sprite_active_tag(&player.sprite, "jump_trans") } + + if _can_wallslide(input) { + _enter_wall_slide() + } } rel_mouse_pos := fw.get_mouse_pos() - pos @@ -213,6 +243,69 @@ _exit_dash :: proc() { phys.set_velocity(player.body, vel / 2) } +@(private = "file") +_enter_wall_slide :: proc() { + player.has_double_jumped = false + player.wall_slide_dir = _get_input_dir() + player.state = .Wall_Slide +} + +@(private = "file") +_wall_slide_state :: proc(dt: f32) { + set_sprite_active_tag(&player.sprite, "idle") + + input := _get_input_dir() + + collisions := phys.get_collisions(player.body) + + if !_can_wallslide(player.wall_slide_dir) { + player.state = .Default + } + + vel := phys.get_velocity(player.body) + + vel.x = dt_lerp( + vel.x, + input * PLAYER_SPEED, + PLAYER_ACCEL, + ) + vel.y = PLAYER_WALL_SLIDE_SPEED + + phys.set_velocity(player.body, vel) + + phys.update_body(player.body) + + if fw.is_keybind_just_down(actions.jump) { + _enter_wall_jump() + } +} + +@(private = "file") +_enter_wall_jump :: proc() { + vel := phys.get_velocity(player.body) + vel.x = -player.wall_slide_dir * PLAYER_WALL_JUMP_FORCE + vel.y = -PLAYER_JUMP_FORCE + phys.set_velocity(player.body, vel) + + player.state = .Wall_Jump + + player.wall_jump_timer = PLAYER_WALL_JUMP_TIME +} + +@(private = "file") +_wall_jump_state :: proc(dt: f32) { + phys.update_body(player.body) + + player.wall_jump_timer -= dt + if player.wall_jump_timer <= 0 { + vel := phys.get_velocity(player.body) + vel.y = PLAYER_JUMP_RELEASE_CUT * 2 + phys.set_velocity(player.body, vel) + + player.state = .Default + } +} + @(private = "file") _get_camera_target_pos :: proc() -> Vec2 { pos := phys.get_position(player.body) - SCREEN_SIZE * 0.5 @@ -268,6 +361,8 @@ update_player :: proc(dt: f32) { switch player.state { case .Default: _default_state(dt) case .Dash: _dash_state(dt) + case .Wall_Slide: _wall_slide_state(dt) + case .Wall_Jump: _wall_jump_state(dt) } pos := phys.get_position(player.body) @@ -313,16 +408,35 @@ draw_player :: proc() { draw_player_fg :: proc() { rc_start := phys.get_position(player.body) dir := Vec2{math.cos(player.gun.sprite.rotation), math.sin(player.gun.sprite.rotation)} - bodies := phys.get_colliding_bodies(phys.make_raycast(rc_start, dir), allocator = context.temp_allocator) - is_colliding := len(bodies) > 0 - - color := fw.GREEN if is_colliding else fw.RED - fw.draw_line(rc_start, rc_start + dir * 500, color = color) + rc_end := rc_start + dir * 32 + collision, is_colliding := phys.get_closest_raycast_collision( + phys.make_raycast(rc_start, rc_end), + ) - for body in bodies { - rect := phys.get_rect(body) - fw.draw_rect(rect.start, rect.size, color = fw.RED) + if is_colliding { + rect := phys.get_rect(collision.body) + fw.draw_rect(rect.start, rect.size, color = Color{0, 1, 1, 0.5}) + fw.draw_line(rc_start, rc_end, color = fw.GREEN) + fw.draw_rect(collision.enter, {1, 1}, color = fw.RED) + fw.draw_rect(collision.exit, {1, 1}, color = fw.BLUE) } + + input := _get_input_dir() + pos := phys.get_position(player.body) + rect := phys.get_rect(player.body) + rect.start += pos + + top_start := rect.start + {rect.size.x * 0.5, 0} + top_end := top_start + {input * 8, 0} + + bot_start := rect.start + {rect.size.x * 0.5, rect.size.y} + bot_end := bot_start + {input * 8, 0} + + fw.draw_line(top_start, top_end) + fw.draw_line(bot_start, bot_end) + + // top_rc := phys.make_raycast(top_start, top_end) + // bot_rc := phys.make_raycast(bot_start, bot_end) } @(private = "file") -- cgit v1.3-2-g0d8e