Climb in more directions
This commit is contained in:
@ -23,7 +23,7 @@ var nearby_grips: Array[GripArea3D] = []
|
||||
@export var climb_speed: float = 2.0
|
||||
@export var grip_handover_distance: float = 1 # How close to next grip to initiate handover
|
||||
@export var climb_acceleration: float = 10.0 # How quickly pawn reaches climb_speed
|
||||
@export var climb_angle_threshold_deg: float = 60.0 # How wide the forward cone is
|
||||
@export var climb_angle_threshold_deg: float = 80.0 # How wide the forward cone is
|
||||
var climb_direction_world: Vector3 = Vector3.ZERO # Direction currently climbing
|
||||
var next_grip_target: GripArea3D = null # The grip we are trying to transition to
|
||||
|
||||
@ -76,11 +76,8 @@ func process_movement(delta: float, move_input: Vector2, reach_input: PlayerCont
|
||||
_process_reaching(delta)
|
||||
MovementState.GRIPPING:
|
||||
_process_gripping(delta, move_input)
|
||||
|
||||
if release_input.pressed or release_input.held:
|
||||
_release_current_grip()
|
||||
MovementState.CLIMBING:
|
||||
_process_climbing(delta)
|
||||
_process_climbing(delta, move_input)
|
||||
MovementState.CHARGING_LAUNCH:
|
||||
_handle_launch_charge(delta)
|
||||
|
||||
@ -112,7 +109,7 @@ func _update_state(
|
||||
_delta: float,
|
||||
move_input: Vector2,
|
||||
reach_input: PlayerController3D.KeyInput,
|
||||
_release_input: PlayerController3D.KeyInput,
|
||||
release_input: PlayerController3D.KeyInput,
|
||||
):
|
||||
match current_state:
|
||||
MovementState.IDLE:
|
||||
@ -127,14 +124,14 @@ func _update_state(
|
||||
_cancel_reach()
|
||||
MovementState.GRIPPING:
|
||||
# print("ZeroGMovementComponent: Gripping State Active")
|
||||
if not is_instance_valid(current_grip):
|
||||
if release_input.pressed or release_input.held or not is_instance_valid(current_grip):
|
||||
_release_current_grip()
|
||||
# Pawn's main state machine will handle transition out
|
||||
if move_input != Vector2.ZERO:
|
||||
_start_climb(move_input)
|
||||
pass
|
||||
MovementState.CLIMBING:
|
||||
if not is_instance_valid(current_grip):
|
||||
if release_input.pressed or release_input.held or not is_instance_valid(current_grip):
|
||||
_stop_climb(true) # Release grip and stop
|
||||
return
|
||||
if move_input == Vector2.ZERO: # Player stopped giving input
|
||||
@ -183,15 +180,7 @@ func _process_gripping(delta: float, _move_input: Vector2):
|
||||
# Calculate the final target position with the offset
|
||||
var grip_in_direction = grip_base_transform.basis.z.normalized()
|
||||
|
||||
var grip_up_vector = grip_base_transform.basis.y.normalized()
|
||||
var grip_down_vector = -grip_base_transform.basis.y.normalized()
|
||||
var pawn_up_vector = pawn.global_transform.basis.y
|
||||
|
||||
var dot_up = pawn_up_vector.dot(grip_up_vector)
|
||||
var dot_down = pawn_up_vector.dot(grip_down_vector)
|
||||
|
||||
var chosen_orientation_up_vector = grip_up_vector if dot_up >= dot_down else grip_down_vector
|
||||
var chosen_basis = Basis.looking_at(-grip_in_direction, chosen_orientation_up_vector).orthonormalized()
|
||||
var chosen_basis = _choose_grip_orientation(current_grip.global_basis)
|
||||
|
||||
var target_position = grip_base_transform.origin + grip_in_direction * _get_hold_distance()
|
||||
# --- End Offset Calculation ---
|
||||
@ -206,37 +195,35 @@ func _process_gripping(delta: float, _move_input: Vector2):
|
||||
# while the physics/orientation logic stops the main body's momentum.
|
||||
|
||||
|
||||
func _process_climbing(delta: float):
|
||||
func _process_climbing(delta: float, move_input: Vector2):
|
||||
if not is_instance_valid(pawn) or not is_instance_valid(current_grip):
|
||||
_stop_climb(true) # Safety check
|
||||
return
|
||||
|
||||
var climb_direction = move_input.y * pawn.global_basis.y + move_input.x * pawn.global_basis.x
|
||||
climb_direction = climb_direction.normalized()
|
||||
|
||||
# 1. Accelerate towards climb speed in the climb direction
|
||||
var target_velocity = climb_direction_world * climb_speed
|
||||
var target_velocity = climb_direction * climb_speed
|
||||
pawn.velocity = pawn.velocity.lerp(target_velocity, delta * climb_acceleration)
|
||||
pawn.angular_velocity = pawn.angular_velocity.lerp(Vector3.ZERO, delta * gripping_angular_damping) # Dampen spin while climbing
|
||||
|
||||
# 2. Find the next potential grip in the climb direction
|
||||
next_grip_target = _find_climb_target_grip(climb_direction_world) # Use world direction
|
||||
next_grip_target = _find_best_grip(climb_direction) # Use world direction
|
||||
|
||||
# 3. Check for Handover
|
||||
if is_instance_valid(next_grip_target):
|
||||
var next_grip_pos = next_grip_target.global_position # Use grip's actual position for distance check
|
||||
var dist_sq_to_next = pawn.global_position.distance_squared_to(next_grip_pos)
|
||||
|
||||
if dist_sq_to_next < grip_handover_distance:
|
||||
_perform_grip_handover()
|
||||
_perform_grip_handover()
|
||||
|
||||
# 4. (Optional) Maintain Orientation relative to current grip or next target?
|
||||
# Keep simple for now: orient towards current grip transform
|
||||
var target_transform = current_grip.global_transform
|
||||
# Only lerp origin slightly to allow movement, prioritize basis slerp
|
||||
pawn.global_transform.origin = pawn.global_transform.origin.lerp(target_transform.origin + target_transform.basis.z * _get_hold_distance(), delta * reach_speed * 0.5) # Slower position lerp
|
||||
pawn.global_transform.basis = pawn.global_transform.basis.slerp(target_transform.basis, delta * gripping_orient_speed)
|
||||
# pawn.global_transform.origin = pawn.global_transform.origin.lerp(target_transform.origin + target_transform.basis.z * _get_hold_distance(), delta * reach_speed * 0.5) # Slower position lerp
|
||||
pawn.global_transform.basis = pawn.global_transform.basis.slerp(_choose_grip_orientation(current_grip.global_basis), delta * gripping_orient_speed)
|
||||
|
||||
# --- Grip Helpers
|
||||
# Attempts to find and grab the best available grip within range
|
||||
func _try_initiate_reach():
|
||||
var closest_grip: GripArea3D = _find_closest_available_grip()
|
||||
var closest_grip: GripArea3D = _find_best_grip()
|
||||
|
||||
if is_instance_valid(closest_grip):
|
||||
current_grip = closest_grip
|
||||
@ -248,41 +235,68 @@ func _try_initiate_reach():
|
||||
print("ZeroGMovementComponent: No available grips in range to initiate reach.")
|
||||
# TODO: Initiate generic surface grab?
|
||||
|
||||
func _find_closest_available_grip() -> GripArea3D:
|
||||
var closest_grip: GripArea3D = null
|
||||
var closest_distance: float = INF
|
||||
for grip: GripArea3D in nearby_grips:
|
||||
if grip.is_occupied():
|
||||
continue
|
||||
var distance = pawn.global_transform.origin.distance_squared_to(grip.global_transform.origin)
|
||||
if distance < closest_distance:
|
||||
closest_distance = distance
|
||||
closest_grip = grip
|
||||
return closest_grip
|
||||
# --- Grip Orientation Helper ---
|
||||
func _choose_grip_orientation(grip_basis: Basis) -> Basis:
|
||||
var grip_up_vector = grip_basis.y.normalized()
|
||||
var grip_down_vector = -grip_basis.y.normalized()
|
||||
var pawn_up_vector = pawn.global_transform.basis.y
|
||||
|
||||
# --- Modify _find_climb_target_grip to accept world direction ---
|
||||
func _find_climb_target_grip(world_direction: Vector3) -> GripArea3D:
|
||||
var dot_up = pawn_up_vector.dot(grip_up_vector)
|
||||
var dot_down = pawn_up_vector.dot(grip_down_vector)
|
||||
|
||||
var chosen_orientation_up_vector = grip_up_vector if dot_up >= dot_down else grip_down_vector
|
||||
return Basis.looking_at(-grip_basis.z.normalized(), chosen_orientation_up_vector).orthonormalized()
|
||||
|
||||
# --- Grip Selection Logic ---
|
||||
# Finds the best grip based on direction, distance, and angle constraints
|
||||
func _find_best_grip(direction := Vector3.ZERO, max_distance_sq := INF, angle_threshold_deg := 120.0) -> GripArea3D:
|
||||
var best_grip: GripArea3D = null
|
||||
var min_dist_sq = INF
|
||||
var desired_dir = world_direction.normalized()
|
||||
var min_dist_sq = max_distance_sq # Start checking against max allowed distance
|
||||
|
||||
var angle_threshold_rad = deg_to_rad(climb_angle_threshold_deg)
|
||||
var dot_threshold = cos(angle_threshold_rad / 2.0)
|
||||
var use_direction_filter = direction != Vector3.ZERO
|
||||
var max_allowed_angle_rad = 0.0 # Initialize
|
||||
if use_direction_filter:
|
||||
# Calculate the maximum allowed angle deviation from the center direction
|
||||
max_allowed_angle_rad = deg_to_rad(angle_threshold_deg) / 2.0
|
||||
|
||||
# Iterate through all grips detected by the pawn
|
||||
for grip in nearby_grips:
|
||||
# Basic validity checks
|
||||
if not is_instance_valid(grip) or grip == current_grip or not grip.can_grab(pawn):
|
||||
continue
|
||||
|
||||
var grip_pos = grip.global_position
|
||||
# Use direction_to which automatically normalizes
|
||||
var dir_to_grip = pawn.global_position.direction_to(grip_pos)
|
||||
var dist_sq = pawn.global_position.distance_squared_to(grip_pos)
|
||||
|
||||
# Check distance first
|
||||
if dist_sq >= min_dist_sq: # Use >= because we update min_dist_sq later
|
||||
continue
|
||||
|
||||
# If using direction filter, check angle constraint
|
||||
if use_direction_filter:
|
||||
# Ensure the direction vector we compare against is normalized
|
||||
var normalized_direction = direction.normalized()
|
||||
# Calculate the dot product
|
||||
var dot = dir_to_grip.dot(normalized_direction)
|
||||
# Clamp dot product to handle potential floating-point errors outside [-1, 1]
|
||||
dot = clamp(dot, -1.0, 1.0)
|
||||
# Calculate the actual angle between the vectors in radians
|
||||
var angle_rad = acos(dot)
|
||||
|
||||
# Check if the calculated angle exceeds the maximum allowed deviation
|
||||
if angle_rad > max_allowed_angle_rad:
|
||||
# print("Grip ", grip.get_parent().name, " outside cone. Angle: ", rad_to_deg(angle_rad), " > ", rad_to_deg(max_allowed_angle_rad))
|
||||
continue # Skip this grip if it's outside the cone
|
||||
|
||||
# If it passes all filters and is closer than the previous best:
|
||||
min_dist_sq = dist_sq
|
||||
best_grip = grip
|
||||
|
||||
if is_instance_valid(best_grip):
|
||||
print("Best grip found: ", best_grip.get_parent().name, " at distance squared: ", min_dist_sq)
|
||||
|
||||
# Check if the grip is roughly in the desired direction
|
||||
var dot = dir_to_grip.dot(desired_dir)
|
||||
if dot > dot_threshold: # Is it within the forward cone?
|
||||
var dist_sq = pawn.global_position.distance_squared_to(grip_pos)
|
||||
if dist_sq < min_dist_sq:
|
||||
min_dist_sq = dist_sq
|
||||
best_grip = grip
|
||||
return best_grip
|
||||
|
||||
# --- Helper for Hold Distance ---
|
||||
@ -313,8 +327,8 @@ func _start_climb(move_input: Vector2):
|
||||
current_state = MovementState.CLIMBING
|
||||
|
||||
# Calculate initial climb direction based on input relative to camera/grip
|
||||
var pawn_up = pawn.global_transform.basis.y
|
||||
var pawn_right = pawn.global_transform.basis.x
|
||||
var pawn_up = pawn.global_basis.y
|
||||
var pawn_right = pawn.global_basis.x
|
||||
|
||||
# Project input onto plane perpendicular to grip normal? Or just use camera space?
|
||||
# Let's use camera space initially for simplicity
|
||||
|
||||
Reference in New Issue
Block a user