737 lines
25 KiB
GDScript
737 lines
25 KiB
GDScript
extends CharacterBody3D
|
|
|
|
@export_category("Extra Options")
|
|
@export var enable_physics_gun: bool = false
|
|
@export var enable_depth_of_field: bool = false
|
|
@export var disable_shadow_in_first_person: bool = false
|
|
@export var enable_audio: bool = false
|
|
@export var flashlight_intensity: float = 1.0
|
|
|
|
# --- Stuff you might be interested in tweaking ---
|
|
const LOOK_SENSITIVITY = 0.0025
|
|
const MOVE_MULT = 1.4
|
|
const RUN_MULT = 1.25
|
|
const FP_FOV = 75.0
|
|
const TP_FOV = 60.0
|
|
const ZOOM_MULT = 0.35
|
|
const DOF_AREA_SOFTNESS = 1.15
|
|
const DOF_AREA_SIZE_MULTIPLIER = 0.0
|
|
const DOF_MAX_RANGE = 1000.0
|
|
const FLASHLIGHT_ANGLE = 35.0
|
|
# WARNING: in Godot Jolt physics damping seems to have inconsistent behavior between different physics tick rates
|
|
const PHYSICS_GUN_DAMPING = 30.0
|
|
const PHYSICS_GUN_PULL_FORCE = 800.0
|
|
const PHYSICS_GUN_SHOOT_FORCE = 13.0
|
|
const PHYSICS_GUN_HOLD_DISTANCE = 2.5
|
|
const PHYSICS_GUN_PULL_RANGE = 15.0
|
|
# --- Stuff you might be interested in tweaking ---
|
|
|
|
const FP_CAMERA_HEIGHT = 1.655
|
|
const TP_CAMERA_HEIGHT = 1.544
|
|
const TP_CAMERA_OFFSET = 0.5
|
|
const TP_CAMERA_DISTANCE = 2.1
|
|
const FLASHLIGHT_DISTANCE_FROM_MODEL = 0.35
|
|
const TRANSITION_SPEED = 0.25
|
|
const LOOK_LIMIT_UPPER = 1.25
|
|
const LOOK_LIMIT_LOWER = -1.25
|
|
const ANIM_MOVE_SPEED = 3.0
|
|
const ANIM_RUN_SPEED = 5.5
|
|
const JUMP_LAND_TIMEOUT = 0.1
|
|
const NOCLIP_MULT = 4.0
|
|
const ROTATE_SPEED = 12.0
|
|
const STEP_HEIGHT = 0.25
|
|
const JUMP_FORCE = 15.0
|
|
const GRAVITY_FORCE = 50.0
|
|
# 285 seems to be enough to move a max of 200kg
|
|
const COLLIDE_FORCE = 200.0
|
|
const MAX_PUSHABLE_WEIGHT = 200.0
|
|
const PHYSICS_GUN_PULL_MARGIN = 2.0
|
|
const PHYSICS_GUN_PULL_WIDTH = 0.8
|
|
const TOGGLE_COOLDOWN = 0.25
|
|
const DOF_MOVE_SPEED = 30.0
|
|
const DOF_INTENSITY = 0.25
|
|
const BUMP_AUDIO_TIMEOUT = 0.15
|
|
const BUMP_AUDIO_FORCE_THRESHOLD = 400.0
|
|
const BUMP_AUDIO_VOLUME_DB = -20.0
|
|
const BUMP_AUDIO_ATTENUATION_THRESHOLD = 0.2
|
|
|
|
var move_direction = Vector3.ZERO
|
|
var move_direction_no_y = Vector3.ZERO
|
|
var camera_rotation = Quaternion.IDENTITY
|
|
var camera_rotation_no_y = Quaternion.IDENTITY
|
|
var is_off_floor_duration = 0.0
|
|
var is_on_floor_duration = 0.0
|
|
var has_landed_from_fall = false
|
|
var boot_sound_timeout = true
|
|
var noclip_on = false
|
|
var noclip_toggle_cooldown = 0.0
|
|
var flashlight_on = false
|
|
var flashlight_toggle_cooldown = 0.0
|
|
var cam_is_fp = false
|
|
var cam_toggle_cooldown = 0.0
|
|
var cam_is_zoomed = false
|
|
var cam_zoom_cooldown = 0.0
|
|
var dof_target_distance = 0.0
|
|
var dof_amount_to_apply = 0.0
|
|
var shoulder_is_swapped = false
|
|
var shoulder_cooldown = 0.0
|
|
var mousecapture_on = true
|
|
var mousecapture_toggle_cooldown = 0.0
|
|
var physics_gun_cooldown = 0.0
|
|
var is_cam_transitioning = false
|
|
var input_velocity = Vector3.ZERO
|
|
var orig_transform = Transform3D.IDENTITY
|
|
var rigidbody_collisions = []
|
|
var colliders_in_contact = []
|
|
var collider_bump_cooldowns = []
|
|
var physics_gun_has_grabbed = false
|
|
var physics_gun_object = null
|
|
var physics_gun_object_linear_damp = 0.0
|
|
var physics_gun_object_angular_damp = 0.0
|
|
var physics_gun_hit_point = Vector3.ZERO
|
|
|
|
var mouse_movement = Vector2.ZERO
|
|
var forward_isdown = false
|
|
var backward_isdown = false
|
|
var left_isdown = false
|
|
var right_isdown = false
|
|
var cam_toggle_isdown = false
|
|
var noclip_isdown = false
|
|
var flashlight_isdown = false
|
|
var sprint_isdown = false
|
|
var jump_isdown = false
|
|
var mousecapture_isdown = false
|
|
var zoom_isdown = false
|
|
var shoulder_isdown = false
|
|
var physics_gun_fire_isdown = false
|
|
|
|
@onready var model_root = $"ModelRoot"
|
|
@onready var anim_player = $"ModelRoot/HumanModel/AnimationPlayer"
|
|
@onready var player_mesh = $"ModelRoot/HumanModel/root/Skeleton3D/mannequiny"
|
|
@onready var collision_shape = $"CollisionShape"
|
|
@onready var camera_pivot = $"CameraPivot"
|
|
@onready var spring_arm = $"CameraPivot/SpringArm"
|
|
@onready var camera = $"CameraPivot/SpringArm/Camera"
|
|
@onready var focus_ray = $"CameraPivot/SpringArm/Camera/RayCast3D"
|
|
@onready var audio_listener = $"CameraPivot/SpringArm/Camera/AudioListener3D"
|
|
@onready var right_footstep = $"ModelRoot/HumanModel/root/Skeleton3D/RightFootLocation/FootstepPlayer"
|
|
@onready var left_footstep = $"ModelRoot/HumanModel/root/Skeleton3D/LeftFootLocation/FootstepPlayer"
|
|
@onready var jump_land_audio = $"ModelRoot/JumpLandPlayer"
|
|
@onready var physics_object_collector = $"CameraPivot/SpringArm/PhysicsGun/PhysicsObjectCollector"
|
|
@onready var physics_object_collector_collider = $"CameraPivot/SpringArm/PhysicsGun/PhysicsObjectCollector/CollisionShape3D"
|
|
@onready var physics_gun_raycast = $"CameraPivot/SpringArm/PhysicsGun/RayCast3D"
|
|
@onready var flashlight_pin = $"ModelRoot/HumanModel/root/Skeleton3D/FlashlightPin/FlashlightOffset"
|
|
@onready var flashlight = $"ModelRoot/HumanModel/root/Skeleton3D/FlashlightPin/FlashlightOffset/SpotLight3D"
|
|
|
|
@onready var bump_audio = load("res://Godot-Human-For-Scale/Assets/BumpAudio.tscn")
|
|
|
|
@onready var footstep_sounds = [
|
|
load("res://Godot-Human-For-Scale/Assets/Audio/Footstep1.wav"),
|
|
load("res://Godot-Human-For-Scale/Assets/Audio/Footstep2.wav"),
|
|
load("res://Godot-Human-For-Scale/Assets/Audio/Footstep3.wav"),
|
|
load("res://Godot-Human-For-Scale/Assets/Audio/Footstep4.wav")
|
|
]
|
|
|
|
@onready var bump_sounds = [
|
|
load("res://Godot-Human-For-Scale/Assets/Audio/Bump1.wav"),
|
|
load("res://Godot-Human-For-Scale/Assets/Audio/Bump2.wav"),
|
|
load("res://Godot-Human-For-Scale/Assets/Audio/Bump3.wav")
|
|
]
|
|
|
|
func _ready():
|
|
var y_rotation = Vector3(0.0, global_rotation.y, 0.0)
|
|
|
|
global_rotation = Vector3.ZERO
|
|
|
|
physics_object_collector_collider.shape.height = PHYSICS_GUN_PULL_RANGE
|
|
physics_object_collector_collider.shape.radius = PHYSICS_GUN_PULL_WIDTH
|
|
physics_object_collector_collider.position.z = -(PHYSICS_GUN_PULL_RANGE/2)
|
|
physics_gun_raycast.target_position.z = -PHYSICS_GUN_PULL_RANGE
|
|
|
|
camera_pivot.global_rotation = y_rotation
|
|
model_root.global_rotation = y_rotation
|
|
camera_rotation = Quaternion.from_euler(camera_pivot.global_rotation)
|
|
camera_rotation_no_y = Quaternion.from_euler(camera_pivot.global_rotation)
|
|
|
|
flashlight.light_energy = flashlight_intensity
|
|
flashlight.spot_angle = FLASHLIGHT_ANGLE
|
|
|
|
camera.make_current()
|
|
|
|
if enable_depth_of_field:
|
|
hijack_camera_attributes()
|
|
|
|
if enable_audio:
|
|
audio_listener.make_current()
|
|
|
|
await get_tree().create_timer(0.25).timeout
|
|
boot_sound_timeout = false
|
|
|
|
func _process(delta):
|
|
process_camera()
|
|
process_off_on_floor_time(delta)
|
|
process_movement()
|
|
process_animation(delta)
|
|
process_mousecapture(delta)
|
|
process_noclip(delta)
|
|
process_flashlight(delta)
|
|
process_cam_toggle(delta)
|
|
process_cam_zoom(delta)
|
|
process_shoulder_swap(delta)
|
|
process_physics_gun_fire(delta)
|
|
process_dof(delta)
|
|
|
|
var move_speed = ANIM_MOVE_SPEED * MOVE_MULT
|
|
if sprint_isdown:
|
|
move_speed = ANIM_RUN_SPEED * RUN_MULT
|
|
|
|
if noclip_on:
|
|
velocity = move_direction * (move_speed * NOCLIP_MULT)
|
|
else:
|
|
velocity.x = move_direction_no_y.x * move_speed
|
|
velocity.z = move_direction_no_y.z * move_speed
|
|
if !is_on_floor():
|
|
velocity.y -= GRAVITY_FORCE * delta
|
|
if jump_isdown and is_on_floor():
|
|
velocity.y = JUMP_FORCE
|
|
play_jump_land_sound()
|
|
|
|
if is_on_floor_duration >= JUMP_LAND_TIMEOUT and !has_landed_from_fall:
|
|
has_landed_from_fall = true
|
|
play_jump_land_sound()
|
|
if is_off_floor_duration >= JUMP_LAND_TIMEOUT and has_landed_from_fall:
|
|
has_landed_from_fall = false
|
|
|
|
input_velocity = velocity
|
|
orig_transform = global_transform
|
|
|
|
move_and_slide()
|
|
|
|
rigidbody_collisions = []
|
|
|
|
var has_stairstepped = stairstepping(orig_transform, delta)
|
|
|
|
# Rigidbody interactions don't play nice with stairstepping ☹️
|
|
if !has_stairstepped:
|
|
collate_rigidbody_interactions()
|
|
|
|
flashlight.global_position = flashlight_pin.global_position
|
|
var backwards = Basis(camera_rotation_no_y).z
|
|
var up = flashlight_pin.global_basis.y
|
|
var right = up.cross(backwards)
|
|
flashlight.global_basis = Basis(right, up, right.cross(up)).orthonormalized()
|
|
flashlight.position = flashlight.position + (-flashlight.basis.z * FLASHLIGHT_DISTANCE_FROM_MODEL)
|
|
flashlight.global_rotation = camera_pivot.global_rotation
|
|
|
|
func _physics_process(delta):
|
|
process_physics_gun(delta)
|
|
|
|
var collide_force = COLLIDE_FORCE * delta
|
|
var central_multiplier = input_velocity.length() * collide_force
|
|
|
|
var collider_indexes_still_in_contact = []
|
|
var colliders_still_in_contact = []
|
|
var non_expired_cooldowns = []
|
|
|
|
for collision in rigidbody_collisions:
|
|
if collision.get_collider() == null:
|
|
continue
|
|
var collider = collision.get_collider()
|
|
var weight = collider.mass
|
|
var direction = -collision.get_normal()
|
|
var mult_actual = lerp(0.0, central_multiplier, ease_out_circ(weight/MAX_PUSHABLE_WEIGHT))
|
|
|
|
collider.apply_central_impulse(direction * mult_actual)
|
|
collider_indexes_still_in_contact.append(colliders_in_contact.find(collider))
|
|
|
|
if !colliders_in_contact.has(collider):
|
|
colliders_in_contact.append(collider)
|
|
var volume_scale = mult_actual/(BUMP_AUDIO_FORCE_THRESHOLD * delta)
|
|
if volume_scale > BUMP_AUDIO_ATTENUATION_THRESHOLD:
|
|
var has_collider = false
|
|
for cooldown in collider_bump_cooldowns:
|
|
if cooldown["collider"] != null and cooldown["collider"] == collider:
|
|
has_collider = true
|
|
break
|
|
if !has_collider:
|
|
play_bump_audio(collision.get_position(), volume_scale)
|
|
var cooldown = {"collider": collider, "cooldown": BUMP_AUDIO_TIMEOUT}
|
|
collider_bump_cooldowns.append(cooldown)
|
|
|
|
for index in collider_indexes_still_in_contact:
|
|
colliders_still_in_contact.append(colliders_in_contact[index])
|
|
|
|
colliders_in_contact = colliders_still_in_contact
|
|
|
|
for cooldown in collider_bump_cooldowns:
|
|
cooldown["cooldown"] -= delta
|
|
|
|
for cooldown in collider_bump_cooldowns:
|
|
if cooldown["cooldown"] > 0.0:
|
|
non_expired_cooldowns.append(cooldown)
|
|
|
|
collider_bump_cooldowns = non_expired_cooldowns
|
|
|
|
func stairstepping(starting_transform, delta):
|
|
if (input_velocity.x == 0 and input_velocity.z == 0) or noclip_on or !is_on_floor() or !is_on_wall():
|
|
return false
|
|
|
|
var collision_out = KinematicCollision3D.new()
|
|
var begin_transform = starting_transform
|
|
var test_direction = Vector3.UP * STEP_HEIGHT
|
|
# Test to above current position
|
|
var can_not_step = test_move(begin_transform, test_direction)
|
|
|
|
if can_not_step:
|
|
return false
|
|
|
|
begin_transform.origin = begin_transform.origin + test_direction
|
|
test_direction = Vector3(input_velocity.x, 0, input_velocity.z) * delta
|
|
# Then, test towards player's direction running into wall
|
|
can_not_step = test_move(begin_transform, test_direction)
|
|
|
|
if can_not_step:
|
|
return false
|
|
|
|
begin_transform.origin = begin_transform.origin + test_direction
|
|
test_direction = Vector3.DOWN * STEP_HEIGHT
|
|
# Then, test downwards
|
|
can_not_step = test_move(begin_transform, test_direction, collision_out)
|
|
|
|
if can_not_step:
|
|
# If we hit something, teleport towards hit location
|
|
begin_transform.origin = begin_transform.origin + collision_out.get_travel()
|
|
else:
|
|
# If we hit nothing, teleport back to original height
|
|
begin_transform.origin = begin_transform.origin + test_direction
|
|
|
|
# Without the buffer the player can fail to make steps, especially at higher framerates
|
|
var step_landing_buffer = floor_snap_length - safe_margin
|
|
begin_transform.origin = begin_transform.origin + (Vector3.UP * step_landing_buffer)
|
|
global_transform = begin_transform
|
|
return true
|
|
|
|
func collate_rigidbody_interactions():
|
|
for index in get_slide_collision_count():
|
|
if get_slide_collision(index) == null:
|
|
continue
|
|
var collision = get_slide_collision(index)
|
|
if collision.get_collider() is RigidBody3D:
|
|
rigidbody_collisions.append(collision)
|
|
|
|
func _on_right_footstep():
|
|
if !enable_audio:
|
|
return
|
|
right_footstep.stream = footstep_sounds.pick_random()
|
|
right_footstep.play()
|
|
|
|
func _on_left_footstep():
|
|
if !enable_audio:
|
|
return
|
|
left_footstep.stream = footstep_sounds.pick_random()
|
|
left_footstep.play()
|
|
|
|
func play_jump_land_sound():
|
|
if !enable_audio or boot_sound_timeout:
|
|
return
|
|
if !left_footstep.playing or !right_footstep.playing:
|
|
jump_land_audio.stream = footstep_sounds.pick_random()
|
|
jump_land_audio.play()
|
|
|
|
func play_bump_audio(global_audio_position, volume_scale):
|
|
if !enable_audio:
|
|
return
|
|
var spawned_bump_audio = bump_audio.instantiate()
|
|
get_tree().root.get_child(0).add_child(spawned_bump_audio)
|
|
spawned_bump_audio.global_position = global_audio_position
|
|
spawned_bump_audio.stream = bump_sounds.pick_random()
|
|
volume_scale = clamp(volume_scale, 0.0, 1.0)
|
|
spawned_bump_audio.volume_db = lerp(-80.0, BUMP_AUDIO_VOLUME_DB, ease_out_circ(volume_scale))
|
|
spawned_bump_audio.play()
|
|
await get_tree().create_timer(0.5).timeout
|
|
spawned_bump_audio.queue_free()
|
|
|
|
func process_off_on_floor_time(delta):
|
|
if is_on_floor():
|
|
is_on_floor_duration += delta
|
|
is_off_floor_duration = 0.0
|
|
else:
|
|
is_on_floor_duration = 0.0
|
|
is_off_floor_duration += delta
|
|
is_on_floor_duration = clamp(is_on_floor_duration, 0.0, JUMP_LAND_TIMEOUT)
|
|
is_off_floor_duration = clamp(is_off_floor_duration, 0.0, JUMP_LAND_TIMEOUT)
|
|
|
|
func process_camera():
|
|
var camera_rotation_euler = camera_rotation.get_euler()
|
|
|
|
if mousecapture_on:
|
|
camera_rotation_euler += Vector3(mouse_movement.y, mouse_movement.x, 0.0) * LOOK_SENSITIVITY
|
|
camera_rotation_euler.x = clamp(camera_rotation_euler.x, LOOK_LIMIT_LOWER, LOOK_LIMIT_UPPER)
|
|
|
|
camera_rotation = Quaternion.from_euler(camera_rotation_euler)
|
|
camera_pivot.global_basis = Basis(camera_rotation)
|
|
camera_rotation_no_y = Basis(camera_pivot.global_basis.x, Vector3.UP, camera_pivot.global_basis.z).get_rotation_quaternion()
|
|
|
|
mouse_movement = Vector2.ZERO
|
|
|
|
func process_movement():
|
|
var input_direction = Vector3.ZERO
|
|
|
|
if forward_isdown:
|
|
input_direction.z -= 1.0
|
|
if backward_isdown:
|
|
input_direction.z += 1.0
|
|
if left_isdown:
|
|
input_direction.x -= 1.0
|
|
if right_isdown:
|
|
input_direction.x += 1.0
|
|
|
|
move_direction = camera_rotation * input_direction
|
|
move_direction_no_y = camera_rotation_no_y * input_direction
|
|
move_direction = move_direction.normalized()
|
|
move_direction_no_y = move_direction_no_y.normalized()
|
|
|
|
func process_animation(delta):
|
|
if is_off_floor_duration >= JUMP_LAND_TIMEOUT:
|
|
switch_anim("Fall")
|
|
elif move_direction != Vector3.ZERO:
|
|
if sprint_isdown:
|
|
switch_anim("Run", RUN_MULT)
|
|
else:
|
|
switch_anim("Jog", MOVE_MULT)
|
|
else:
|
|
switch_anim("Idle")
|
|
|
|
if move_direction != Vector3.ZERO:
|
|
model_root.global_basis = basis_rotate_toward(model_root.global_basis, Basis.looking_at(move_direction_no_y), ROTATE_SPEED * delta)
|
|
|
|
func process_mousecapture(delta):
|
|
if mousecapture_isdown and mousecapture_toggle_cooldown == 0.0:
|
|
mousecapture_on = !mousecapture_on
|
|
mousecapture_toggle_cooldown = TOGGLE_COOLDOWN
|
|
|
|
if mousecapture_on:
|
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
|
else:
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
|
|
mousecapture_toggle_cooldown -= delta
|
|
mousecapture_toggle_cooldown = clamp(mousecapture_toggle_cooldown, 0.0, TOGGLE_COOLDOWN)
|
|
|
|
func process_noclip(delta):
|
|
if noclip_isdown and noclip_toggle_cooldown == 0.0:
|
|
noclip_on = !noclip_on
|
|
collision_shape.disabled = !collision_shape.disabled
|
|
noclip_toggle_cooldown = TOGGLE_COOLDOWN
|
|
|
|
noclip_toggle_cooldown -= delta
|
|
noclip_toggle_cooldown = clamp(noclip_toggle_cooldown, 0.0, TOGGLE_COOLDOWN)
|
|
|
|
func process_flashlight(delta):
|
|
if flashlight_isdown and flashlight_toggle_cooldown == 0.0:
|
|
flashlight_on = !flashlight_on
|
|
flashlight.visible = flashlight_on
|
|
flashlight_toggle_cooldown = TOGGLE_COOLDOWN
|
|
|
|
flashlight_toggle_cooldown -= delta
|
|
flashlight_toggle_cooldown = clamp(flashlight_toggle_cooldown, 0.0, TOGGLE_COOLDOWN)
|
|
|
|
func process_cam_toggle(delta):
|
|
if cam_toggle_isdown and cam_toggle_cooldown == 0.0 and !is_cam_transitioning:
|
|
cam_is_fp = !cam_is_fp
|
|
cam_toggle_cooldown = TOGGLE_COOLDOWN
|
|
cam_transition()
|
|
|
|
cam_toggle_cooldown -= delta
|
|
cam_toggle_cooldown = clamp(cam_toggle_cooldown, 0.0, TOGGLE_COOLDOWN)
|
|
|
|
func process_cam_zoom(delta):
|
|
if !mousecapture_on:
|
|
return
|
|
|
|
if zoom_isdown and cam_zoom_cooldown == 0.0 and !is_cam_transitioning:
|
|
cam_is_zoomed = !cam_is_zoomed
|
|
cam_zoom_cooldown = TOGGLE_COOLDOWN
|
|
cam_transition()
|
|
|
|
cam_zoom_cooldown -= delta
|
|
cam_zoom_cooldown = clamp(cam_zoom_cooldown, 0.0, TOGGLE_COOLDOWN)
|
|
|
|
func process_shoulder_swap(delta):
|
|
if shoulder_isdown and !cam_is_fp and shoulder_cooldown == 0.0 and !is_cam_transitioning:
|
|
shoulder_is_swapped = !shoulder_is_swapped
|
|
shoulder_cooldown = TOGGLE_COOLDOWN
|
|
cam_transition()
|
|
|
|
shoulder_cooldown -= delta
|
|
shoulder_cooldown = clamp(shoulder_cooldown, 0.0, TOGGLE_COOLDOWN)
|
|
|
|
func process_physics_gun_fire(delta):
|
|
if !enable_physics_gun or !mousecapture_on:
|
|
return
|
|
|
|
if physics_gun_fire_isdown and physics_gun_cooldown == 0.0:
|
|
if physics_gun_has_grabbed:
|
|
fire_physics_gun()
|
|
else:
|
|
grab_physics_gun()
|
|
physics_gun_cooldown = TOGGLE_COOLDOWN
|
|
|
|
physics_gun_cooldown -= delta
|
|
physics_gun_cooldown = clamp(physics_gun_cooldown, 0.0, TOGGLE_COOLDOWN)
|
|
|
|
func grab_physics_gun():
|
|
var rigidbodies_detected = []
|
|
|
|
for node in physics_object_collector.get_overlapping_bodies():
|
|
if node is RigidBody3D:
|
|
rigidbodies_detected.append(node)
|
|
|
|
if physics_gun_raycast.is_colliding():
|
|
physics_gun_hit_point = physics_gun_raycast.get_collision_point()
|
|
else:
|
|
physics_gun_hit_point = spring_arm.global_position
|
|
|
|
rigidbodies_detected.sort_custom(rigidbody_distance_sort)
|
|
|
|
if rigidbodies_detected.size() == 0 or rigidbodies_detected[0] == null:
|
|
return
|
|
|
|
physics_gun_object = rigidbodies_detected[0]
|
|
|
|
physics_gun_object_linear_damp = physics_gun_object.linear_damp
|
|
physics_gun_object_angular_damp = physics_gun_object.angular_damp
|
|
physics_gun_object.linear_damp = PHYSICS_GUN_DAMPING
|
|
physics_gun_object.angular_damp = PHYSICS_GUN_DAMPING
|
|
|
|
physics_gun_has_grabbed = true
|
|
|
|
func rigidbody_distance_sort(rigidbody_a, rigidbody_b):
|
|
if physics_gun_hit_point.distance_to(rigidbody_a.global_position) < physics_gun_hit_point.distance_to(rigidbody_b.global_position):
|
|
return true
|
|
else:
|
|
return false
|
|
|
|
func fire_physics_gun():
|
|
physics_gun_has_grabbed = false
|
|
|
|
physics_gun_object.linear_damp = physics_gun_object_linear_damp
|
|
physics_gun_object.angular_damp = physics_gun_object_angular_damp
|
|
|
|
physics_gun_object.linear_velocity = Vector3.ZERO
|
|
physics_gun_object.angular_velocity = Vector3.ZERO
|
|
physics_gun_object.constant_force = Vector3.ZERO
|
|
|
|
physics_gun_object.apply_central_impulse(-camera_pivot.basis.z * (PHYSICS_GUN_SHOOT_FORCE * physics_gun_object.mass))
|
|
|
|
func process_physics_gun(delta):
|
|
if !enable_physics_gun or !physics_gun_has_grabbed:
|
|
return
|
|
|
|
if physics_gun_object == null:
|
|
physics_gun_has_grabbed = false
|
|
return
|
|
|
|
var physics_gun_hold_location = camera_pivot.global_position + (-camera_pivot.global_basis.z * PHYSICS_GUN_HOLD_DISTANCE)
|
|
|
|
var lerp_force = physics_gun_hold_location.distance_to(physics_gun_object.global_position) / PHYSICS_GUN_PULL_MARGIN
|
|
lerp_force = clamp(lerp_force, 0.0, 1.0)
|
|
|
|
var physics_gun_suck = physics_gun_object.global_position.direction_to(physics_gun_hold_location) * PHYSICS_GUN_PULL_FORCE
|
|
physics_gun_suck = physics_gun_suck * physics_gun_object.mass
|
|
physics_gun_suck = lerp(Vector3.ZERO, physics_gun_suck, lerp_force)
|
|
|
|
physics_gun_object.constant_force = physics_gun_suck
|
|
|
|
func cam_transition():
|
|
if is_cam_transitioning:
|
|
return
|
|
|
|
var fov
|
|
var offset
|
|
|
|
if cam_is_zoomed:
|
|
if cam_is_fp:
|
|
fov = FP_FOV * ZOOM_MULT
|
|
else:
|
|
fov = TP_FOV * ZOOM_MULT
|
|
else:
|
|
if cam_is_fp:
|
|
fov = FP_FOV
|
|
else:
|
|
fov = TP_FOV
|
|
|
|
if shoulder_is_swapped:
|
|
offset = -TP_CAMERA_OFFSET
|
|
else:
|
|
offset = TP_CAMERA_OFFSET
|
|
|
|
if cam_is_fp:
|
|
cam_transitioning(FP_CAMERA_HEIGHT, 0.0, 0.0, fov, false)
|
|
else:
|
|
cam_transitioning(TP_CAMERA_HEIGHT, offset, TP_CAMERA_DISTANCE, fov, true)
|
|
|
|
func cam_transitioning(height, offset, length, fov, mesh_visible):
|
|
is_cam_transitioning = true
|
|
|
|
var time = Time.get_ticks_msec()
|
|
var orig_height = camera_pivot.position.y
|
|
var orig_offset = spring_arm.position.x
|
|
var orig_length = spring_arm.spring_length
|
|
var orig_fov = camera.fov
|
|
|
|
if mesh_visible:
|
|
if disable_shadow_in_first_person:
|
|
player_mesh.visible = true
|
|
else:
|
|
player_mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
|
|
|
while is_cam_transitioning:
|
|
var current_time = Time.get_ticks_msec()
|
|
var lerp = (current_time - time)/(TRANSITION_SPEED * 1000.0)
|
|
|
|
if lerp >= 1.0:
|
|
camera_pivot.position.y = height
|
|
spring_arm.position.x = offset
|
|
spring_arm.spring_length = length
|
|
camera.fov = fov
|
|
if !mesh_visible:
|
|
if disable_shadow_in_first_person:
|
|
player_mesh.visible = false
|
|
else:
|
|
player_mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_SHADOWS_ONLY
|
|
is_cam_transitioning = false
|
|
break
|
|
|
|
camera_pivot.position.y = lerp(orig_height, height, ease_in_out_sine(lerp))
|
|
spring_arm.position.x = lerp(orig_offset, offset, ease_in_out_sine(lerp))
|
|
# Adjusting spring_length is jittery. Likely only updates on physics process.
|
|
spring_arm.spring_length = lerp(orig_length, length, lerp)
|
|
# So if you're noticing jitter when switching cams, ^ this lerp is responsible.
|
|
camera.fov = lerp(orig_fov, fov, ease_in_out_sine(lerp))
|
|
|
|
await get_tree().process_frame
|
|
|
|
func process_dof(delta):
|
|
if camera.attributes == null or !enable_depth_of_field:
|
|
return
|
|
|
|
var target_distance = DOF_MAX_RANGE
|
|
if focus_ray.is_colliding():
|
|
target_distance = camera.global_position.distance_to(focus_ray.get_collision_point())
|
|
var blur_amount = DOF_INTENSITY
|
|
if !cam_is_zoomed:
|
|
blur_amount = 0.0
|
|
var distance_multiplier = 0.75
|
|
var coming_back_bonus_multiplier = 3.4
|
|
if target_distance < dof_target_distance:
|
|
distance_multiplier = distance_multiplier * coming_back_bonus_multiplier
|
|
var distance_additional = abs(dof_target_distance - target_distance)
|
|
distance_additional = distance_additional * distance_multiplier
|
|
var adjusted_dof_move_speed = DOF_MOVE_SPEED + distance_additional
|
|
|
|
dof_target_distance = move_toward(dof_target_distance, target_distance, adjusted_dof_move_speed * delta)
|
|
dof_amount_to_apply = move_toward(dof_amount_to_apply, blur_amount, 0.45 * delta)
|
|
|
|
camera.attributes.dof_blur_near_distance = dof_target_distance - (dof_target_distance * DOF_AREA_SIZE_MULTIPLIER)
|
|
camera.attributes.dof_blur_near_transition = dof_target_distance * DOF_AREA_SOFTNESS
|
|
camera.attributes.dof_blur_far_distance = dof_target_distance + (dof_target_distance * DOF_AREA_SIZE_MULTIPLIER)
|
|
camera.attributes.dof_blur_far_transition = dof_target_distance * DOF_AREA_SOFTNESS
|
|
|
|
if dof_amount_to_apply < 0.001:
|
|
camera.attributes.dof_blur_far_enabled = false
|
|
camera.attributes.dof_blur_near_distance = 0.5
|
|
camera.attributes.dof_blur_near_transition = 0.25
|
|
camera.attributes.dof_blur_amount = 0.1
|
|
else:
|
|
camera.attributes.dof_blur_far_enabled = true
|
|
camera.attributes.dof_blur_amount = dof_amount_to_apply
|
|
|
|
func hijack_camera_attributes():
|
|
var cams_or_envs = []
|
|
cams_or_envs += get_tree().current_scene.find_children("*", "Camera3D", true)
|
|
cams_or_envs += get_tree().current_scene.find_children("*", "WorldEnvironment", true)
|
|
var hijacked_attributes = null
|
|
|
|
for node in cams_or_envs:
|
|
if node is Camera3D:
|
|
if node.attributes != null:
|
|
hijacked_attributes = node.attributes
|
|
break
|
|
if node is WorldEnvironment:
|
|
if node.camera_attributes != null:
|
|
hijacked_attributes = node.camera_attributes
|
|
break
|
|
|
|
camera.attributes = assert_practical_attributes(hijacked_attributes)
|
|
|
|
camera.attributes.dof_blur_near_enabled = true
|
|
focus_ray.target_position.z = -DOF_MAX_RANGE
|
|
|
|
func assert_practical_attributes(attributes):
|
|
var practical_attributes = CameraAttributesPractical.new()
|
|
|
|
if attributes != null:
|
|
if attributes is CameraAttributesPractical:
|
|
practical_attributes = attributes
|
|
else:
|
|
push_warning("CameraAttributesPhysical not supported. Making a CameraAttributesPractical duplicate for Godot-Human-For-Scale's camera.")
|
|
practical_attributes.auto_exposure_enabled = attributes.auto_exposure_enabled
|
|
practical_attributes.auto_exposure_scale = attributes.auto_exposure_scale
|
|
practical_attributes.auto_exposure_speed = attributes.auto_exposure_speed
|
|
practical_attributes.exposure_multiplier = attributes.exposure_multiplier
|
|
practical_attributes.exposure_sensitivity = attributes.exposure_sensitivity
|
|
|
|
return practical_attributes
|
|
|
|
func _unhandled_input(event):
|
|
if event is InputEventMouseMotion:
|
|
mouse_movement -= event.relative
|
|
|
|
if event is InputEventMouseButton:
|
|
match event.button_index:
|
|
MOUSE_BUTTON_LEFT:
|
|
physics_gun_fire_isdown = event.pressed
|
|
MOUSE_BUTTON_RIGHT:
|
|
zoom_isdown = event.pressed
|
|
|
|
if event is InputEventKey:
|
|
match event.keycode:
|
|
KEY_W:
|
|
forward_isdown = event.pressed
|
|
KEY_S:
|
|
backward_isdown = event.pressed
|
|
KEY_A:
|
|
left_isdown = event.pressed
|
|
KEY_D:
|
|
right_isdown = event.pressed
|
|
KEY_V:
|
|
cam_toggle_isdown = event.pressed
|
|
KEY_F:
|
|
flashlight_isdown = event.pressed
|
|
KEY_QUOTELEFT:
|
|
noclip_isdown = event.pressed
|
|
KEY_SHIFT:
|
|
sprint_isdown = event.pressed
|
|
KEY_SPACE:
|
|
jump_isdown = event.pressed
|
|
KEY_ESCAPE:
|
|
mousecapture_isdown = event.pressed
|
|
KEY_TAB:
|
|
shoulder_isdown = event.pressed
|
|
|
|
func switch_anim(anim, speed = 1.0):
|
|
if anim_player.current_animation != anim:
|
|
anim_player.play(anim, -1, speed)
|
|
|
|
func quat_rotate_toward(from: Quaternion, to: Quaternion, delta: float) -> Quaternion:
|
|
return from.slerp(to, clamp(delta / from.angle_to(to), 0.0, 1.0)).normalized()
|
|
|
|
func basis_rotate_toward(from: Basis, to: Basis, delta: float) -> Basis:
|
|
return Basis(quat_rotate_toward(from.get_rotation_quaternion(), to.get_rotation_quaternion(), delta)).orthonormalized()
|
|
|
|
func ease_in_out_sine(lerp: float) -> float:
|
|
return -(cos(PI * lerp) - 1.0) / 2.0
|
|
|
|
func ease_out_circ(lerp: float) -> float:
|
|
return sqrt(1.0 - pow(lerp - 1.0, 2.0))
|