641 lines
19 KiB
GDScript
641 lines
19 KiB
GDScript
class_name Runner extends RigidBody3D
|
|
|
|
const COYOTE_TIMEOUT: float = 0.25
|
|
const FLOOR_THRESHOLD: float = 0.5
|
|
const TURNING_LERP_WEIGHT: float = 0.125
|
|
const RIGHTING_LERP_WEIGHT: float = 0.03125
|
|
const CENTRIFUGAL_ACCELERATION: float = 50.0
|
|
const CENTRIFUGAL_COYOTE_FACTOR: float = 0.75
|
|
const TURNING_THRESHOLD: float = 0.25
|
|
const ANIMATION_BLEND_TIME: float = 0.25
|
|
const KNOCKBACK_SPEED: float = 10.0
|
|
const WALL_JUMP_FACTOR: float = 0.75
|
|
const DEATH_PLANE: float = -128.0
|
|
const RESPAWN_HEIGHT: float = 2.0
|
|
const LAND_SOUND_THRESHOLD: float = 0.0
|
|
const SWIM_VERTICAL_ACCELERATION: float = 5.0
|
|
const KNOCKBACK_FLASHING_DURATION: float = 1.5
|
|
const KNOCKBACK_FLASHING_FREQUENCY: float = 2.0
|
|
const STAIR_FACTOR_V: float = 0.5
|
|
const STAIR_FACTOR_H: float = 2.0
|
|
const STAIR_ANTICLIP: float = 0.125
|
|
const STAIR_CLIMB_ACCELERATION: float = 320.0
|
|
const STAIR_CLIMB_ANTI_FALL_BOOST: float = 4.0
|
|
const STAIR_CLIMB_ANTI_FALL_BOOST_THRESHOLD: float = 1.0
|
|
const STAIR_COYOTE_THRESHOLD: float = 0.97
|
|
const TURN_CUTOFF: float = 0.02
|
|
|
|
@onready var target_basis := basis
|
|
var floor_normal := Vector3.ZERO
|
|
var wall_normal := Vector3.ZERO
|
|
var stairs_detected := false
|
|
@onready var up_normal := basis.y
|
|
var floor_coyote_timer: float = 0.0
|
|
var wall_coyote_timer: float = 0.0
|
|
@onready var last_known_safe_position := global_position
|
|
var impetus := Vector3.ZERO
|
|
var state := &'fall'
|
|
@onready var animation_player := $'AnimationPlayer' as AnimationPlayer
|
|
@onready var audio_player := $'AudioStreamPlayer3D' as AudioStreamPlayer3D
|
|
@onready var mesh := $'Figure/Skeleton3D/Mesh' as MeshInstance3D
|
|
var height: float
|
|
var width: float
|
|
var knockback_flashing_timer: float = 0.0
|
|
@onready var raycast := PhysicsRayQueryParameters3D.new()
|
|
var finished_course := false
|
|
var floor_unsafe := false
|
|
var turn_target := Vector3.ZERO
|
|
var turn_target_basis := Basis.IDENTITY
|
|
|
|
@export var top_run_speed: float = 20.0
|
|
@export var run_acceleration: float = 15.0
|
|
@export var run_deceleration: float = 15.0
|
|
@export var jump_strength: float = 9.0
|
|
|
|
@export var jump_sound: AudioStream
|
|
@export var fall_sound: AudioStream
|
|
@export var land_sound: AudioStream
|
|
@export var knockback_sound: AudioStream
|
|
@export var slide_sound: AudioStream
|
|
|
|
@export var walk_animation_speed_factor: float = 0.1
|
|
@export var run_animation_speed_factor: float = 0.1
|
|
@export var sprint_animation_speed_factor: float = 0.1
|
|
@export var swim_animation_speed_factor: float = 0.1
|
|
|
|
@export var ability: RunnerAbility
|
|
@export var taunt_animation: StringName
|
|
|
|
func _find_dimensions() -> void:
|
|
var shape := ($'CollisionShape3D' as CollisionShape3D).shape
|
|
if shape is BoxShape3D:
|
|
width = shape.size.slide(Vector3.UP).length()
|
|
height = shape.size.y
|
|
elif shape is CapsuleShape3D or shape is CylinderShape3D:
|
|
width = 2.0*shape.radius
|
|
height = shape.height
|
|
elif shape is SphereShape3D:
|
|
width = 2.0*shape.radius
|
|
height = 2.0*shape.radius
|
|
else:
|
|
width = 0.0
|
|
height = 0.0
|
|
|
|
func _ready() -> void:
|
|
add_to_group(&'runners')
|
|
continuous_cd = true
|
|
max_contacts_reported = 24
|
|
contact_monitor = true
|
|
can_sleep = false
|
|
lock_rotation = true
|
|
physics_material_override = PhysicsMaterial.new()
|
|
physics_material_override.friction = 0.0
|
|
animation_player.playback_default_blend_time = ANIMATION_BLEND_TIME
|
|
audio_player.bus = &'Sound'
|
|
raycast.exclude = [self]
|
|
raycast.hit_back_faces = true
|
|
_find_dimensions()
|
|
_do_state_begin()
|
|
|
|
func _integrate_forces(body_state: PhysicsDirectBodyState3D) -> void:
|
|
_detect_floors_and_walls(body_state)
|
|
_detect_stairs(body_state)
|
|
_adjust_basis(body_state)
|
|
_apply_fake_and_exaggerated_centrifugal_force(body_state)
|
|
_respond_to_impetus(body_state)
|
|
_apply_deceleration(body_state)
|
|
_enforce_top_speed(body_state)
|
|
_do_state_integrate_forces(body_state)
|
|
_update_animation_speed()
|
|
_clear_impetus()
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
_check_for_death_plane()
|
|
_update_knockback_flashing(delta)
|
|
|
|
func _update_knockback_flashing(delta: float) -> void:
|
|
if knockback_flashing_timer > 0.0:
|
|
knockback_flashing_timer -= delta
|
|
if knockback_flashing_timer <= 0.0:
|
|
mesh.visible = true
|
|
else:
|
|
mesh.visible = (
|
|
2.0*KNOCKBACK_FLASHING_FREQUENCY*fposmod(
|
|
knockback_flashing_timer,
|
|
1.0/(2.0*KNOCKBACK_FLASHING_FREQUENCY)
|
|
) >= 0.5
|
|
)
|
|
|
|
func _detect_floors_and_walls(body_state: PhysicsDirectBodyState3D) -> void:
|
|
var old_floor_normal := floor_normal
|
|
var old_wall_normal := wall_normal
|
|
floor_normal = Vector3.ZERO
|
|
wall_normal = Vector3.ZERO
|
|
for i in body_state.get_contact_count():
|
|
var other := body_state.get_contact_collider_object(i)
|
|
if not (
|
|
other is RigidBody3D and (not (other is Platform)) and
|
|
(other as RigidBody3D).mass <= mass
|
|
):
|
|
var normal := body_state.get_contact_local_normal(i)
|
|
if (
|
|
normal.dot(up_normal) >= FLOOR_THRESHOLD or
|
|
normal.dot(Vector3.UP) >= FLOOR_THRESHOLD
|
|
):
|
|
floor_normal += normal
|
|
else:
|
|
wall_normal += normal
|
|
if (
|
|
(not floor_normal.is_zero_approx())
|
|
and state != &'jump' and state != &'wall_jump'
|
|
):
|
|
floor_normal = floor_normal.normalized()
|
|
floor_coyote_timer = COYOTE_TIMEOUT
|
|
if not floor_unsafe:
|
|
last_known_safe_position = (
|
|
global_position + floor_normal*RESPAWN_HEIGHT
|
|
)
|
|
elif floor_coyote_timer > 0.0:
|
|
floor_normal = old_floor_normal
|
|
floor_coyote_timer -= body_state.step
|
|
if not wall_normal.is_zero_approx():
|
|
wall_normal = wall_normal.normalized()
|
|
wall_coyote_timer = COYOTE_TIMEOUT
|
|
elif wall_coyote_timer > 0.0:
|
|
wall_normal = old_wall_normal
|
|
wall_coyote_timer -= body_state.step
|
|
|
|
func _detect_stairs(
|
|
body_state: PhysicsDirectBodyState3D
|
|
) -> void:
|
|
if (
|
|
(not wall_normal.is_zero_approx()) and
|
|
(not floor_normal.is_zero_approx()) and
|
|
floor_coyote_timer/COYOTE_TIMEOUT > STAIR_COYOTE_THRESHOLD
|
|
):
|
|
var dss := get_world_3d().direct_space_state
|
|
raycast.to = body_state.transform.origin + (
|
|
up_normal*STAIR_ANTICLIP -
|
|
width*STAIR_FACTOR_H*wall_normal
|
|
)
|
|
raycast.from = raycast.to + height*STAIR_FACTOR_V*up_normal
|
|
var clip := dss.intersect_ray(raycast)
|
|
stairs_detected = (
|
|
(not clip.is_empty()) and not clip.normal.is_zero_approx()
|
|
)
|
|
else:
|
|
stairs_detected = false
|
|
|
|
func _adjust_basis(body_state: PhysicsDirectBodyState3D) -> void:
|
|
if not floor_normal.is_zero_approx():
|
|
up_normal = floor_normal.normalized()
|
|
else:
|
|
up_normal = (
|
|
up_normal.lerp(Vector3.UP, RIGHTING_LERP_WEIGHT).normalized()
|
|
)
|
|
if up_normal.is_zero_approx():
|
|
up_normal = Vector3.UP
|
|
var lookdir = _orientation_in_current_state()
|
|
if body_state.total_gravity.dot(Vector3.UP) < 0.0:
|
|
lookdir = lookdir.slide(up_normal)
|
|
if (
|
|
lookdir.length() >= TURNING_THRESHOLD and
|
|
not up_normal.cross(lookdir.normalized()).is_zero_approx()
|
|
):
|
|
target_basis = Basis.looking_at(lookdir, up_normal, true)
|
|
angular_velocity = (
|
|
basis.x.cross(target_basis.x) +
|
|
basis.y.cross(target_basis.y) +
|
|
basis.z.cross(target_basis.z)
|
|
)*TURNING_LERP_WEIGHT/body_state.step
|
|
|
|
func _apply_fake_and_exaggerated_centrifugal_force(
|
|
body_state: PhysicsDirectBodyState3D
|
|
) -> void:
|
|
if (
|
|
(not floor_normal.is_zero_approx()) and
|
|
floor_coyote_timer/COYOTE_TIMEOUT >= CENTRIFUGAL_COYOTE_FACTOR and
|
|
state != &'jump' and state != &'wall_jump' and state != &'fall'
|
|
):
|
|
body_state.apply_central_force(
|
|
-mass *
|
|
CENTRIFUGAL_ACCELERATION *
|
|
((linear_velocity.slide(up_normal).length()/top_run_speed)**2.0) *
|
|
up_normal
|
|
)
|
|
|
|
func change_state(new_state: StringName) -> void:
|
|
if not finished_course:
|
|
_do_state_end()
|
|
state = new_state
|
|
_do_state_begin()
|
|
|
|
func _orientation_in_current_state() -> Vector3:
|
|
match state:
|
|
&'ability': return ability.orientation(self)
|
|
&'climb_stairs':
|
|
return basis.z if wall_normal.is_zero_approx() else -wall_normal
|
|
&'fall': return linear_velocity
|
|
&'jump': return linear_velocity
|
|
&'knockback': return basis.z
|
|
&'run': return linear_velocity
|
|
&'sprint': return linear_velocity
|
|
&'stand': return basis.z
|
|
&'swim': return linear_velocity
|
|
&'taunt': return basis.z
|
|
&'turn_to_look': return turn_target - global_position
|
|
&'walk': return linear_velocity
|
|
&'wall_jump': return linear_velocity
|
|
&'wall_slide': return wall_normal
|
|
_: return basis.z
|
|
|
|
func _play_land_sound_if_landing_hard_enough() -> void:
|
|
if linear_velocity.project(up_normal).length() >= LAND_SOUND_THRESHOLD:
|
|
audio_player.stream = land_sound
|
|
audio_player.play()
|
|
|
|
func _do_state_begin() -> void:
|
|
match state:
|
|
&'ability':
|
|
animation_player.play(ability.animation)
|
|
audio_player.stream = ability.sound
|
|
audio_player.play()
|
|
ability.begin(self)
|
|
&'climb_stairs':
|
|
animation_player.play(&'walk')
|
|
&'fall':
|
|
animation_player.play(&'fall')
|
|
audio_player.stream = fall_sound
|
|
audio_player.play()
|
|
&'jump':
|
|
animation_player.play(&'jump')
|
|
audio_player.stream = jump_sound
|
|
audio_player.play()
|
|
linear_velocity += up_normal*jump_strength
|
|
floor_normal = Vector3.ZERO
|
|
floor_coyote_timer = 0.0
|
|
&'knockback':
|
|
animation_player.play(&'knockback')
|
|
audio_player.stream = knockback_sound
|
|
audio_player.play()
|
|
floor_normal = Vector3.ZERO
|
|
floor_coyote_timer = 0.0
|
|
knockback_flashing_timer = KNOCKBACK_FLASHING_DURATION
|
|
&'run':
|
|
animation_player.play(&'run')
|
|
&'sprint':
|
|
animation_player.play(&'sprint')
|
|
&'stand':
|
|
animation_player.play(&'stand')
|
|
&'swim':
|
|
animation_player.play(&'swim')
|
|
&'taunt':
|
|
animation_player.play(taunt_animation)
|
|
&'turn_to_look':
|
|
animation_player.play(&'stand')
|
|
&'walk':
|
|
animation_player.play(&'walk')
|
|
&'wall_jump':
|
|
animation_player.play(&'jump')
|
|
audio_player.stream = jump_sound
|
|
audio_player.play()
|
|
linear_velocity = (
|
|
linear_velocity.slide(up_normal) +
|
|
(2.0*wall_normal + up_normal)*jump_strength*WALL_JUMP_FACTOR
|
|
)
|
|
wall_normal = Vector3.ZERO
|
|
wall_coyote_timer = 0.0
|
|
&'wall_slide':
|
|
animation_player.play(&'wall_slide')
|
|
audio_player.stream = slide_sound
|
|
audio_player.play()
|
|
_: pass
|
|
|
|
func _impetus_multiplier_in_current_state() -> float:
|
|
match state:
|
|
&'ability': return ability.impetus_multiplier
|
|
&'climb_stairs': return 0.0
|
|
&'fall': return 0.5
|
|
&'jump': return 0.5
|
|
&'knockback': return 0.0
|
|
&'run': return 1.0
|
|
&'sprint': return 1.0
|
|
&'stand': return 0.0
|
|
&'swim': return 1.0
|
|
&'taunt': return 0.0
|
|
&'turn_to_look': return 0.0
|
|
&'walk': return 0.5
|
|
&'wall_jump': return 0.5
|
|
&'wall_slide': return 0.25
|
|
_: return 1.0
|
|
|
|
func abnormal_gravity(body_state: PhysicsDirectBodyState3D) -> bool:
|
|
return body_state.total_gravity.dot(Vector3.UP) > 0.0
|
|
|
|
func _do_state_integrate_forces(body_state: PhysicsDirectBodyState3D) -> void:
|
|
ability.passive(self, body_state.step, state == &'ability')
|
|
match state:
|
|
&'ability':
|
|
ability.integrate_forces(self, body_state)
|
|
&'climb_stairs':
|
|
if impetus.dot(wall_normal) >= 0.0:
|
|
body_state.linear_velocity = (
|
|
body_state.linear_velocity.slide(up_normal)
|
|
)
|
|
change_state(&'walk')
|
|
elif wall_normal.is_zero_approx() or (
|
|
wall_coyote_timer/COYOTE_TIMEOUT < STAIR_COYOTE_THRESHOLD
|
|
):
|
|
body_state.linear_velocity = (
|
|
body_state.linear_velocity.slide(up_normal)
|
|
)
|
|
if body_state.linear_velocity.length() < (
|
|
STAIR_CLIMB_ANTI_FALL_BOOST_THRESHOLD
|
|
):
|
|
body_state.linear_velocity += basis.z*(
|
|
STAIR_CLIMB_ANTI_FALL_BOOST
|
|
)
|
|
change_state(&'walk')
|
|
else:
|
|
body_state.linear_velocity += (
|
|
STAIR_CLIMB_ACCELERATION*up_normal*body_state.step
|
|
)
|
|
&'fall':
|
|
if not floor_normal.is_zero_approx():
|
|
_play_land_sound_if_landing_hard_enough()
|
|
change_state(&'run')
|
|
elif abnormal_gravity(body_state):
|
|
change_state(&'swim')
|
|
elif not wall_normal.is_zero_approx():
|
|
change_state(&'wall_slide')
|
|
&'jump':
|
|
if not wall_normal.is_zero_approx():
|
|
change_state(&'wall_slide')
|
|
elif (
|
|
body_state.linear_velocity.normalized().dot(up_normal) < 0.125
|
|
):
|
|
change_state(&'fall')
|
|
elif abnormal_gravity(body_state) and (
|
|
body_state.linear_velocity.normalized().dot(up_normal) < 0.5
|
|
):
|
|
change_state(&'swim')
|
|
&'knockback':
|
|
if knockback_flashing_timer <= KNOCKBACK_FLASHING_DURATION/2.0:
|
|
if not floor_normal.is_zero_approx():
|
|
_play_land_sound_if_landing_hard_enough()
|
|
change_state(&'stand')
|
|
elif not wall_normal.is_zero_approx():
|
|
change_state(&'wall_slide')
|
|
&'run':
|
|
if floor_normal.is_zero_approx():
|
|
change_state(&'fall')
|
|
else:
|
|
var speed_factor := (
|
|
body_state.linear_velocity.slide(up_normal).length() /
|
|
top_run_speed
|
|
)
|
|
if speed_factor < 0.125:
|
|
change_state(&'walk')
|
|
elif speed_factor > 0.625:
|
|
change_state(&'sprint')
|
|
&'sprint':
|
|
if floor_normal.is_zero_approx():
|
|
change_state(&'fall')
|
|
else:
|
|
var speed_factor := (
|
|
body_state.linear_velocity.slide(up_normal).length() /
|
|
top_run_speed
|
|
)
|
|
if speed_factor < 0.625:
|
|
change_state(&'run')
|
|
&'stand':
|
|
if floor_normal.is_zero_approx():
|
|
change_state(&'fall')
|
|
elif not impetus.is_zero_approx():
|
|
change_state(&'walk')
|
|
else:
|
|
var speed_factor := (
|
|
body_state.linear_velocity.slide(up_normal).length() /
|
|
top_run_speed
|
|
)
|
|
if speed_factor > 0.125:
|
|
change_state(&'run')
|
|
&'swim':
|
|
var iz: float = (impetus*basis).z
|
|
body_state.linear_velocity -= (
|
|
up_normal*iz*SWIM_VERTICAL_ACCELERATION*body_state.step
|
|
)
|
|
if iz < 0.0:
|
|
body_state.linear_velocity -= (
|
|
1.5*basis.z*iz*run_acceleration*body_state.step
|
|
)
|
|
if not floor_normal.is_zero_approx():
|
|
change_state(&'run')
|
|
elif not abnormal_gravity(body_state):
|
|
change_state(&'fall')
|
|
&'taunt':
|
|
body_state.linear_velocity = Vector3.ZERO
|
|
&'turn_to_look':
|
|
if (basis.z - turn_target_basis.z).length() < TURN_CUTOFF:
|
|
change_state(&'stand')
|
|
&'walk':
|
|
if stairs_detected:
|
|
change_state(&'climb_stairs')
|
|
elif floor_normal.is_zero_approx():
|
|
change_state(&'fall')
|
|
elif impetus.is_zero_approx():
|
|
change_state(&'stand')
|
|
else:
|
|
var speed_factor := (
|
|
body_state.linear_velocity.slide(up_normal).length() /
|
|
top_run_speed
|
|
)
|
|
if speed_factor > 0.125:
|
|
change_state(&'run')
|
|
&'wall_jump':
|
|
if body_state.linear_velocity.normalized().dot(up_normal) < 0.125:
|
|
change_state(&'fall')
|
|
&'wall_slide':
|
|
if not floor_normal.is_zero_approx():
|
|
_play_land_sound_if_landing_hard_enough()
|
|
change_state(&'run')
|
|
elif wall_normal.is_zero_approx():
|
|
change_state(&'fall')
|
|
_: pass
|
|
|
|
func _respond_to_impetus(body_state: PhysicsDirectBodyState3D) -> void:
|
|
body_state.linear_velocity += (
|
|
_impetus_multiplier_in_current_state() *
|
|
impetus *
|
|
run_acceleration *
|
|
body_state.step
|
|
)
|
|
|
|
func _apply_deceleration(body_state: PhysicsDirectBodyState3D) -> void:
|
|
if impetus.is_zero_approx():
|
|
var horizontal_velocity = body_state.linear_velocity.slide(up_normal)
|
|
if not horizontal_velocity.is_zero_approx():
|
|
var speed = horizontal_velocity.length()
|
|
var direction = horizontal_velocity/speed
|
|
speed = max(speed - run_deceleration*body_state.step, 0.0)
|
|
body_state.linear_velocity -= horizontal_velocity - direction*speed
|
|
|
|
func _enforce_top_speed(body_state: PhysicsDirectBodyState3D) -> void:
|
|
var horizontal_velocity = body_state.linear_velocity.slide(up_normal)
|
|
var speed = horizontal_velocity.length()
|
|
if speed > top_run_speed:
|
|
var direction = horizontal_velocity/speed
|
|
body_state.linear_velocity -= horizontal_velocity - direction*clampf(
|
|
speed - run_acceleration*body_state.step,
|
|
0.0, speed
|
|
)
|
|
|
|
func _clear_impetus() -> void:
|
|
impetus = Vector3.ZERO
|
|
|
|
func _do_state_end() -> void:
|
|
match state:
|
|
&'ability': ability.end(self)
|
|
&'climb_stairs': pass
|
|
&'fall': pass
|
|
&'jump': pass
|
|
&'knockback': pass
|
|
&'run': pass
|
|
&'sprint': pass
|
|
&'stand': pass
|
|
&'swim': pass
|
|
&'taunt': pass
|
|
&'turn_to_look': pass
|
|
&'walk': pass
|
|
&'wall_jump': pass
|
|
&'wall_slide':
|
|
if audio_player.stream == slide_sound:
|
|
audio_player.stop()
|
|
_: pass
|
|
|
|
func _update_animation_speed() -> void:
|
|
match state:
|
|
&'ability':
|
|
animation_player.speed_scale = ability.animation_speed(self)
|
|
&'climb_stairs':
|
|
animation_player.speed_scale = 1.0 + (
|
|
linear_velocity.length()*walk_animation_speed_factor
|
|
)
|
|
&'fall':
|
|
animation_player.speed_scale = 1.0
|
|
&'jump':
|
|
animation_player.speed_scale = 1.0
|
|
&'knockback':
|
|
animation_player.speed_scale = 1.0
|
|
&'run':
|
|
animation_player.speed_scale = 1.0 + (
|
|
linear_velocity.length()*run_animation_speed_factor
|
|
)
|
|
&'sprint':
|
|
animation_player.speed_scale = 1.0 + (
|
|
linear_velocity.length()*sprint_animation_speed_factor
|
|
)
|
|
&'stand':
|
|
animation_player.speed_scale = 1.0
|
|
&'swim':
|
|
animation_player.speed_scale = 0.25 + (
|
|
linear_velocity.length()*swim_animation_speed_factor
|
|
)
|
|
&'taunt':
|
|
animation_player.speed_scale = 1.0
|
|
&'turn_to_look':
|
|
animation_player.speed_scale = 1.0
|
|
&'walk':
|
|
animation_player.speed_scale = 1.0 + (
|
|
linear_velocity.length()*walk_animation_speed_factor
|
|
)
|
|
&'wall_jump':
|
|
animation_player.speed_scale = 1.0
|
|
&'wall_slide':
|
|
animation_player.speed_scale = 1.0
|
|
_: pass
|
|
|
|
func _check_for_death_plane() -> void:
|
|
if global_position.y <= DEATH_PLANE:
|
|
respawn()
|
|
|
|
func respawn() -> void:
|
|
global_position = last_known_safe_position
|
|
linear_velocity = Vector3.ZERO
|
|
if FX.camera_is_following(self):
|
|
FX.hard_reorient_camera(basis.z)
|
|
audio_player.stream = knockback_sound
|
|
audio_player.play()
|
|
knockback_flashing_timer = KNOCKBACK_FLASHING_DURATION
|
|
|
|
func jump() -> void:
|
|
if (
|
|
state != &'knockback' and
|
|
state != &'turn_to_look' and
|
|
not finished_course
|
|
):
|
|
if (
|
|
state == &'jump' or state == &'wall_jump' or
|
|
state == &'fall' or state == &'ability'
|
|
):
|
|
do_ability()
|
|
elif state == &'swim' or not floor_normal.is_zero_approx():
|
|
change_state(&'jump')
|
|
elif not wall_normal.is_zero_approx():
|
|
change_state(&'wall_jump')
|
|
|
|
func cancel_jump() -> void:
|
|
if (state == &'jump' or state == &'wall_jump') and not finished_course:
|
|
linear_velocity = (
|
|
linear_velocity.slide(up_normal) +
|
|
linear_velocity.project(up_normal)/2.0
|
|
)
|
|
|
|
func do_ability() -> void:
|
|
if (
|
|
state != &'knockback' and
|
|
state != &'turn_to_look' and
|
|
not finished_course
|
|
):
|
|
if state != &'ability':
|
|
if ability.available(self):
|
|
change_state(&'ability')
|
|
else:
|
|
ability.on_repeated_input(self)
|
|
|
|
func knockback(origin: Vector3, strength: float) -> void:
|
|
if (
|
|
state != &'knockback' and knockback_flashing_timer <= 0.0 and
|
|
not finished_course
|
|
):
|
|
linear_velocity = Vector3.ZERO
|
|
apply_central_impulse((
|
|
(global_position - origin).slide(up_normal).normalized() +
|
|
up_normal/2.0
|
|
).normalized()*strength)
|
|
change_state(&'knockback')
|
|
look_at(
|
|
global_position + (origin - global_position).slide(up_normal),
|
|
up_normal,
|
|
true
|
|
)
|
|
|
|
func finish_course() -> void:
|
|
change_state(&'taunt')
|
|
finished_course = true
|
|
|
|
func cancel_finish_course() -> void:
|
|
finished_course = false
|
|
change_state(&'stand')
|
|
|
|
func turn_toward(where: Vector3) -> void:
|
|
turn_target = where
|
|
turn_target_basis = Basis.looking_at(
|
|
(where - global_position).slide(up_normal),
|
|
up_normal, true
|
|
)
|
|
change_state(&'turn_to_look')
|
|
var done := func() -> bool:
|
|
return state != &'turn_to_look'
|
|
await Wait.until(done)
|