134 lines
3.9 KiB
GDScript3
134 lines
3.9 KiB
GDScript3
|
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)
|