extends CharacterBody3D # TODO: Add descriptions for each value @export_category("Character") @export var start_rotation : float = 0 var start_rotated : bool = false @export var max_health : float = 100.0 var hp : float var playerallied : bool = true var currentgun : String = "pistol" @export var base_speed : float = 10.0 @export var sprint_speed : float = 6.0 @export var crouch_speed : float = 6.0 @export var acceleration : float = 10.0 @export var jump_velocity : float = 6.0 @export var mouse_sensitivity : float = 0.1 @export var walljump_cooldown : float = 0.75 @export var wallbounce_multiplier : float = 0.3 @export var aircontrol_multiplier : float = 0.5 @export var aircontrol_delay : float = 0.5 @export var coyotetime : float = 0.3 var coyotechance : bool = false @export var dash_cooldown : float = 2.0 @export var dash_speedmultiplier : float = 3.0 @export var dash_distance : float = 5.0 var dash_spent : bool = false var dash_active : bool = false var dash_startpos : Vector3 = Vector3(0, 0, 0) @export var physics_push_force : float = 1.0 var holding_object : bool = false var held_object : RigidBody3D @export var object_hurl_force : float = 40.0 @export var object_grab_distance : float = 8.0 @export var object_hold_height : float = 1.0 @export var gun_damage : float = 6 @export var gun_firedelay : float = 0.2 @export var gun_physics_nudge_multiplier : float = 5 var stomping : bool = false var stomp_startingheight : float var stomp_speed : float = 25 var stomp_radius : float = 6 var stomp_radiusthreshold : float = 2 var stomp_radiusincrement : float = 1 var stomp_maxradius : float = 12 var stomp_damagepermeter : float = 3 var stomp_mindamage : float = 4 var stomp_maxdamage : float = 16 @export_group("Nodes") @export var HEAD : Node3D @export var CAMERA : Camera3D @export var CAMERA_ANIMATION : AnimationPlayer @export_group("Controls") # We are using UI controls because they are built into Godot Engine so they can be used right away @export var JUMP : String = "move_jump" @export var LEFT : String = "move_left" @export var RIGHT : String = "move_right" @export var FORWARD : String = "move_forward" @export var BACKWARD : String = "move_backward" @export var PAUSE : String = "ui_cancel" @export var CROUCH : String = "move_crouch" @export var DASH : String = "move_dash" @export var TELEK : String = "action_telekinesis" @export var SHOOT : String = "action_shoot" @export var SPRINT : String @export_group("Feature Settings") @export var immobile : bool = false @export var jumping_enabled : bool = true @export var in_air_momentum : bool = true @export var motion_smoothing : bool = true @export var sprint_enabled : bool = false @export var crouch_enabled : bool = true @export_enum("Hold to Crouch", "Toggle Crouch") var crouch_mode : int = 0 @export_enum("Hold to Sprint", "Toggle Sprint") var sprint_mode : int = 0 @export var dynamic_fov : bool = false @export var continuous_jumping : bool = false @export var view_bobbing : bool = false # Member variables var speed : float = base_speed var is_crouching : bool = false var is_sprinting : bool = false var walljump_ready : bool = true var enemies : Array[Node] = [] # Get the gravity from the project settings to be synced with RigidBody nodes var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity") # Don't set this as a const, see the gravity section in _physics_process const tex_pistol_neutral = preload("res://sprites/fpguns/pistol_neutral.png") const tex_pistol_recoil = preload("res://sprites/fpguns/pistol_recoil.png") const tex_telehand_grip = preload("res://sprites/fpguns/telehand_grip.png") const tex_telehand_pull = preload("res://sprites/fpguns/telehand_pull.png") const tex_telehand_throw = preload("res://sprites/fpguns/telehand_throw.png") func _ready(): HEAD = $Head CAMERA = $Head/Camera CAMERA_ANIMATION = $Head/camera_animation Input.mouse_mode = Input.MOUSE_MODE_CAPTURED $Head/Camera/Ray.target_position.z -= 1024 $Head/TelekinesisPoint.position.z -= object_grab_distance / 2 $Head/TelekinesisPoint.position.y += object_hold_height hp = max_health $UserInterface/Telehand.visible = false $stomparea.scale = Vector3(stomp_radius, stomp_radius, stomp_radius) func _physics_process(delta): if Input.is_action_just_pressed("exit"): get_tree().quit() $UserInterface/HUDContainer/HUDText.update(self,$walljump_timer,$dash_timer) if !start_rotated: HEAD.rotation_degrees.y += start_rotation start_rotated = true # Add some debug data $UserInterface/DebugPanel.add_property("Movement Speed", speed, 1) $UserInterface/DebugPanel.add_property("Velocity", get_real_velocity(), 2) # Gravity #gravity = ProjectSettings.get_setting("physics/3d/default_gravity") # If the gravity changes during your game, uncomment this code if not is_on_floor(): if velocity.y >= gravity: velocity.y = gravity velocity.y -= gravity * delta handle_jumping() var input_dir = Vector2.ZERO if !immobile: input_dir = Input.get_vector(LEFT, RIGHT, FORWARD, BACKWARD) handle_movement(delta, input_dir) handle_stomp() handle_telekinesis() handle_gunshoot() # toggle_crouch() toggle_sprint(input_dir) if Input.is_action_just_pressed(DASH) and !dash_spent: dash_spent = true dash_startpos = position dash_active = true $dash_timer.start(dash_cooldown) $aircontrol_timer.stop() if dash_active and dash_startpos.distance_to(position) > dash_distance: dash_active = false if dash_active and input_dir.x == 0 and input_dir.y == 0: dash_active = false if dash_active: speed = base_speed * dash_speedmultiplier elif is_crouching: speed = crouch_speed elif is_sprinting: speed = sprint_speed else: speed = base_speed if view_bobbing: headbob_animation(input_dir) for i in get_slide_collision_count(): var slaminto = get_slide_collision(i) if slaminto.get_collider() is RigidBody3D: slaminto.get_collider().apply_central_impulse(-slaminto.get_normal() * physics_push_force) func handle_jumping(): if jumping_enabled: if !coyotechance and !is_on_floor(): $coyote_timer.start(coyotetime) coyotechance = true if continuous_jumping: if Input.is_action_pressed(JUMP) and is_on_floor(): velocity.y += jump_velocity else: if Input.is_action_just_pressed(JUMP): if is_on_floor() or (coyotechance and !$coyote_timer.is_stopped()): velocity.y += jump_velocity $aircontrol_timer.start(aircontrol_delay) if walljump_ready and Input.is_action_just_pressed(JUMP) and !is_on_floor() and is_on_wall(): velocity.x = velocity.x + ((position.bounce(get_slide_collision(0).get_normal()).x) * wallbounce_multiplier) velocity.z = velocity.z + ((position.bounce(get_slide_collision(0).get_normal()).z) * wallbounce_multiplier) velocity.y += jump_velocity walljump_ready = false $walljump_timer.start(walljump_cooldown) $aircontrol_timer.start(aircontrol_delay) print("walljump timer started") func handle_movement(delta, input_dir): var direction = input_dir.rotated(-HEAD.rotation.y) direction = Vector3(direction.x, 0, direction.y) move_and_slide() if in_air_momentum: if is_on_floor(): # Don't lerp y movement if motion_smoothing: velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta) velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta) else: velocity.x = direction.x * speed velocity.z = direction.z * speed else: if direction.x != 0 and direction.z != 0 and $aircontrol_timer.is_stopped(): if motion_smoothing: velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta) velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta) else: if motion_smoothing: velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta) velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta) else: velocity.x = direction.x * speed velocity.z = direction.z * speed func _process(delta): $UserInterface/DebugPanel.add_property("FPS", 1.0/delta, 0) if Input.is_action_just_pressed(PAUSE): if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE elif Input.mouse_mode == Input.MOUSE_MODE_VISIBLE: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func _unhandled_input(event): if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: HEAD.rotation_degrees.y -= event.relative.x * mouse_sensitivity HEAD.rotation_degrees.x -= event.relative.y * mouse_sensitivity HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90)) """func toggle_crouch(): if crouch_enabled: if crouch_mode == 0: is_crouching = Input.is_action_pressed(CROUCH) elif crouch_mode == 1: if Input.is_action_just_pressed(CROUCH): is_crouching = !is_crouching # Replace with your own crouch animation code if is_crouching: $Collision.scale.y = lerp($Collision.scale.y, 0.75, 0.2) else: $Collision.scale.y = lerp($Collision.scale.y, 1.0, 0.2)""" func toggle_sprint(moving): if sprint_enabled: if sprint_mode == 0: if !is_crouching: # Crouching takes priority over sprinting is_sprinting = Input.is_action_pressed(SPRINT) else: is_sprinting = false # Fix a bug where if you are sprinting and then crouch then let go of the sprinting button you keep sprinting elif sprint_mode == 1: if Input.is_action_just_pressed(SPRINT): if !is_crouching: is_sprinting = !is_sprinting else: is_sprinting = false if dynamic_fov: if is_sprinting and moving: CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3) else: CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3) func headbob_animation(moving): if moving and is_on_floor(): CAMERA_ANIMATION.play("headbob") CAMERA_ANIMATION.speed_scale = speed / base_speed else: CAMERA_ANIMATION.play("RESET") func handle_telekinesis(): if holding_object: held_object.global_position = held_object.global_position.lerp($Head/TelekinesisPoint.global_position, 0.3) if $UserInterface/Telehand.texture == tex_telehand_pull and held_object.global_position.distance_to($Head/TelekinesisPoint.global_position) < 0.1: $UserInterface/Telehand.texture = tex_telehand_grip if Input.is_action_just_pressed(CROUCH) and holding_object: holding_object = false held_object.gravity_scale = 1.0 $UserInterface/Telehand.texture = tex_telehand_throw $telehand_timer.start(0.6) elif Input.is_action_just_pressed(TELEK): if !holding_object: var object = $Head/Camera/Ray.get_collider() if object != null and ($Head/Camera/Ray.get_collision_point().distance_to(position) <= object_grab_distance) and object.get("grabbable") == true: holding_object = true held_object = object held_object.gravity_scale = 0.0 $UserInterface/Telehand.texture = tex_telehand_pull $UserInterface/Telehand.visible = true $telehand_timer.stop() $telehand_timer.start(0.5) else: held_object.apply_central_impulse($Head/Camera/Ray.global_position.direction_to(held_object.global_position - Vector3(0,object_hold_height,0)) * Vector3(object_hurl_force, object_hurl_force, object_hurl_force)) holding_object = false held_object.gravity_scale = 1.0 $UserInterface/Telehand.texture = tex_telehand_throw $telehand_timer.start(0.5) func handle_gunshoot(): if Input.is_action_just_pressed(SHOOT) and $gunfire_timer.is_stopped(): $gunfire_timer.start(gun_firedelay) $gunflash_timer.start(0.08) $UserInterface/Gun.texture = tex_pistol_recoil var object = $Head/Camera/Ray.get_collider() if object != null: if object.get("shootable") == true: var multiplier : float = 1.0 if object.get("weakpoint") == true: if object.rootnode: multiplier = object.weakpoint_damage else: multiplier = object.owner.weakpoint_damage if object.rootnode: object.deal_damage(gun_damage * multiplier) else: object.owner.deal_damage(gun_damage * multiplier) elif object.get("grabbable") == true: var shootforce = gun_damage * gun_physics_nudge_multiplier object.apply_central_impulse($Head/Camera/Ray.global_position.direction_to(object.global_position) * Vector3(shootforce, shootforce, shootforce)) if object == held_object: holding_object = false held_object.gravity_scale = 1.0 $UserInterface/Telehand.visible = false $telehand_timer.stop func handle_stomp(): if !stomping and !is_on_floor() and !holding_object and Input.is_action_just_pressed(CROUCH): stomping = true stomp_startingheight = global_position.y $stomparea.monitoring = true if stomping: if !is_on_floor(): velocity.y = -stomp_speed var stompheight = stomp_startingheight - global_position.y if stompheight >= stomp_radiusthreshold: var newradius = clamp(stomp_radius + (stomp_radiusincrement * (stompheight - stomp_radiusthreshold)), stomp_radius, stomp_maxradius) $stomparea.scale = Vector3(newradius, newradius, newradius) else: stomping = false var damage = clamp(stomp_damagepermeter * (stomp_startingheight - global_position.y), stomp_mindamage, stomp_maxdamage) for body in $stomparea.get_overlapping_bodies(): if body is RigidBody3D: body.apply_central_impulse(global_position.direction_to(body.global_position) * Vector3(damage*2, damage*4, damage*2)) if body is CharacterBody3D and body.get("playerallied") == false: body.deal_damage(damage) body.global_position.y += 0.2 body.velocity = global_position.direction_to(body.global_position) * Vector3(damage*20, damage*40, damage*20) $stomparea.scale = Vector3(stomp_radius, stomp_radius, stomp_radius) $stomparea.monitoring = false func deal_damage(damage): print("player takes "+ str(damage)+" damage, leaving "+str(hp)+" HP left") hp = hp - damage if hp <= 0: print("player died! oh no!") func _on_walljump_timer_timeout(): walljump_ready = true $walljump_timer.stop() func _on_aircontrol_timer_timeout(): $aircontrol_timer.stop() func _on_coyote_timer_timeout(): $coyote_timer.stop() func _on_dash_timer_timeout(): dash_spent = false $dash_timer.stop() pass # Replace with function body. func _on_gunfire_timer_timeout(): $gunfire_timer.stop() func _on_gunflash_timer_timeout(): $UserInterface/Gun.texture = tex_pistol_neutral $gunflash_timer.stop() func _on_telehand_timer_timeout(): if $UserInterface/Telehand.texture == tex_telehand_pull: $UserInterface/Telehand.texture = tex_telehand_grip elif $UserInterface/Telehand.texture == tex_telehand_throw: $UserInterface/Telehand.visible = false $telehand_timer.stop()