Refactor player controller
This commit is contained in:
parent
918af0c4cb
commit
4133671b8f
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,6 @@
|
||||||
extends PhysicsBody3D
|
class_name Player extends PhysicsBody3D
|
||||||
|
|
||||||
|
enum MovementType { VERTICAL, LATERAL }
|
||||||
|
|
||||||
@export var jump_speed := 4.5
|
@export var jump_speed := 4.5
|
||||||
@export var max_speed := 8
|
@export var max_speed := 8
|
||||||
|
@ -61,24 +63,22 @@ extends PhysicsBody3D
|
||||||
@export var max_iteration_count := 4
|
@export var max_iteration_count := 4
|
||||||
|
|
||||||
var jump_pressed := false
|
var jump_pressed := false
|
||||||
|
var escape_pressed_prev := false
|
||||||
|
|
||||||
var gravity := 9.8
|
var gravity := 9.8
|
||||||
|
|
||||||
var _velocity: Vector3 = Vector3()
|
var _velocity: Vector3 = Vector3()
|
||||||
var at_max_speed: bool = true
|
var at_max_speed := true
|
||||||
|
|
||||||
var grounded: bool = false
|
var grounded: bool = false
|
||||||
var ground_normal: Vector3
|
var ground_normal: Vector3
|
||||||
var steep_slope_normals: Array[Vector3] = []
|
var steep_slope_normals: Array[Vector3] = []
|
||||||
var total_stepped_height: float = 0
|
var total_stepped_height: float = 0
|
||||||
|
|
||||||
var escape_pressed: int
|
# TODO Should these be class variables? Could get away with just locals
|
||||||
var vertical_collisions: Array[KinematicCollision3D]
|
var vertical_collisions: Array[KinematicCollision3D]
|
||||||
var lateral_collisions: Array[KinematicCollision3D]
|
var lateral_collisions: Array[KinematicCollision3D]
|
||||||
var snap_collisions: Array[KinematicCollision3D]
|
var snap_collisions: Array[KinematicCollision3D]
|
||||||
|
|
||||||
enum MovementType { VERTICAL, LATERAL }
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
lock_mouse()
|
lock_mouse()
|
||||||
|
@ -103,26 +103,24 @@ func _input(event: InputEvent) -> void:
|
||||||
|
|
||||||
|
|
||||||
# TODO should this be in unhandled input?
|
# TODO should this be in unhandled input?
|
||||||
|
# TODO this should return void and just set the appropriate fields (input_dir!!)
|
||||||
# Input buffering? lol lmao
|
# Input buffering? lol lmao
|
||||||
func get_input() -> Vector2:
|
func get_input() -> Vector2:
|
||||||
if !Input.is_key_pressed(KEY_ESCAPE) && escape_pressed == 1:
|
# Toggle mouse capture mode
|
||||||
|
var escape_pressed_curr := Input.is_key_pressed(KEY_ESCAPE)
|
||||||
|
if escape_pressed_curr && !escape_pressed_prev:
|
||||||
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||||
unlock_mouse()
|
unlock_mouse()
|
||||||
else:
|
else:
|
||||||
lock_mouse()
|
lock_mouse()
|
||||||
|
escape_pressed_prev = escape_pressed_curr
|
||||||
|
|
||||||
if Input.is_key_pressed(KEY_ESCAPE):
|
# We don't want to be moving if the mouse isn't captured!
|
||||||
escape_pressed = 1
|
|
||||||
elif !Input.is_key_pressed(KEY_ESCAPE):
|
|
||||||
escape_pressed = 0
|
|
||||||
|
|
||||||
jump_pressed = Input.is_action_just_pressed("cc_jump")
|
|
||||||
|
|
||||||
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||||
return Vector2(0, 0)
|
return Vector2(0, 0)
|
||||||
|
|
||||||
if Input.is_action_just_pressed("cc_sprint"):
|
jump_pressed = Input.is_action_just_pressed("cc_jump")
|
||||||
at_max_speed = !at_max_speed
|
at_max_speed = Input.is_action_pressed("cc_sprint")
|
||||||
|
|
||||||
var input_dir: Vector2 = Vector2()
|
var input_dir: Vector2 = Vector2()
|
||||||
if Input.is_action_pressed("cc_forward"):
|
if Input.is_action_pressed("cc_forward"):
|
||||||
|
@ -140,39 +138,27 @@ func get_input() -> Vector2:
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
# Before Move
|
# Calculate our target velocity
|
||||||
var _desired_horz_velocity := get_input()
|
var move_speed := max_speed if at_max_speed else slow_speed
|
||||||
var desired_horz_velocity := Vector3.ZERO
|
var target_velocity_h := get_input() * move_speed
|
||||||
desired_horz_velocity.x = _desired_horz_velocity.x
|
var target_velocity_v := 0.0
|
||||||
desired_horz_velocity.z = _desired_horz_velocity.y
|
|
||||||
|
|
||||||
if at_max_speed:
|
|
||||||
desired_horz_velocity *= max_speed
|
|
||||||
else:
|
|
||||||
desired_horz_velocity *= slow_speed
|
|
||||||
|
|
||||||
var desired_vertical_velocity := Vector3.ZERO
|
|
||||||
|
|
||||||
if !grounded:
|
if !grounded:
|
||||||
desired_vertical_velocity.y = _velocity.y
|
target_velocity_v += _velocity.y
|
||||||
elif jump_pressed:
|
elif jump_pressed:
|
||||||
desired_vertical_velocity.y = jump_speed
|
target_velocity_v += jump_speed
|
||||||
|
target_velocity_v -= gravity * delta
|
||||||
|
|
||||||
desired_vertical_velocity.y -= gravity * delta
|
var target_velocity := Vector3(target_velocity_h.x, target_velocity_v, target_velocity_h.y)
|
||||||
|
move(target_velocity, delta)
|
||||||
# Could have calculated them together
|
|
||||||
# But separating them makes it easier to experiment with different movement algorithms
|
|
||||||
var desired_velocity := desired_horz_velocity + desired_vertical_velocity
|
|
||||||
move(desired_velocity, delta)
|
|
||||||
|
|
||||||
|
|
||||||
# Entry point to moving
|
# Entry point to moving
|
||||||
func move(intended_velocity: Vector3, delta: float) -> void:
|
func move(intended_velocity: Vector3, delta: float) -> void:
|
||||||
var start_position := position
|
var start_position := position
|
||||||
|
|
||||||
var lateral_translation := horz(intended_velocity * delta)
|
var lateral_translation := _horz(intended_velocity * delta)
|
||||||
var initial_lateral_translation := lateral_translation
|
var initial_lateral_translation := lateral_translation
|
||||||
var vertical_translation := vert(intended_velocity * delta)
|
var vertical_translation := _vert(intended_velocity * delta)
|
||||||
var initial_vertical_translation := vertical_translation
|
var initial_vertical_translation := vertical_translation
|
||||||
|
|
||||||
grounded = false
|
grounded = false
|
||||||
|
@ -186,46 +172,44 @@ func move(intended_velocity: Vector3, delta: float) -> void:
|
||||||
# An initial grounded check is important because ground normal is used
|
# An initial grounded check is important because ground normal is used
|
||||||
# to detect seams with steep slopes; which often are collided with before the ground
|
# to detect seams with steep slopes; which often are collided with before the ground
|
||||||
if vertical_translation.y <= 0:
|
if vertical_translation.y <= 0:
|
||||||
var initial_grounded_collision := move_and_collide(
|
var collision := move_and_collide(
|
||||||
Vector3.DOWN * ground_cast_distance, true, depenetration_margin
|
Vector3.DOWN * ground_cast_distance, true, depenetration_margin
|
||||||
)
|
)
|
||||||
if initial_grounded_collision:
|
if collision:
|
||||||
if (
|
var normal := collision.get_normal(0)
|
||||||
initial_grounded_collision.get_normal(0).angle_to(Vector3.UP)
|
if _under_slope_limit(normal):
|
||||||
< deg_to_rad(slope_limit)
|
|
||||||
):
|
|
||||||
grounded = true
|
grounded = true
|
||||||
ground_normal = initial_grounded_collision.get_normal(0)
|
ground_normal = normal
|
||||||
|
|
||||||
# === Iterate Movement Laterally
|
# Lateral movement
|
||||||
var lateral_iterations := 0
|
for i in max_iteration_count:
|
||||||
while lateral_translation.length() > 0 and lateral_iterations < max_iteration_count:
|
if lateral_translation.length() <= 0:
|
||||||
|
break
|
||||||
lateral_translation = move_iteration(
|
lateral_translation = move_iteration(
|
||||||
MovementType.LATERAL,
|
MovementType.LATERAL,
|
||||||
lateral_collisions,
|
lateral_collisions,
|
||||||
initial_lateral_translation,
|
initial_lateral_translation,
|
||||||
lateral_translation
|
lateral_translation
|
||||||
)
|
)
|
||||||
lateral_iterations += 1
|
|
||||||
|
|
||||||
# De-jitter by just ignoring lateral movement
|
# De-jitter by just ignoring lateral movement
|
||||||
# (multiple steep slopes have been collided, but movement is very small)
|
# (multiple steep slopes have been collided, but movement is very small)
|
||||||
if (
|
if (
|
||||||
steep_slope_normals.size() > 1
|
steep_slope_normals.size() > 1
|
||||||
and horz(position - start_position).length() < steep_slope_jitter_reduce
|
and _horz(position - start_position).length() < steep_slope_jitter_reduce
|
||||||
):
|
):
|
||||||
position = start_position
|
position = start_position
|
||||||
|
|
||||||
# === Iterate Movement Vertically
|
# Vertical movement
|
||||||
var vertical_iterations := 0
|
for i in max_iteration_count:
|
||||||
while vertical_translation.length() > 0 and vertical_iterations < max_iteration_count:
|
if vertical_translation.length() <= 0:
|
||||||
|
break
|
||||||
vertical_translation = move_iteration(
|
vertical_translation = move_iteration(
|
||||||
MovementType.VERTICAL,
|
MovementType.VERTICAL,
|
||||||
vertical_collisions,
|
vertical_collisions,
|
||||||
initial_vertical_translation,
|
initial_vertical_translation,
|
||||||
vertical_translation
|
vertical_translation
|
||||||
)
|
)
|
||||||
vertical_iterations += 1
|
|
||||||
|
|
||||||
# Don't include step height in actual velocity
|
# Don't include step height in actual velocity
|
||||||
var actual_translation := position - start_position
|
var actual_translation := position - start_position
|
||||||
|
@ -247,45 +231,7 @@ func move(intended_velocity: Vector3, delta: float) -> void:
|
||||||
# Keeps the character on slopes and on steps when travelling down
|
# Keeps the character on slopes and on steps when travelling down
|
||||||
if grounded:
|
if grounded:
|
||||||
camera.damp()
|
camera.damp()
|
||||||
|
snap_down()
|
||||||
# === Iterate Movement Vertically (Snap)
|
|
||||||
# We allow snap to slide down slopes
|
|
||||||
# It really helps reduce jitter on steep slopes
|
|
||||||
var before_snap_pos := position
|
|
||||||
var ground_snap_iterations := 0
|
|
||||||
var ground_snap_translation := Vector3.DOWN * snap_to_ground_distance
|
|
||||||
while ground_snap_translation.length() > 0 and ground_snap_iterations < max_iteration_count:
|
|
||||||
ground_snap_translation = move_iteration(
|
|
||||||
MovementType.VERTICAL, snap_collisions, Vector3.DOWN, ground_snap_translation
|
|
||||||
)
|
|
||||||
ground_snap_iterations += 1
|
|
||||||
|
|
||||||
# Decide whether to keep the snap or not
|
|
||||||
if snap_collisions.is_empty():
|
|
||||||
var after_snap_ground_test := move_and_collide(
|
|
||||||
Vector3.DOWN * ground_cast_distance, true, depenetration_margin
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
after_snap_ground_test
|
|
||||||
and (
|
|
||||||
after_snap_ground_test.get_normal(0).angle_to(Vector3.UP)
|
|
||||||
< deg_to_rad(slope_limit)
|
|
||||||
)
|
|
||||||
):
|
|
||||||
# There was no snap collisions, but there is ground underneath
|
|
||||||
# This can be due to an edge case where the snap movement falls through the ground
|
|
||||||
# Why does this check not fall through the ground? I don't know
|
|
||||||
# In any case, manually set the y
|
|
||||||
position.y = after_snap_ground_test.get_position(0).y
|
|
||||||
else:
|
|
||||||
# No snap collisions and no floor, reset
|
|
||||||
position = before_snap_pos
|
|
||||||
elif !(
|
|
||||||
snap_collisions[snap_collisions.size() - 1].get_normal(0).angle_to(Vector3.UP)
|
|
||||||
< deg_to_rad(slope_limit)
|
|
||||||
):
|
|
||||||
# Collided with steep ground, reset
|
|
||||||
position = before_snap_pos
|
|
||||||
else:
|
else:
|
||||||
camera.donmp()
|
camera.donmp()
|
||||||
|
|
||||||
|
@ -302,11 +248,12 @@ func move_iteration(
|
||||||
|
|
||||||
# If Lateral movement, try stepping
|
# If Lateral movement, try stepping
|
||||||
if movement_type == MovementType.LATERAL:
|
if movement_type == MovementType.LATERAL:
|
||||||
var do_step := false
|
|
||||||
var temp_position := position
|
var temp_position := position
|
||||||
|
|
||||||
var walk_test_collision := move_and_collide(translation, true, 0)
|
var walk_test_collision := move_and_collide(translation, true, 0)
|
||||||
|
|
||||||
|
# To correctly step we need to cast up, then forwards, then down
|
||||||
|
# This makes sure that the full step motion is actually clear, and ensures
|
||||||
|
# that the step action is reversible
|
||||||
var current_step_height := step_height
|
var current_step_height := step_height
|
||||||
var step_up_collisions := move_and_collide(Vector3.UP * step_height, false, 0)
|
var step_up_collisions := move_and_collide(Vector3.UP * step_height, false, 0)
|
||||||
if step_up_collisions:
|
if step_up_collisions:
|
||||||
|
@ -319,13 +266,10 @@ func move_iteration(
|
||||||
# This stops stepping up ramps
|
# This stops stepping up ramps
|
||||||
if (
|
if (
|
||||||
down_collision
|
down_collision
|
||||||
and down_collision.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit)
|
and _under_slope_limit(down_collision.get_normal(0))
|
||||||
and walk_test_collision
|
and walk_test_collision
|
||||||
and !walk_test_collision.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit)
|
and !_under_slope_limit(walk_test_collision.get_normal(0))
|
||||||
):
|
): # Keep track of stepepd distance to cancel it out later
|
||||||
do_step = true
|
|
||||||
|
|
||||||
if do_step: # Keep track of stepepd distance to cancel it out later
|
|
||||||
total_stepped_height += position.y - temp_position.y
|
total_stepped_height += position.y - temp_position.y
|
||||||
collisions = raised_forward_collisions
|
collisions = raised_forward_collisions
|
||||||
camera.damp()
|
camera.damp()
|
||||||
|
@ -345,31 +289,10 @@ func move_iteration(
|
||||||
|
|
||||||
# If any ground collisions happen during movement, the character is grounded
|
# If any ground collisions happen during movement, the character is grounded
|
||||||
# Imporant to keep this up-to-date rather than just rely on the initial grounded state
|
# Imporant to keep this up-to-date rather than just rely on the initial grounded state
|
||||||
if collisions.get_normal(0).angle_to(Vector3.UP) < deg_to_rad(slope_limit):
|
var normal := collisions.get_normal(0)
|
||||||
|
if _under_slope_limit(normal):
|
||||||
grounded = true
|
grounded = true
|
||||||
ground_normal = collisions.get_normal(0)
|
ground_normal = normal
|
||||||
|
|
||||||
# Surface Angle will be used to "block" movement in some directions
|
|
||||||
var surface_angle := collisions.get_normal(0).angle_to(Vector3.UP)
|
|
||||||
|
|
||||||
# For Vertical, blocking angle is between 0 - slopeLimit
|
|
||||||
# For Lateral, blocking angle is slopeLimit - 360 (grounded) or 90 (not grounded)
|
|
||||||
# The latter allows players to slide down ceilings while in the air
|
|
||||||
#
|
|
||||||
# These values shouldn't be calculated every frame; they only need to change
|
|
||||||
# when the user defines the slope limit
|
|
||||||
# But I'm lazy :)
|
|
||||||
var min_block_angle: float
|
|
||||||
var max_block_angle: float
|
|
||||||
if movement_type == MovementType.LATERAL:
|
|
||||||
min_block_angle = deg_to_rad(slope_limit)
|
|
||||||
if grounded:
|
|
||||||
max_block_angle = 2 * PI
|
|
||||||
else:
|
|
||||||
max_block_angle = PI / 2
|
|
||||||
if movement_type == MovementType.VERTICAL:
|
|
||||||
min_block_angle = 0
|
|
||||||
max_block_angle = deg_to_rad(slope_limit)
|
|
||||||
|
|
||||||
# This algorithm for determining where to move on a collisions uses "projection plane"
|
# This algorithm for determining where to move on a collisions uses "projection plane"
|
||||||
# Whatever surface the character hits, we generate a blocking "plane" that we will slide along
|
# Whatever surface the character hits, we generate a blocking "plane" that we will slide along
|
||||||
|
@ -378,55 +301,56 @@ func move_iteration(
|
||||||
# transform into a plane at the end
|
# transform into a plane at the end
|
||||||
#
|
#
|
||||||
# By default, projection normal is just the normal of the surface
|
# By default, projection normal is just the normal of the surface
|
||||||
# This may be unecessary after we account for all edge cases
|
# This may be unnecessary after we account for all edge cases
|
||||||
# I'm leaving it here to help understand the algorithm
|
#
|
||||||
var projection_normal := collisions.get_normal(0)
|
# We also want to "block" movement in some directions based on a surface angle
|
||||||
|
# For Vertical, blocking angle is between 0 - slopeLimit
|
||||||
var cylinder := collision_shape.shape as CylinderShape3D
|
# For Lateral, blocking angle is slopeLimit - 360 (grounded) or 90 (not grounded)
|
||||||
var collision_point := collisions.get_position(0)
|
# The latter allows players to slide down ceilings while in the air
|
||||||
|
# TODO These can be precalculated on slope limit change
|
||||||
|
var projection_normal := normal
|
||||||
|
var surface_angle := normal.angle_to(Vector3.UP)
|
||||||
|
match movement_type:
|
||||||
|
MovementType.LATERAL:
|
||||||
|
var min_block_angle := deg_to_rad(slope_limit)
|
||||||
|
var max_block_angle := 2 * PI if grounded else PI / 2
|
||||||
|
|
||||||
# If collision happens on the "side" of the cylinder, treat it as a vertical
|
# If collision happens on the "side" of the cylinder, treat it as a vertical
|
||||||
# wall in all cases (we use the tangent of the cylinder)
|
# wall in all cases (we use the tangent of the cylinder)
|
||||||
if (
|
var cylinder := collision_shape.shape as CylinderShape3D
|
||||||
movement_type == MovementType.LATERAL
|
var collision_point := collisions.get_position(0)
|
||||||
and (
|
var side_y := (collision_shape.global_position.y - cylinder.height / 2) + bottom_height
|
||||||
collision_point.y
|
if collision_point.y > side_y:
|
||||||
> (collision_shape.global_position.y - cylinder.height / 2) + bottom_height
|
|
||||||
)
|
|
||||||
):
|
|
||||||
projection_normal = collision_shape.global_position - collision_point
|
projection_normal = collision_shape.global_position - collision_point
|
||||||
projection_normal.y = 0
|
projection_normal.y = 0
|
||||||
projection_normal = projection_normal.normalized()
|
projection_normal = projection_normal.normalized()
|
||||||
|
|
||||||
# Otherwise, determine if the surface is a blocking surface
|
|
||||||
elif surface_angle >= min_block_angle and surface_angle <= max_block_angle:
|
elif surface_angle >= min_block_angle and surface_angle <= max_block_angle:
|
||||||
if movement_type == MovementType.LATERAL:
|
|
||||||
# "Wall off" the slope
|
# "Wall off" the slope
|
||||||
projection_normal = horz(collisions.get_normal(0)).normalized()
|
projection_normal = _horz(normal).normalized()
|
||||||
|
|
||||||
# Or, "Wall off" the slope by figuring out the seam with the ground
|
# Or, "Wall off" the slope by figuring out the seam with the ground
|
||||||
if grounded and surface_angle < PI / 2:
|
if grounded and surface_angle < PI / 2:
|
||||||
if !already_touched_slope_close_match(collisions.get_normal(0)):
|
if !already_touched_slope_close_match(normal):
|
||||||
steep_slope_normals.append(collisions.get_normal(0))
|
steep_slope_normals.append(normal)
|
||||||
|
|
||||||
var seam := collisions.get_normal(0).cross(ground_normal)
|
var seam := normal.cross(ground_normal)
|
||||||
var temp_projection_plane := Plane(Vector3.ZERO, seam, seam + Vector3.UP)
|
var temp_projection_plane := Plane(Vector3.ZERO, seam, seam + Vector3.UP)
|
||||||
projection_normal = temp_projection_plane.normal
|
projection_normal = temp_projection_plane.normal
|
||||||
|
|
||||||
if movement_type == MovementType.VERTICAL:
|
|
||||||
# If vertical is blocked, you're on solid ground - just stop moving
|
|
||||||
return Vector3.ZERO
|
|
||||||
|
|
||||||
# Otherwise force the direction to align with input direction
|
# Otherwise force the direction to align with input direction
|
||||||
# (projecting translation over the normal of a slope does not align with input direction)
|
# (projecting translation over the normal of a slope does not align with input direction)
|
||||||
elif movement_type == MovementType.LATERAL and surface_angle < (PI / 2):
|
elif surface_angle < (PI / 2):
|
||||||
projection_normal = relative_slope_normal(collisions.get_normal(0), translation)
|
projection_normal = relative_slope_normal(normal, translation)
|
||||||
|
MovementType.VERTICAL:
|
||||||
|
# If vertical is blocked, you're on solid ground - just stop moving
|
||||||
|
var min_block_angle := 0
|
||||||
|
var max_block_angle := deg_to_rad(slope_limit)
|
||||||
|
if surface_angle >= min_block_angle and surface_angle <= max_block_angle:
|
||||||
|
return Vector3.ZERO
|
||||||
|
|
||||||
# Don't let one move call ping pong around
|
# Don't let one move call ping pong around
|
||||||
var projection_plane := Plane(projection_normal)
|
var projection_plane := Plane(projection_normal)
|
||||||
var continued_translation := projection_plane.project(collisions.get_remainder())
|
var continued_translation := projection_plane.project(collisions.get_remainder())
|
||||||
var initial_influenced_translation := projection_plane.project(initial_direction)
|
var initial_influenced_translation := projection_plane.project(initial_direction)
|
||||||
|
|
||||||
var next_translation: Vector3
|
var next_translation: Vector3
|
||||||
if initial_influenced_translation.dot(continued_translation) >= 0:
|
if initial_influenced_translation.dot(continued_translation) >= 0:
|
||||||
next_translation = continued_translation
|
next_translation = continued_translation
|
||||||
|
@ -437,37 +361,33 @@ func move_iteration(
|
||||||
|
|
||||||
# See same_surface_adjust_distance
|
# See same_surface_adjust_distance
|
||||||
if next_translation.normalized() == translation.normalized():
|
if next_translation.normalized() == translation.normalized():
|
||||||
next_translation += collisions.get_normal(0) * same_surface_adjust_distance
|
next_translation += normal * same_surface_adjust_distance
|
||||||
|
|
||||||
return next_translation
|
return next_translation
|
||||||
|
|
||||||
|
|
||||||
|
# TODO Should this use same_surface_adjust_distance
|
||||||
func already_touched_slope_close_match(normal: Vector3) -> bool:
|
func already_touched_slope_close_match(normal: Vector3) -> bool:
|
||||||
for steep_slope_normal in steep_slope_normals:
|
return steep_slope_normals.any(
|
||||||
if steep_slope_normal.distance_squared_to(normal) < 0.001:
|
func(n: Vector3) -> bool: return n.distance_squared_to(normal) < 0.001
|
||||||
return true
|
)
|
||||||
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
# I wrote this a while ago in Unity
|
# I wrote this a while ago in Unity
|
||||||
# I ported it here but I only have a vague grasp of how it works
|
# I ported it here but I only have a vague grasp of how it works
|
||||||
func relative_slope_normal(slope_normal: Vector3, lateral_desired_direction: Vector3) -> Vector3:
|
func relative_slope_normal(slope_normal: Vector3, lateral_desired_direction: Vector3) -> Vector3:
|
||||||
var slope_normal_horz := horz(slope_normal)
|
var surface_angle := slope_normal.angle_to(Vector3.UP)
|
||||||
var angle_to_straight := slope_normal_horz.angle_to(-lateral_desired_direction)
|
var inverse_surface_angle := PI / 2 - surface_angle
|
||||||
var angle_to_up := slope_normal.angle_to(Vector3.UP)
|
if inverse_surface_angle <= 0:
|
||||||
var complementary_angle_to_up := PI / 2 - angle_to_up
|
|
||||||
|
|
||||||
if angle_to_up >= (PI / 2):
|
|
||||||
push_error("Trying to calculate relative slope normal for a ceiling")
|
push_error("Trying to calculate relative slope normal for a ceiling")
|
||||||
|
|
||||||
# Geometry!
|
# Geometry!
|
||||||
|
|
||||||
# This is the component of the desired travel that points straight into the slope
|
# This is the component of the desired travel that points straight into the slope
|
||||||
|
var angle_to_straight := _horz(slope_normal).angle_to(-lateral_desired_direction)
|
||||||
var straight_length := cos(angle_to_straight) * lateral_desired_direction.length()
|
var straight_length := cos(angle_to_straight) * lateral_desired_direction.length()
|
||||||
|
|
||||||
# Which helps us calculate the height on the slope at the end of the desired travel
|
# Which helps us calculate the height on the slope at the end of the desired travel
|
||||||
var height := straight_length / tan(complementary_angle_to_up)
|
var height := straight_length / tan(inverse_surface_angle)
|
||||||
|
|
||||||
# Which gives us the actual desired movement
|
# Which gives us the actual desired movement
|
||||||
var vector_up_slope := Vector3(lateral_desired_direction.x, height, lateral_desired_direction.z)
|
var vector_up_slope := Vector3(lateral_desired_direction.x, height, lateral_desired_direction.z)
|
||||||
|
@ -476,13 +396,48 @@ func relative_slope_normal(slope_normal: Vector3, lateral_desired_direction: Vec
|
||||||
# the plane that will give this result
|
# the plane that will give this result
|
||||||
var rotation_axis := vector_up_slope.cross(Vector3.UP).normalized()
|
var rotation_axis := vector_up_slope.cross(Vector3.UP).normalized()
|
||||||
var emulated_normal := vector_up_slope.rotated(rotation_axis, PI / 2)
|
var emulated_normal := vector_up_slope.rotated(rotation_axis, PI / 2)
|
||||||
|
|
||||||
return emulated_normal.normalized()
|
return emulated_normal.normalized()
|
||||||
|
|
||||||
|
|
||||||
func horz(value: Vector3) -> Vector3:
|
# TODO this doesn't always work. Why?
|
||||||
|
func snap_down() -> void:
|
||||||
|
# We allow snap to slide down slopes
|
||||||
|
# It really helps reduce jitter on steep slopes
|
||||||
|
var before_snap_pos := position
|
||||||
|
var ground_snap_translation := Vector3.DOWN * snap_to_ground_distance
|
||||||
|
for i in max_iteration_count:
|
||||||
|
if ground_snap_translation.length() <= 0:
|
||||||
|
break
|
||||||
|
ground_snap_translation = move_iteration(
|
||||||
|
MovementType.VERTICAL, snap_collisions, Vector3.DOWN, ground_snap_translation
|
||||||
|
)
|
||||||
|
|
||||||
|
# Decide whether to keep the snap or not
|
||||||
|
if snap_collisions.is_empty():
|
||||||
|
var ground_collision := move_and_collide(
|
||||||
|
Vector3.DOWN * ground_cast_distance, true, depenetration_margin
|
||||||
|
)
|
||||||
|
if ground_collision && _under_slope_limit(ground_collision.get_normal(0)):
|
||||||
|
# There was no snap collisions, but there is ground underneath
|
||||||
|
# This can be due to an edge case where the snap movement falls through the ground
|
||||||
|
# Why does this check not fall through the ground? I don't know
|
||||||
|
# In any case, manually set the y
|
||||||
|
position.y = ground_collision.get_position(0).y
|
||||||
|
else:
|
||||||
|
# No snap collisions and no floor, reset
|
||||||
|
position = before_snap_pos
|
||||||
|
elif !_under_slope_limit(snap_collisions[snap_collisions.size() - 1].get_normal(0)):
|
||||||
|
# Collided with steep ground, reset
|
||||||
|
position = before_snap_pos
|
||||||
|
|
||||||
|
|
||||||
|
func _under_slope_limit(normal: Vector3) -> bool:
|
||||||
|
return normal.angle_to(Vector3.UP) < deg_to_rad(slope_limit)
|
||||||
|
|
||||||
|
|
||||||
|
func _horz(value: Vector3) -> Vector3:
|
||||||
return Vector3(value.x, 0, value.z)
|
return Vector3(value.x, 0, value.z)
|
||||||
|
|
||||||
|
|
||||||
func vert(value: Vector3) -> Vector3:
|
func _vert(value: Vector3) -> Vector3:
|
||||||
return Vector3(0, value.y, 0)
|
return Vector3(0, value.y, 0)
|
||||||
|
|
Loading…
Reference in New Issue