stick-the-quick/characters/RunnerObserver.gd

134 lines
3.9 KiB
GDScript

class_name RunnerObserver extends Camera3D
signal area_entered(where: Area3D)
signal area_exited(where: Area3D)
const RADIUS = 1.0
const BIRDS_EYE_FACTOR: float = 1.5
const DECENTERING_FACTOR: float = 0.4
const UP_LERP_WEIGHT: float = 0.03125
const FOLLOW_LERP_WEIGHT: float = 1.0
const FOV_LERP_WEIGHT: float = 0.0625
const DEFAULT_DISTANCE: float = 5.0
const TARGET_OVERRIDE_TURN_LERP_WEIGHT: float = 0.03125
const TARGET_OVERRIDE_TURN_TIMEOUT: float = 3.0
const TURN_LERP_WEIGHT: float = 1.0
const ANTICLIP: float = 0.125
const TARGET_OVERRIDE_DECENTERING_METAFACTOR: float = 0.25
@onready var runner := $'..' as Runner
var maintain_distance: float
var maintain_direction: Vector3
var up_normal := Vector3.UP
var _target_override_turn_timeout: float = -1.0
@onready var _wall_raycast = PhysicsRayQueryParameters3D.new()
var _target_override: Node3D = null
var target_override: Node3D:
get: return _target_override
set(target):
if target != _target_override:
_target_override_turn_timeout = TARGET_OVERRIDE_TURN_TIMEOUT
_target_override = target
@onready var _hitbox := $Area3D as Area3D
func _ready() -> void:
top_level = true
_hitbox.area_entered.connect(_emit_area_entered)
_hitbox.area_exited.connect(_emit_area_exited)
if runner:
switch_runner(runner)
func switch_runner(whom: Runner, distance: float = DEFAULT_DISTANCE) -> void:
runner = whom
if runner:
maintain_distance = distance
maintain_direction = runner.basis.z - runner.basis.y/2.0
reorient(false, true)
func detach() -> void:
switch_runner(null)
func _try_go(where: Vector3) -> void:
_wall_raycast.from = global_position
_wall_raycast.to = where
var raycast_results := (
get_world_3d().direct_space_state.intersect_ray(_wall_raycast)
)
if raycast_results.is_empty(): global_position = where
else:
global_position = raycast_results.position + (
global_position - raycast_results.position +
raycast_results.normal
).normalized()*ANTICLIP
func reorient(directional_follow: bool = true, snap: bool = false) -> void:
var target := runner as Node3D
var decentering_factor: float = DECENTERING_FACTOR
if target_override:
target = target_override
decentering_factor = -decentering_factor*(
TARGET_OVERRIDE_DECENTERING_METAFACTOR
)
var decentering := (
BIRDS_EYE_FACTOR*maintain_distance*decentering_factor*up_normal
)
if target:
var lv := Vector3.ZERO
var mlv: float = 30.0
if target is Runner:
lv = (target as Runner).linear_velocity
mlv = (target as Runner).top_run_speed
elif target is RigidBody3D:
lv = (target as RigidBody3D).linear_velocity
if directional_follow:
maintain_direction = (
target.global_position - global_position + decentering
).normalized()
up_normal = up_normal.lerp(
(target as Runner).up_normal if target is Runner else Vector3.UP,
1.0 if snap else UP_LERP_WEIGHT
).normalized()
_try_go(global_position.lerp(
target.global_position -
maintain_distance*maintain_direction + decentering,
1.0 if snap else (1.0 - 1.0/(1.0 + lv.length()))
))
if ((
target.global_position - global_position
).cross(up_normal).length() >= 0.1):
var desired_basis := Basis.looking_at(
target.global_position + (
target.global_position - global_position
).length()*decentering_factor*up_normal - global_position,
up_normal
)
basis = basis.slerp(
desired_basis, lerpf(
TURN_LERP_WEIGHT, TARGET_OVERRIDE_TURN_LERP_WEIGHT,
clampf(
_target_override_turn_timeout /
TARGET_OVERRIDE_TURN_TIMEOUT,
0.0, 1.0
)
)*(
(1.0 + target_override.linear_velocity.length())
if target_override is RigidBody3D
else 1.0
)
)
fov = lerp(fov, min(75.0 + 15.0*(
lv.project(basis.z).length()/mlv
), 179.0), FOV_LERP_WEIGHT)
func _physics_process(delta: float) -> void:
reorient()
_target_override_turn_timeout -= delta
func _emit_area_entered(where: Area3D) -> void:
area_entered.emit(where)
func _emit_area_exited(where: Area3D) -> void:
area_exited.emit(where)