WIP Launch logic
This commit is contained in:
@ -18,6 +18,7 @@ var nearby_grips: Array[GripArea3D] = []
|
||||
@export var gripping_linear_damping: float = 5.0 # How quickly velocity stops
|
||||
@export var gripping_angular_damping: float = 5.0 # How quickly spin stops
|
||||
@export var gripping_orient_speed: float = 2.0 # How quickly pawn rotates to face grip
|
||||
# var _movement_input_was_neutral: bool = true # Tracks if input was released, for coasting.
|
||||
|
||||
# --- Climbing parameters ---
|
||||
@export var climb_speed: float = 2.0
|
||||
@ -76,11 +77,13 @@ func process_movement(delta: float, move_input: Vector2, vertical_input: float,
|
||||
MovementState.IDLE:
|
||||
# State is IDLE (free-floating).
|
||||
# Check for EVA suit usage.
|
||||
var is_moving = (move_input != Vector2.ZERO or vertical_input != 0.0 or roll_input != 0.0)
|
||||
if is_moving and is_instance_valid(pawn.eva_suit_component):
|
||||
var has_movement_input = (move_input != Vector2.ZERO or vertical_input != 0.0 or roll_input != 0.0)
|
||||
if has_movement_input and is_instance_valid(pawn.eva_suit_component):
|
||||
# Use EVA suit
|
||||
pawn.eva_suit_component.apply_thrusters(pawn, delta, move_input, vertical_input, roll_input)
|
||||
|
||||
# _movement_input_was_neutral = not has_movement_input
|
||||
|
||||
# Check for body orientation (if applicable)
|
||||
if release_input.held and is_instance_valid(pawn.eva_suit_component):
|
||||
pawn.eva_suit_component._orient_pawn(delta) # Use suit's orient
|
||||
@ -134,13 +137,10 @@ func _update_state(
|
||||
MovementState.REACHING:
|
||||
# TODO: If reach animation completes/hand near target -> GRIPPING
|
||||
# If interact released during reach -> CANCEL -> IDLE
|
||||
if is_instance_valid(current_grip):
|
||||
current_state = MovementState.GRIPPING
|
||||
return
|
||||
|
||||
if _seeking_climb_input != Vector2.ZERO:
|
||||
# We are in a "seek-climb-reach" chain. Cancel if move input stops.
|
||||
if move_input == Vector2.ZERO:
|
||||
# This will transition state to IDLE
|
||||
_cancel_reach()
|
||||
elif not (reach_input.pressed or reach_input.held):
|
||||
# This was a normal reach initiated by click. Cancel if click is released.
|
||||
@ -150,10 +150,14 @@ func _update_state(
|
||||
# print("ZeroGMovementComponent: Gripping State Active")
|
||||
if release_input.pressed or release_input.held or not is_instance_valid(current_grip):
|
||||
_release_current_grip(move_input)
|
||||
# Pawn's main state machine will handle transition out
|
||||
if move_input != Vector2.ZERO:
|
||||
return
|
||||
|
||||
# FIX: Check for launch charge *before* checking for climb, as it's a more specific action.
|
||||
if (reach_input.pressed or reach_input.held) and move_input != Vector2.ZERO:
|
||||
_start_charge(move_input)
|
||||
return
|
||||
elif move_input != Vector2.ZERO:
|
||||
_start_climb(move_input)
|
||||
pass
|
||||
MovementState.CLIMBING:
|
||||
if release_input.pressed or release_input.held or not is_instance_valid(current_grip):
|
||||
_stop_climb(true) # Release grip and stop
|
||||
@ -162,16 +166,12 @@ func _update_state(
|
||||
_stop_climb(false) # Stop moving, return to GRIPPING
|
||||
return
|
||||
# Continue climbing logic (finding next grip) happens in _process_climbing
|
||||
|
||||
# elif move_input != Vector2.ZERO:
|
||||
# _start_charge(move_input) # Start charging launch
|
||||
# MovementState.CHARGING_LAUNCH:
|
||||
# if reaching_released:
|
||||
# _execute_launch()
|
||||
# # Pawn's main state machine handles transition out
|
||||
# elif move_input == Vector2.ZERO: # Cancel charge while holding interact
|
||||
# state = MovementState.GRIPPING
|
||||
# print("ZeroGMovementComponent: Cancelled Launch Charge")
|
||||
MovementState.CHARGING_LAUNCH:
|
||||
if not (reach_input.pressed or reach_input.held):
|
||||
_execute_launch()
|
||||
elif move_input == Vector2.ZERO: # Cancel charge while holding interact
|
||||
current_state = MovementState.GRIPPING
|
||||
print("ZeroGMovementComponent: Cancelled Launch Charge")
|
||||
|
||||
|
||||
# === MOVEMENT PROCESSING ===
|
||||
@ -179,11 +179,10 @@ func _process_reaching(_delta: float):
|
||||
# TODO: Drive IK target towards current_grip.get_grip_transform().origin
|
||||
# TODO: Monitor distance / animation state
|
||||
# For now, we just instantly grip.
|
||||
print(_seeking_climb_input)
|
||||
if _seeking_climb_input != Vector2.ZERO:
|
||||
_perform_grip_handover() # Use handover to complete the seek-reach
|
||||
_attempt_grip(next_grip_target) # Complete the seek-reach
|
||||
else:
|
||||
_try_initiate_reach() # Use initiate_reach for a normal click-reach
|
||||
_attempt_grip(_find_best_grip())
|
||||
|
||||
func _apply_grip_physics(delta: float, _move_input: Vector2, roll_input: float):
|
||||
if not is_instance_valid(pawn) or not is_instance_valid(current_grip):
|
||||
@ -198,13 +197,7 @@ func _apply_grip_physics(delta: float, _move_input: Vector2, roll_input: float):
|
||||
var hold_distance = _get_hold_distance()
|
||||
var target_position = grip_base_transform.origin + target_direction * hold_distance
|
||||
|
||||
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 target_basis = Basis.looking_at(-target_direction, chosen_orientation_up_vector).orthonormalized()
|
||||
var target_basis = _choose_grip_orientation(grip_base_transform.basis)
|
||||
|
||||
# --- 2. Apply Linear Force (PD Controller) ---
|
||||
var error_pos = target_position - pawn.global_position
|
||||
@ -222,19 +215,7 @@ func _apply_grip_physics(delta: float, _move_input: Vector2, roll_input: float):
|
||||
pawn.add_torque(roll_torque_global, delta)
|
||||
else:
|
||||
# Auto-Orient (PD Controller)
|
||||
var current_quat = pawn.global_transform.basis.get_rotation_quaternion()
|
||||
var target_quat = target_basis.get_rotation_quaternion()
|
||||
var error_quat = target_quat * current_quat.inverse()
|
||||
var error_angle = error_quat.get_angle()
|
||||
var error_axis = error_quat.get_axis()
|
||||
|
||||
# Proportional torque (spring)
|
||||
var torque_proportional = error_axis.normalized() * error_angle * gripping_orient_speed # 'speed' acts as Kp
|
||||
# Derivative torque (damping)
|
||||
var torque_derivative = -pawn.angular_velocity * gripping_angular_damping # 'damping' acts as Kd
|
||||
|
||||
var total_torque_global = (torque_proportional + torque_derivative)
|
||||
pawn.add_torque(total_torque_global, delta)
|
||||
_apply_orientation_torque(target_basis, delta)
|
||||
|
||||
func _apply_climb_physics(delta: float, move_input: Vector2):
|
||||
if not is_instance_valid(pawn) or not is_instance_valid(current_grip):
|
||||
@ -248,7 +229,7 @@ func _apply_climb_physics(delta: float, move_input: Vector2):
|
||||
next_grip_target = _find_best_grip(climb_direction, INF, climb_angle_threshold_deg)
|
||||
|
||||
# 3. Check for Handover: This should be more eager to mark a new grip as current than below check is to release when climbing past
|
||||
var performed_handover = _perform_grip_handover()
|
||||
var performed_handover = _attempt_grip(next_grip_target)
|
||||
|
||||
# 4. Check for Release Past Grip (if no handover)
|
||||
if not performed_handover:
|
||||
@ -265,25 +246,8 @@ func _apply_climb_physics(delta: float, move_input: Vector2):
|
||||
|
||||
# 6. Apply Angular Force (Auto-Orient to current grip)
|
||||
var grip_base_transform = current_grip.global_transform
|
||||
var target_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 target_basis = Basis.looking_at(-target_direction, chosen_orientation_up_vector).orthonormalized()
|
||||
|
||||
var current_quat = pawn.global_transform.basis.get_rotation_quaternion()
|
||||
var target_quat = target_basis.get_rotation_quaternion()
|
||||
var error_quat = target_quat * current_quat.inverse()
|
||||
var error_angle = error_quat.get_angle()
|
||||
var error_axis = error_quat.get_axis()
|
||||
|
||||
var torque_proportional = error_axis.normalized() * error_angle * gripping_orient_speed
|
||||
var torque_derivative = -pawn.angular_velocity * gripping_angular_damping
|
||||
var total_torque_global = (torque_proportional + torque_derivative)
|
||||
pawn.add_torque(total_torque_global, delta)
|
||||
var target_basis = _choose_grip_orientation(grip_base_transform.basis)
|
||||
_apply_orientation_torque(target_basis, delta)
|
||||
|
||||
|
||||
func _process_seeking_climb(_delta: float, move_input: Vector2):
|
||||
@ -302,20 +266,34 @@ func _process_seeking_climb(_delta: float, move_input: Vector2):
|
||||
|
||||
|
||||
# --- Grip Helpers
|
||||
# Attempts to find and grab the best available grip within range
|
||||
func _try_initiate_reach():
|
||||
var closest_grip: GripArea3D = _find_best_grip()
|
||||
|
||||
if is_instance_valid(closest_grip) and closest_grip.grab(pawn):
|
||||
current_grip = closest_grip
|
||||
current_state = MovementState.GRIPPING # Set internal state
|
||||
print("ZeroGMovementComponent: Initiated grab on ", current_grip.get_parent().name)
|
||||
_seeking_climb_input = Vector2.ZERO # Reset seek input on successful grip
|
||||
elif is_instance_valid(closest_grip):
|
||||
print("ZeroGMovementComponent: Grab failed (grip occupied?)")
|
||||
|
||||
## The single, authoritative function for grabbing a grip.
|
||||
func _attempt_grip(target_grip: GripArea3D) -> bool:
|
||||
if not is_instance_valid(target_grip):
|
||||
return false
|
||||
|
||||
if target_grip.grab(pawn):
|
||||
# Successfully grabbed the new grip.
|
||||
var old_grip = current_grip
|
||||
if is_instance_valid(old_grip) and old_grip != target_grip:
|
||||
old_grip.release(pawn)
|
||||
|
||||
current_grip = target_grip
|
||||
next_grip_target = null
|
||||
_seeking_climb_input = Vector2.ZERO
|
||||
|
||||
# If we weren't already climbing, transition to GRIPPING state.
|
||||
if current_state != MovementState.CLIMBING:
|
||||
current_state = MovementState.GRIPPING
|
||||
|
||||
print("Successfully gripped: ", current_grip.get_parent().name)
|
||||
return true
|
||||
else:
|
||||
print("ZeroGMovementComponent: No available grips in range to initiate reach.")
|
||||
# TODO: Initiate generic surface grab?
|
||||
# Failed to grab the new grip.
|
||||
print("Failed to grip: ", target_grip.get_parent().name, " (likely occupied).")
|
||||
if current_state == MovementState.CLIMBING:
|
||||
_stop_climb(false) # Stop climbing, return to gripping previous one
|
||||
return false
|
||||
|
||||
# --- Grip Orientation Helper ---
|
||||
func _choose_grip_orientation(grip_basis: Basis) -> Basis:
|
||||
@ -376,8 +354,8 @@ func _find_best_grip(direction := Vector3.ZERO, max_distance_sq := INF, angle_th
|
||||
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)
|
||||
# if is_instance_valid(best_grip):
|
||||
# print("Best grip found: ", best_grip.get_parent().name, " at distance squared: ", min_dist_sq)
|
||||
|
||||
return best_grip
|
||||
|
||||
@ -431,46 +409,31 @@ func _stop_climb(release_grip: bool):
|
||||
else:
|
||||
current_state = MovementState.GRIPPING # Go back to stationary gripping
|
||||
|
||||
func _apply_orientation_torque(target_basis: Basis, delta: float):
|
||||
var current_quat = pawn.global_transform.basis.get_rotation_quaternion()
|
||||
var target_quat = target_basis.get_rotation_quaternion()
|
||||
var error_quat = target_quat * current_quat.inverse()
|
||||
var error_angle = error_quat.get_angle()
|
||||
var error_axis = error_quat.get_axis()
|
||||
|
||||
func _perform_grip_handover() -> bool:
|
||||
if not is_instance_valid(next_grip_target): return false
|
||||
var torque_proportional = error_axis.normalized() * error_angle * gripping_orient_speed
|
||||
var torque_derivative = -pawn.angular_velocity * gripping_angular_damping
|
||||
var total_torque_global = (torque_proportional + torque_derivative)
|
||||
pawn.add_torque(total_torque_global, delta)
|
||||
|
||||
print("Attempting handover to: ", next_grip_target.get_parent().name)
|
||||
if next_grip_target.grab(pawn):
|
||||
# Successfully grabbed the next one
|
||||
if is_instance_valid(current_grip):
|
||||
current_grip.release(pawn) # Release the old one
|
||||
current_grip = next_grip_target # Update current grip reference
|
||||
next_grip_target = null # Clear the target
|
||||
_seeking_climb_input = Vector2.ZERO # We are now climbing/gripping, so reset the seek input.
|
||||
print("Handover successful. New grip: ", current_grip.get_parent().name)
|
||||
# Stay in CLIMBING state, velocity continues
|
||||
return true # Indicate success
|
||||
else:
|
||||
# Failed to grab next grip (e.g., became occupied)
|
||||
print("Handover failed - couldn't grab next grip.")
|
||||
_stop_climb(false) # Stop climbing, return to gripping previous one
|
||||
return false # Indicate failure
|
||||
|
||||
|
||||
# --- Launch helpers ---
|
||||
func _start_charge(move_input: Vector2):
|
||||
if not is_instance_valid(current_grip): return
|
||||
if not is_instance_valid(current_grip): return # Safety check
|
||||
current_state = MovementState.CHARGING_LAUNCH
|
||||
launch_charge = 0.0
|
||||
|
||||
# Calculate launch direction based on input and push-off normal
|
||||
var push_dir_local = (Vector3.FORWARD * -move_input.y + Vector3.RIGHT * move_input.x).normalized()
|
||||
var push_normal = current_grip.get_push_off_normal()
|
||||
# The direction is based on the pawn's current orientation, not the camera or grip.
|
||||
# This makes it feel like you're pushing off in a direction relative to your body.
|
||||
var pawn_up = pawn.global_basis.y
|
||||
var pawn_right = pawn.global_basis.x
|
||||
launch_direction = (pawn_up * move_input.y + pawn_right * move_input.x).normalized()
|
||||
|
||||
# Basis oriented away from surface, using pawn's current up as reference
|
||||
var _surface_basis = Basis.looking_at(push_normal, pawn.global_transform.basis.y).orthonormalized()
|
||||
|
||||
var input_influence = 0.5 # Blend between pushing straight off and sliding along input dir
|
||||
var input_dir_world = pawn.camera_pivot.global_transform.basis * push_dir_local # Convert input dir relative to camera/head
|
||||
var push_dir_along_surface = input_dir_world.slide(push_normal).normalized()
|
||||
|
||||
launch_direction = (push_normal * (1.0 - input_influence) + push_dir_along_surface * input_influence).normalized()
|
||||
print("ZeroGMovementComponent: Charging Launch")
|
||||
|
||||
|
||||
@ -478,6 +441,7 @@ func _handle_launch_charge(delta: float):
|
||||
launch_charge = min(launch_charge + launch_charge_rate * delta, max_launch_speed)
|
||||
pawn.velocity = Vector3.ZERO
|
||||
pawn.angular_velocity = Vector3.ZERO
|
||||
# _movement_input_was_neutral = false # Ensure we don't immediately thrust after launch
|
||||
|
||||
|
||||
func _execute_launch():
|
||||
@ -485,23 +449,25 @@ func _execute_launch():
|
||||
_release_current_grip() # Release AFTER calculating direction
|
||||
pawn.velocity = launch_direction * launch_charge # Apply launch velocity to pawn
|
||||
launch_charge = 0.0
|
||||
current_state = MovementState.IDLE
|
||||
print("ZeroGMovementComponent: Launched with speed ", pawn.velocity.length())
|
||||
|
||||
# Instead of going to IDLE, go to SEEKING_CLIMB to find the next grip.
|
||||
# The move_input that started the launch is what we'll use for the seek direction.
|
||||
_seeking_climb_input = (pawn.global_basis.y.dot(launch_direction) * Vector2.UP) + (pawn.global_basis.x.dot(launch_direction) * Vector2.RIGHT)
|
||||
current_state = MovementState.SEEKING_CLIMB
|
||||
print("ZeroGMovementComponent: Launched with speed ", pawn.velocity.length(), " and now SEEKING_CLIMB")
|
||||
|
||||
|
||||
# --- Signal Handlers ---
|
||||
func on_grip_area_entered(area: Area3D):
|
||||
print("Area detected")
|
||||
if area is GripArea3D: # Check if the entered area is actually a GripArea3D node
|
||||
var grip = area as GripArea3D
|
||||
if not grip in nearby_grips:
|
||||
nearby_grips.append(grip)
|
||||
print("Detected nearby grip: ", grip.get_parent().name if grip.get_parent() else "UNKNOWN") # Print parent name for context
|
||||
# print("Detected nearby grip: ", grip.get_parent().name if grip.get_parent() else "UNKNOWN") # Print parent name for context
|
||||
|
||||
func on_grip_area_exited(area: Area3D):
|
||||
if area is GripArea3D:
|
||||
var grip = area as GripArea3D
|
||||
if grip in nearby_grips:
|
||||
nearby_grips.erase(grip)
|
||||
print("Grip out of range: ", grip.get_parent().name if grip.get_parent() else "UNKNOWN")
|
||||
|
||||
# print("Grip out of range: ", grip.get_parent().name if grip.get_parent() else "UNKNOWN")
|
||||
|
||||
Reference in New Issue
Block a user