extends CharacterBody3D class_name Player const JUMP_WEAPON_DIP = Vector3(0,-5,0) const AIR_TRANSITION_SPEED = 7 const GROUND_TRANSITION_SPEED = 7 const FLASHLIGHT_BRIGHTNESS = 100 const KICK_AMOUNT = 20 const LAND_CAMERA_TILT : Vector3 = Vector3(-1,0,0) const CROUCH_SPEED = 3.0 const SPRINT_SPEED = 15.0 const CROUCHED_POS = Vector3(0,-.1,0) const STAND_POS = Vector3(0,0.889,0) const HEAD_TILT_AMT = .06 const BOB_FREQ = 1.7 const BOB_AMP = 0.1 const ADS_POS = Vector3(0,-.05,-.3) const HELD_BUTTON_AMT = 1.0 #in seconds #JOYSTICK SETTINGS const JOYSTICK_SENSITIVITY = .06 var adj_sensitivity = JOYSTICK_SENSITIVITY const R_JOYSTICK_DEADZONE = 0.1 const L_JOYSTICK_DEADZONE = .2 const L_JOYSTICK_SENSITIVITY = .1 #Jumping const JUMP_VELOCITY = 7 const MAX_JUMPS = 2 var jumps_remaining var air_dash var gravity var is_climbing = false var ladder_center var moving_fast = false var moving_fast_top_speed = 0.0 var mouse_input : Vector2 var ads : bool var last_ground_pos @export_group("Game Settings") @export var AUDIO = true @export_group("Player Movement") @export var DASH_STAM_REQ = 10 @export var SENSITIVITY = .01 @export_subgroup("Head Bob & Gun Sway") @export var t_bob = 1.0 @export var weapon_holder : Node3D @export var weapon_sway_amount : float = .09 @export var weapon_rotation_amount : float = .07 @export_subgroup("FOV") @export var BASE_FOV : float = 80 @export var FOV_CHANGE = 1.5 ## Movement var input_dir ## GUNS AND AMMO var gun : Node var holstered_gun_id : int var gun_is_holstered = false var weapon_dip_pos = Vector3.ZERO var gun_folded = false var current_weapon_index var dead = false var hud_visible : bool = true var held_item : Node var held_item_cache = {} var held_item_rotation : float = 0 var gamespeed_controlled = false var crouched = false var flashlight_on : bool = false var can_wall_jump = true ##CACHED VARS var weapon_start_pos var held_key_check = 0.0 var def_weapon_holder_pos : Vector3 var weapon_holder_start_rot var weapon_holder_start_pos var controlled_elsewhere = false @onready var dead_player : Resource = load("res://assets/dead_cam.tscn") @onready var pause_menu: Control = $Head/Recoil/Camera3D/PauseMenu @onready var gun_ray = $Head/Recoil/Camera3D/GunRay @onready var level_control = get_tree().current_scene @onready var interact_ray = $Head/Recoil/Camera3D/InteractRay @onready var bullet_ray = $Head/Recoil/Camera3D/BulletRay @onready var gun_look_target: Marker3D = $Head/Recoil/Camera3D/BulletRay/GunLookTarget @onready var wall_ray_1: RayCast3D = $wall_ray1 @onready var wall_ray_2: RayCast3D = $wall_ray2 @onready var wall_ray_3: RayCast3D = $wall_ray3 @onready var hitmarker = load("res://hitmarker.tscn") @onready var animation_player: AnimationPlayer = $AnimationPlayer @onready var head = $Head @onready var camera = $Head/Recoil/Camera3D @onready var world_environment = $"../WorldEnvironment" @onready var pickup_sound = $Audio/PickupSound @onready var ear_wind = $Audio/EarWind @onready var land_sound: AudioStreamPlayer = $Audio/LandSound @onready var hurt_audio = $Audio/Hurt @onready var hit_indicator: AudioStreamPlayer = $Audio/HitIndicator @onready var health_indicator = $HealthIndicator @onready var stamina_counter = $Head/Recoil/Camera3D/StaminaCounter @onready var recoil: Node3D = $Head/Recoil @onready var weapon_spawner = $Head/Recoil/Camera3D/WeaponHolder/WeaponSpawner @onready var pick_up_detection = $pick_up_detection @onready var item_holder: Node3D = $Head/ItemHolder @onready var flashlight: SpotLight3D = $Head/Recoil/Camera3D/WeaponHolder/Flashlight @onready var flashlight_audio: AudioStreamPlayer3D = $Head/Recoil/Camera3D/WeaponHolder/FlashlightButton @onready var kick_audio: AudioStreamPlayer3D = $Head/Recoil/Camera3D/Audio/Kick @onready var weapon_pickup_audio: AudioStreamPlayer = $Audio/WeaponPickup @onready var motion_lines: ColorRect = $Head/Recoil/Camera3D/MotionLines @onready var moveable_holder: Node3D = $Head/Recoil/Camera3D/MoveableHolder @onready var stand_check: RayCast3D = $StandCheck @onready var enemy_killed_audio: AudioStreamPlayer = $Audio/EnemyKilled @onready var footstep_sound: AudioStreamPlayer3D = $Audio/FootstepSound @onready var standing_collision: CollisionShape3D = $StandingCollision @onready var crouching_collision: CollisionShape3D = $CrouchingCollision @onready var crouch_check: RayCast3D = $CrouchingCollision/CrouchCheck @onready var hit_head: AudioStreamPlayer3D = $Audio/HitHead @onready var crouch_audio: AudioStreamPlayer3D = $Audio/Crouch @onready var hud: Control = $Head/Recoil/Camera3D/HUD @onready var clock_sound: AudioStreamPlayer = $Audio/ClockSound @onready var weapon_select_menu: Control = $Head/Recoil/Camera3D/WeaponSelect @onready var wall_jump_timer: Timer = $WallJumpTimer @onready var remaining_stamina : float = level_control.gamemode.max_stamina func _ready(): level_control.player = self SignalBus.enemy_hit.connect(enemy_hit) SignalBus.enemy_killed.connect(enemy_killed) air_dash = level_control.gamemode.air_dash_max weapon_holder_start_rot = weapon_holder.rotation weapon_holder_start_pos = weapon_holder.position Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) var viewportWidth = get_viewport().size.x var viewportHeight = get_viewport().size.y health_indicator.size = Vector2(viewportWidth,viewportHeight) health_indicator.color = Color(0.471, 0, 0, 0) motion_lines.visible = false #Cache vars weapon_start_pos = weapon_spawner.position #turn off audio if unchecked in player if AUDIO == false: AudioServer.set_bus_volume_db(0,-80) func _input(event) -> void: if !controlled_elsewhere: if !level_control.paused: if event is InputEventMouseMotion: self.rotate_y(-event.relative.x * SENSITIVITY) head.rotate_x(-event.relative.y * SENSITIVITY) head.rotation.x = clamp(head.rotation.x, deg_to_rad(-90), deg_to_rad(85)) mouse_input = event.relative else: if event is InputEventMouseMotion: self.rotate_y(event.relative.x * .00001) head.rotate_x(event.relative.y * .00001) head.rotation.x = clamp(head.rotation.x, deg_to_rad(-90), deg_to_rad(85)) mouse_input = event.relative func _physics_process(delta): if !dead and !level_control.paused: # Head bob t_bob += delta * velocity.length() * float(is_on_floor()) camera.transform.origin = _headbob(t_bob) # FOV var velocity_clamped = clamp(velocity.length(), 0.5, SPRINT_SPEED) var target_fov = BASE_FOV + FOV_CHANGE * velocity_clamped camera.fov = lerp(camera.fov, target_fov, delta * 8) # Health Indicator var health_opacity = 1.5 - GameGlobals.health / level_control.gamemode.start_health if GameGlobals.health < (level_control.gamemode.start_health/2): health_indicator.color = lerp(Color(0.471, 0, 0, 0), Color(0.471, 0, 0, .25),health_opacity) else: health_indicator.color = Color(0.471, 0, 0, 0) # Moving Fast Sound var wind_volume = clamp(velocity.length()/20,0,1) #expected max velocity for effect ear_wind.volume_db = lerp(-80,0,wind_volume) if velocity.length() > 20: motion_lines.visible = true else: motion_lines.visible = false if moving_fast: #cache fastest speed if abs(velocity.y) > moving_fast_top_speed: moving_fast_top_speed = abs(velocity.y) #Land Sound if velocity.y < .1 and self.is_on_floor() and moving_fast == true: var land_volume = clamp( moving_fast_top_speed - 20 ,-10,0) var recoil_amount = Vector3(-.3,0,0) land_sound.volume_db = land_volume land_sound.play() SignalBus.emit_signal("suspicious_sound",global_position,10,10) Input.start_joy_vibration(0,.1,.1,.1) recoil.add_recoil(recoil_amount,10,10) moving_fast_top_speed = 0.0 moving_fast = false # Game Speed if !level_control.paused: if Input.is_action_pressed("slow_down") and remaining_stamina > 0 : ads = true if !gamespeed_controlled: Engine.time_scale = lerp(Engine.time_scale, level_control.gamemode.time_slowed_speed, (delta * 50) / Engine.time_scale) AudioServer.set_bus_effect_enabled(0,0,true) clock_sound.play() if remaining_stamina > 0: remaining_stamina = clamp(remaining_stamina - ((delta * level_control.gamemode.stamina_drain) / Engine.time_scale),0,level_control.gamemode.max_stamina) else: ads = false if !gamespeed_controlled: Engine.time_scale = lerp(Engine.time_scale, 1.0, (delta * 50)/Engine.time_scale) clock_sound.stop() AudioServer.set_bus_effect_enabled(0,0,false) if remaining_stamina < level_control.gamemode.max_stamina and !Input.is_action_pressed("slow_down"): if level_control.gamemode.stamina_regen == true: remaining_stamina = clamp(remaining_stamina + (delta * level_control.gamemode.stamina_gain/Engine.time_scale), 0, level_control.gamemode.max_stamina) if Input.is_action_pressed("inspect") and !gun.anim_player.is_playing(): gun.anim_player.play("inspect") if Input.is_action_just_pressed("flashlight"): if flashlight_on: flashlight_on = false flashlight_audio.play() else: flashlight_on = true flashlight_audio.play() # Gun folding out of the way if gun_ray.is_colliding(): var distance_to_wall = 1 - (gun_ray.global_position.distance_to(gun_ray.get_collision_point()) / abs(gun_ray.target_position.z)) var rotation_target = lerp(weapon_holder.rotation, Vector3(2, -1, -1), distance_to_wall) var position_target = lerp(weapon_holder.position, Vector3(-.02,-.1,.06),distance_to_wall) weapon_holder.rotation = lerp(weapon_holder.rotation, rotation_target, delta * 7) weapon_holder.position = lerp(weapon_holder.position, position_target, delta * 7) gun_folded = true elif !gun_ray.is_colliding(): weapon_holder.rotation = lerp(weapon_holder.rotation, weapon_holder_start_rot,delta*7) weapon_holder.position = lerp(weapon_holder.position, weapon_holder_start_pos,delta*7) gun_folded = false #Weapon Swap Up if Input.is_action_just_pressed("scroll_up"): if held_item != null: held_item_rotation += deg_to_rad(45) print("HELD ITEM ROTATION : ",held_item_rotation) if gun != null: if !gun.anim_player.is_playing(): if held_item == null: if GameGlobals.held_guns.size() > 1: weapon_select(GameGlobals.current_gun_index + 1) else: if gun_is_holstered: holster_gun(false) #Weapon Swap Down if Input.is_action_just_pressed("scroll_down"): if gun != null: if !gun.anim_player.is_playing(): if held_item == null: if GameGlobals.held_guns.size() > 1: weapon_select(GameGlobals.current_gun_index - 1) else: held_item_rotation -= deg_to_rad(45) else: if gun_is_holstered: holster_gun(false) for i in range(1,9): if Input.is_action_just_pressed("numb_" + str(i)): if gun != null: if !gun.anim_player.is_playing(): if i-1 != GameGlobals.current_gun_index and i <= GameGlobals.held_guns.size(): weapon_select((i - 1)) if Input.is_action_just_pressed("holster"): if gun_is_holstered: holster_gun(false) else: holster_gun(true) if Input.is_action_just_pressed("weapon_select"): weapon_select_menu.open() controlled_elsewhere = true gamespeed_controlled = true Engine.time_scale = .01 elif Input.is_action_just_released("weapon_select"): var selection = weapon_select_menu.close() if selection != null and selection != GameGlobals.current_gun_index : weapon_select(selection) Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) gamespeed_controlled = false await get_tree().create_timer(.2).timeout controlled_elsewhere = false #interact button if Input.is_action_just_pressed("interact"): if held_item != null: release_moveable() else: if interact_ray.is_colliding(): var body = interact_ray.get_collider() if interact_ray.get_collider().get_class() == "RigidBody3D": grab_moveable(body) elif interact_ray.get_collider().has_method("interact"): body.interact() held_key_check = 0.0 #kick if Input.is_action_just_pressed("kick"): if !animation_player.is_playing(): animation_player.play("punch") if Input.is_action_just_pressed("kill_self"): GameGlobals.health = 0 if GameGlobals.health <= 0: level_control.die() cache_ground_pos() focus_on_target() joypad_look() aim_down_sights(delta) flashlight_toggle() hold_item(delta) move_and_slide() weapon_tilt(input_dir, delta) weapon_sway(delta) weapon_bob(velocity.length(), delta) ## MOVEMENT func movement_input(): ## HANDLE MOVEMENT DIRECTION var direction #GetKB Input input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down") #Get Joy Input input_dir += joypad_walk() if abs(Input.get_joy_axis(0,JOY_AXIS_LEFT_X)) > L_JOYSTICK_SENSITIVITY or abs(Input.get_joy_axis(0,JOY_AXIS_LEFT_Y)) > L_JOYSTICK_SENSITIVITY: direction = (self.transform.basis * Vector3(input_dir.x, 0, input_dir.y)) else: direction = (self.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() return direction func joypad_walk(): # Joypad right stick look control var dir_out = Vector2(0,0) var xAxis = Input.get_joy_axis(0,JOY_AXIS_LEFT_X) var yAxis = Input.get_joy_axis(0,JOY_AXIS_LEFT_Y) if abs(xAxis) > L_JOYSTICK_DEADZONE: dir_out.x = xAxis if abs(yAxis) > L_JOYSTICK_DEADZONE: dir_out.y = yAxis return dir_out func joypad_look(): if !controlled_elsewhere: # Joypad right stick look control var xAxis = Input.get_joy_axis(0,JOY_AXIS_RIGHT_X) var yAxis = Input.get_joy_axis(0,JOY_AXIS_RIGHT_Y) var aim_assist if abs(xAxis) > R_JOYSTICK_DEADZONE: self.rotate_y(-xAxis * JOYSTICK_SENSITIVITY * 1) if abs(yAxis) > R_JOYSTICK_DEADZONE: head.rotate_x(-yAxis * JOYSTICK_SENSITIVITY * 1) head.rotation.x = clamp(head.rotation.x, deg_to_rad(-90), deg_to_rad(85)) func cache_ground_pos(): if is_on_floor(): last_ground_pos = global_position func punch(): if interact_ray.is_colliding(): if interact_ray.get_collider().has_method("hit"): hit_indicator.play() interact_ray.get_collider().hit(3) if interact_ray.get_collider().get_class() == "RigidBody3D": kick_audio.play() interact_ray.get_collider().linear_velocity += interact_ray.global_transform.basis * Vector3(0,0, -KICK_AMOUNT) if held_item != null: release_moveable() if held_item != null: kick_audio.play() held_item.linear_velocity += interact_ray.global_transform.basis * Vector3(0,0, -KICK_AMOUNT) release_moveable() func _headbob(time) -> Vector3: var pos = Vector3.ZERO pos.y = sin(time * BOB_FREQ) * BOB_AMP pos.x = cos(time * BOB_FREQ / 2) * BOB_AMP if pos.y >= .07 and is_on_floor() and velocity.length() >= 1 and !footstep_sound.is_playing(): footstep_sound.play() if !crouched: SignalBus.emit_signal("suspicious_sound",global_position,10,7) else: SignalBus.emit_signal("suspicious_sound",global_position,5,1) return pos func ladder_collide(): if is_climbing == true: gravity = 0.0 else: gravity = level_control.gamemode.gravity func wall_jump(): if can_wall_jump == true: if wall_ray_1.is_colliding() or wall_ray_2.is_colliding() or wall_ray_3.is_colliding(): can_wall_jump = false wall_jump_timer.start() return true else: return false else: return false func _on_wall_jump_timer_timeout() -> void: can_wall_jump = true ## VARIOUS ACTIONS func flashlight_toggle(): if flashlight_on: flashlight.light_energy = FLASHLIGHT_BRIGHTNESS else: flashlight.light_energy = 0 func aim_down_sights(delta): if gun != null: if ads: if gun.weapon_info.ads == true: camera.fov = lerp(camera.fov,BASE_FOV - float(gun.weapon_info.fov_zoom_amt),(delta * 5)/Engine.time_scale) gun.position = lerp(gun.position,ADS_POS,delta * 10 / Engine.time_scale) else: weapon_holder.rotation = lerp(weapon_holder.rotation,Vector3(0,0,0),(delta * 10)/Engine.time_scale) gun.position = lerp(gun.position, weapon_start_pos,(delta * 4)/Engine.time_scale) camera.fov = lerp(camera.fov,BASE_FOV - float(gun.weapon_info.fov_zoom_amt),(delta * 5)/Engine.time_scale) else: gun.position = lerp(gun.position, weapon_start_pos,(delta * 100)/Engine.time_scale) func grab_moveable(body): held_item_cache = { "gravity_scale" : body.gravity_scale } body.gravity_scale = 0 moveable_holder.add_child(body) body.global_position = moveable_holder.global_position #for i in body.get_children(): #if i.get_class() == "CollisionShape3D": #i.collision_layer = true held_item = body func hold_item(_delta): if held_item != null: const FORCE = 10 var dir = moveable_holder.global_position - held_item.global_position held_item.linear_velocity = dir * FORCE + self.velocity held_item.look_at(camera.global_position,Vector3(0,1,0)) func release_moveable(): held_item.gravity_scale = held_item_cache["gravity_scale"] #for i in held_item.get_children(): #if i.get_class() == "CollisionShape3D": #i.disabled = false get_tree().current_scene.add_child(held_item) held_item = null ## GUNS AND AMMO func holster_gun(holster): if holster: gun_is_holstered = true if gun != null: holstered_gun_id = GameGlobals.current_gun_index gun.anim_player.play("swap_out") else: gun_is_holstered = false if holstered_gun_id != null: weapon_select(holstered_gun_id) ## MISC func _on_pick_up_detection_body_entered(body): if body.is_in_group("item_pickup"): pass #if body.pickupable: #pickup_apply(body.pickup_type,body.ammo_type,body.value) if body.is_in_group("weapon_pickup"): body.picked_up() weapon_pickup_audio.play() Input.start_joy_vibration(0,.1,.1,.1) func _on_pick_up_magnet_body_entered(body): if body.is_in_group("item_pickup") and body.is_in_group("magnet"): if body.magnetable == true: body.player_follow = self body.player = self body.collision_shape.disabled = true func pickup_apply(type,ammo_type,value): pickup_sound.play() Input.start_joy_vibration(0,.1,.1,.1) hud.pick_up_notif(type,ammo_type,value) match type: 0: #AMMO if GameGlobals.ammo_reserve.has(str(ammo_type)): GameGlobals.ammo_reserve[str(ammo_type)] += value else: GameGlobals.ammo_reserve[str(ammo_type)] = value 1: #STAMINA remaining_stamina = clamp(remaining_stamina + value,0,100) 2: #HEALTH GameGlobals.health = clamp(GameGlobals.health + value,0,level_control.gamemode.start_health) 3: #MONEY GameGlobals.money += value * level_control.gamemode.money_multiplier SignalBus.emit_signal("money_changed") func focus_on_target(): if Engine.time_scale < 1: const BLUR_AMOUNT = .1 camera.attributes.dof_blur_far_enabled = true camera.attributes.dof_blur_far_distance = camera.global_position.distance_to(bullet_ray.get_collision_point()) + 5.0 camera.attributes.dof_blur_near_enabled = true camera.attributes.dof_blur_amount = lerp(0.0,BLUR_AMOUNT,1.0 - Engine.time_scale) else: camera.attributes.dof_blur_far_enabled = false camera.attributes.dof_blur_near_enabled = false func weapon_tilt(input, delta): if input: if !ads: if weapon_holder: weapon_holder.rotation.z = lerp(weapon_holder.rotation.z, -input.x * weapon_rotation_amount * 10, 4 * delta) if camera: camera.rotation.z = lerp(camera.rotation.z, -input.x * HEAD_TILT_AMT, 5 * delta) func weapon_sway(delta): #aim gun at center screen if gun != null: if !gun_ray.is_colliding(): gun.look_at(gun_look_target.global_position) #apply sway var joy_input = Vector2(Input.get_joy_axis(0,JOY_AXIS_RIGHT_X)*5,Input.get_joy_axis(0,JOY_AXIS_RIGHT_Y)*5) mouse_input = lerp(mouse_input, Vector2.ZERO, 10 * delta) joy_input = lerp(joy_input,Vector2.ZERO,10 * delta) weapon_holder.rotation.x = lerp(weapon_holder.rotation.x, (mouse_input.y + joy_input.y) * weapon_sway_amount, 5 * delta) weapon_holder.rotation.y = lerp(weapon_holder.rotation.y, (mouse_input.x + joy_input.x) * weapon_sway_amount, 5 * delta) # #else: #if gun != null: #if gun.weapon_info.ads == true: #weapon_holder.rotation = Vector3.ZERO func weapon_bob(vel : float, delta): if weapon_holder: if gun != null and !ads and !gun.weapon_info.ads: weapon_holder.global_position.y += -clamp(velocity.y * .15,-1,1) * delta if vel > 2 and is_on_floor(): var bob_amount : float = 0.05 var bob_freq : float = 0.01 weapon_holder.position.y = lerp(weapon_holder.position.y, def_weapon_holder_pos.y + sin(Time.get_ticks_msec() * bob_freq) * bob_amount, velocity.length() * delta) weapon_holder.position.x = lerp(weapon_holder.position.x, def_weapon_holder_pos.x + sin(Time.get_ticks_msec() * bob_freq) * bob_amount, velocity.length() * delta) else: weapon_holder.position.y = lerp(weapon_holder.position.y, def_weapon_holder_pos.y, .1 * delta) weapon_holder.position.x = lerp(weapon_holder.position.x, def_weapon_holder_pos.x, .1 * delta) func weapon_select(gun_id): if gun != null: gun.anim_player.play("swap_out") if gun_id != null: level_control.gun_spawn(gun_id) func enemy_hit(): var hitmarker_spawn = hitmarker.instantiate() camera.add_child(hitmarker_spawn) hit_indicator.play() func enemy_killed(): if GameGlobals.enemies_killed: GameGlobals.enemies_killed += 1 else: GameGlobals.enemies_killed = 1 SaveLoad.save_persistent_data() enemy_killed_audio.play() func toggle_hud(hud_on): hud.visible = hud_on func hit(damage, fired_by, target_type): SignalBus.emit_signal("player_hit") GameGlobals.health -= damage level_control.last_hit = fired_by level_control.target_type = target_type recoil.add_recoil(Vector3(.5,.1,.1),10,10) if held_item != null: held_item.linear_velocity.y += 5 release_moveable() hurt_audio.play() health_indicator.color = Color(0.471, 0, 0, .25) await get_tree().create_timer(.15).timeout health_indicator.color = Color(0.471, 0, 0, 0) func save(): var save_dict = { "filename" : get_scene_file_path(), "parent" : get_parent().get_path(), "pos_x" : position.x, "pos_y" : position.y, "pos_z" : position.z, "rot_x" : rotation.x, "rot_y" : rotation.y, "rot_z" : rotation.z, "crouched" : crouched, "flashlight_on" : flashlight_on, } return save_dict