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 :: 0 PLAYER_GUN_DIST :: 7 // how far out to hold the gun PLAYER_GUN_KICKBACK :: 4 // how far to move the gun back when you shoot PLAYER_SCARF_DIST :: 5 Player_State :: enum { Default, Dash, } // there will only ever be one player, so just make it a global :) player: struct { body: phys.Body_Handle, sprite: Sprite, gun: struct { sprite: Sprite, kickback: f32, }, scarf: Tail(7), jump_buffer: f32, coyote_time: f32, dash_cooldown: f32, dash_timer: f32, state: Player_State, outside_room: bool, has_double_jumped: bool, owns_double_jump: bool, owns_dash: bool, } init_player :: proc() { body := phys.make_body( phys.Rect{{-4, -8}, {8, 16}}, layers = {.Player}, mask = {.Default} ) player.body = body init_sprite(&player.sprite, .Player) player.sprite.offset = Vec2 { math.floor(f32(player.sprite.width / 2)), math.floor(f32(player.sprite.height / 2)), } init_sprite(&player.gun.sprite, .Pistol) player.gun.sprite.offset = Vec2 { -PLAYER_GUN_DIST, math.round(f32(player.gun.sprite.height / 2)), } player.scarf = { point_max_dist = 2, point_rad = 2, color_start = [4]f32{0.18, 0.13, 0.18, 1} * 0.5, color_end = {0.18, 0.13, 0.18, 1}, } } deinit_player :: proc() { phys.remove_body(player.body) } @(private = "file") _get_input_dir :: proc() -> f32 { if !point_inside_room(phys.get_position(player.body)) { return 0 } 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 player.owns_dash && is_keybind_just_down(actions.dash) && player.dash_cooldown <= 0 { _enter_dash() return } // body := phys.get_body(player.body_handle) pos := phys.get_position(player.body) vel := phys.get_velocity(player.body) collisions := phys.get_collisions(player.body) if .Down in collisions { player.coyote_time = PLAYER_COYOTE_TIME player.has_double_jumped = false } else { switch vel.y { case -math.INF_F32..<-50: set_sprite_active_tag(&player.sprite, "jump_up") case 50.. 0 { if player.coyote_time > 0 { vel.y = -PLAYER_JUMP_FORCE player.jump_buffer = 0 player.coyote_time = 0 } else if player.owns_double_jump && !player.has_double_jumped { vel.y = -PLAYER_DOUBLE_JUMP_FORCE player.has_double_jumped = true player.jump_buffer = 0 } } if .Down not_in collisions && !is_keybind_down(actions.jump) && vel.y < PLAYER_JUMP_RELEASE_CUT { vel.y = PLAYER_JUMP_RELEASE_CUT } vel.x = math.lerp( vel.x, input * PLAYER_SPEED, math.pow(0.5, dt * PLAYER_ACCEL), ) vel.y = math.min(vel.y + GRAVITY * dt, TERMINAL_VELOCITY) phys.set_velocity(player.body, vel) phys.update_body(player.body) } @(private = "file") _enter_dash :: proc() { input := _get_input_dir() if input == 0 { // the sprite x scale is the direction the player is facing :) input = math.sign(player.sprite.scale.x) } phys.set_velocity( player.body, {input * 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) player.dash_timer -= dt if player.dash_timer <= 0 || phys.get_collisions(player.body) != nil { _exit_dash() } } @(private = "file") _exit_dash :: proc() { player.state = .Default player.dash_cooldown = PLAYER_DASH_COOLDOWN vel := phys.get_velocity(player.body) phys.set_velocity(player.body, vel / 2) } @(private = "file") _get_camera_target_pos :: proc() -> Vec2 { pos := phys.get_position(player.body) - SCREEN_SIZE * 0.5 room_size := Vec2{ f32(current_room.width), f32(current_room.height), } camera_bounds_min := Vec2{0, 0} camera_bounds_max := room_size - SCREEN_SIZE pos.x = math.clamp(pos.x, camera_bounds_min.x, camera_bounds_max.x) pos.y = math.clamp(pos.y, camera_bounds_min.y, camera_bounds_max.y) return pos } @(private = "file") _change_rooms :: proc() { pos := phys.get_position(player.body) if point_inside_room(pos) { return } prev_room_pos := Vec2{f32(current_room.x), f32(current_room.y)} changed := open_room_at({i32(pos.x), i32(pos.y)}) if changed { new_room_pos := Vec2{f32(current_room.x), f32(current_room.y)} diff := prev_room_pos - new_room_pos new_pos := pos + diff phys.set_position(player.body, new_pos) if diff.y > 0 { vel := phys.get_velocity(player.body) vel.y = -PLAYER_JUMP_FORCE phys.set_velocity(player.body, vel.y) } else if diff.y > 0 { vel := phys.get_velocity(player.body) vel.y = 0 phys.set_velocity(player.body, vel.y) } new_cam_pos := _get_camera_target_pos() state.camera.target += diff state.camera_target += diff } } update_player :: proc(dt: f32) { switch player.state { case .Default: _default_state(dt) case .Dash: _dash_state(dt) } pos := phys.get_position(player.body) player.sprite.pos = pos update_sprite(&player.sprite, dt) mouse_dir := linalg.normalize0(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.pos = pos - Vec2{0, PLAYER_GUN_HEIGHT} player.gun.sprite.pos += -mouse_dir * player.gun.kickback player.gun.kickback = dt_lerp(player.gun.kickback, 0, 5) 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 state.camera_target = _get_camera_target_pos() scarf_dist: f32 = PLAYER_SCARF_DIST if _get_input_dir() != 0 { scarf_dist -= 1 } scarf_pos := linalg.round( pos + Vec2{-scarf_dist * player.sprite.scale.x, 0}, ) set_tail_position(&player.scarf, scarf_pos, dt) _change_rooms() } draw_player :: proc() { draw_tail(player.scarf) draw_sprite(player.sprite) if player.state != .Dash { 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_position(player.body, obj.pos) }