package demonchime import "core:math" import "core:math/linalg" import "core:log" import rl "vendor:raylib" import "phys" PLAYER_SPEED :: 100 PLAYER_ACCEL :: 10 PLAYER_JUMP_FORCE :: 350 PLAYER_DOUBLE_JUMP_FORCE :: 250 PLAYER_JUMP_BUFFERING :: 0.07 PLAYER_COYOTE_TIME :: 0.06 PLAYER_JUMP_RELEASE_CUT :: -100 PLAYER_DASH_SPEED :: 500 PLAYER_DASH_TIME :: 0.15 PLAYER_DASH_COOLDOWN :: 0.3 PLAYER_GUN_HEIGHT :: 7 PLAYER_GUN_DIST :: 5 // how far out to hold the gun Player_State :: enum { DEFAULT, DASH, } // there will only ever be one player, so just make it a global :) player: struct { body_handle: phys.Body_Handle, sprite: Sprite, gun_sprite: Sprite, jump_buffer: f32, coyote_time: f32, dash_cooldown: f32, dash_timer: f32, state: Player_State, outside_room: bool, has_double_jumped: bool, } init_player :: proc() { handle, body := phys.make_body( phys.Rect{{-4, -16}, {8, 16}}, layers = {.PLAYER}, mask = {.DEFAULT} ) player.body_handle = handle phys.set_body_position(handle, Vec2{50, 100}) init_sprite(&player.sprite, .PLAYER) player.sprite.offset = Vec2 { math.floor(f32(player.sprite.width / 2)), f32(player.sprite.height), } init_sprite(&player.gun_sprite, .PISTOL) player.gun_sprite.offset = Vec2 { -PLAYER_GUN_DIST, math.round(f32(player.gun_sprite.height / 2)), } } deinit_player :: proc() { phys.remove_body(player.body_handle) } @(private = "file") _get_input_dir :: proc() -> f32 { input: f32 if is_keybind_down(actions.move_left) { input -= 1 } if is_keybind_down(actions.move_right) { input += 1 } return input } @(private = "file") _default_state :: proc(dt: f32) { input := _get_input_dir() if input != 0 { set_sprite_active_tag(&player.sprite, "run") } else { set_sprite_active_tag(&player.sprite, "idle") } if is_keybind_just_down(actions.jump) { player.jump_buffer = PLAYER_JUMP_BUFFERING } if is_keybind_just_down(actions.dash) && player.dash_cooldown <= 0 { _enter_dash() return } body := phys.get_body(player.body_handle) if .DOWN in body.collisions { player.coyote_time = PLAYER_COYOTE_TIME player.has_double_jumped = false } else { switch body.vel.y { case -math.INF_F32..<-50: set_sprite_active_tag(&player.sprite, "jump_up") case 50.. 0 { if player.coyote_time > 0 { body.vel.y = -PLAYER_JUMP_FORCE player.jump_buffer = 0 player.coyote_time = 0 } else if !player.has_double_jumped { body.vel.y = -PLAYER_DOUBLE_JUMP_FORCE player.has_double_jumped = true player.jump_buffer = 0 } } if .DOWN not_in body.collisions && !is_keybind_down(actions.jump) && body.vel.y < PLAYER_JUMP_RELEASE_CUT { body.vel.y = PLAYER_JUMP_RELEASE_CUT } body.vel.x = math.lerp( body.vel.x, input * PLAYER_SPEED, math.pow(0.5, dt * PLAYER_ACCEL), ) body.vel.y = math.min(body.vel.y + GRAVITY * dt, TERMINAL_VELOCITY) phys.update_body(player.body_handle) } @(private = "file") _enter_dash :: proc() { body := phys.get_body(player.body_handle) // the sprite x scale is the direction the player is facing :) body.vel = {math.sign(player.sprite.scale.x) * PLAYER_DASH_SPEED, 0} player.dash_timer = PLAYER_DASH_TIME player.state = .DASH } @(private = "file") _dash_state :: proc(dt: f32) { phys.update_body(player.body_handle) player.dash_timer -= dt body := phys.get_body(player.body_handle) if player.dash_timer <= 0 || body.collisions != {} { _exit_dash() } } @(private = "file") _exit_dash :: proc() { player.state = .DEFAULT player.dash_cooldown = PLAYER_DASH_COOLDOWN body := phys.get_body(player.body_handle) body.vel /= 2 } @(private = "file") _change_rooms :: proc() { body := phys.get_body(player.body_handle) width := f32(current_room.width) height := f32(current_room.height) if body.pos.x < 0 || body.pos.x > width || body.pos.y < 0 || body.pos.y > height { prev_room_pos := Vec2{f32(current_room.x), f32(current_room.y)} changed := open_room_at({i32(body.pos.x), i32(body.pos.y)}) if changed { new_room_pos := Vec2{f32(current_room.x), f32(current_room.y)} diff := prev_room_pos - new_room_pos body = phys.get_body(player.body_handle) new_pos := body.pos + diff - {0, 0} phys.set_body_position(player.body_handle, new_pos) } } } update_player :: proc(dt: f32) { switch player.state { case .DEFAULT: _default_state(dt) case .DASH: _dash_state(dt) } body := phys.get_body(player.body_handle) player.sprite.pos = body.pos update_sprite(&player.sprite, dt) player.gun_sprite.pos = body.pos - Vec2{0, PLAYER_GUN_HEIGHT} mouse_dir := get_mouse_pos() - player.gun_sprite.pos player.gun_sprite.rotation = math.atan2( mouse_dir.y, mouse_dir.x, ) * math.DEG_PER_RAD player.gun_sprite.scale.y = -1 if mouse_dir.x < 0 else 1 player.dash_cooldown -= dt player.jump_buffer -= dt player.coyote_time -= dt _change_rooms() } draw_player :: proc() { draw_sprite(player.sprite) draw_sprite(player.gun_sprite) } @(private = "file") already_spawned_player := false object_spawner_player_spawn :: proc(obj: Object_Resource) { if already_spawned_player { return } already_spawned_player = true phys.set_body_position(player.body_handle, obj.pos) }