ZeroGMovementComponent WIP
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bm1rbv4tuppbc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d4jka2etva22s" path="res://scenes/tests/3d/eva_controller.gd" id="1_mb22m"]
|
||||
[ext_resource type="Script" uid="uid://d4jka2etva22s" path="res://scenes/tests/3d/eva_movement_component.gd" id="1_mb22m"]
|
||||
|
||||
[node name="EVASuitController" type="Node3D"]
|
||||
script = ExtResource("1_mb22m")
|
||||
|
||||
@ -142,6 +142,11 @@ move_down_3d={
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
left_click={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
|
||||
@ -7,7 +7,15 @@ class_name CharacterPawn3D
|
||||
@export var base_inertia: float = 1.0 # Pawn's inertia without suit
|
||||
|
||||
## State Machine
|
||||
enum State {FLOATING, GRABBING_SURFACE, CHARGING_LAUNCH, ON_LADDER, GRIPPING_LADDER, WALKING, REACHING_MOVE}
|
||||
enum State {
|
||||
FLOATING,
|
||||
GRABBING_GRIP,
|
||||
CHARGING_LAUNCH,
|
||||
ON_LADDER,
|
||||
GRIPPING_LADDER,
|
||||
WALKING,
|
||||
REACHING_MOVE
|
||||
}
|
||||
var current_state: State = State.FLOATING:
|
||||
set(new_state):
|
||||
if new_state == current_state: return
|
||||
@ -21,6 +29,9 @@ var _roll_input: float = 0.0
|
||||
var _vertical_input: float = 0.0
|
||||
var _interact_pressed: bool = false
|
||||
var _interact_released: bool = false
|
||||
var _l_pressed: bool = false
|
||||
var _l_held: bool = false
|
||||
var _l_released: bool = false
|
||||
var _r_pressed: bool = false
|
||||
var _r_held: bool = false
|
||||
var _r_released: bool = false
|
||||
@ -33,9 +44,9 @@ var _pitch_yaw_input: Vector2 = Vector2.ZERO
|
||||
@export_range(0, PI / 2.0 - 0.01) var max_pitch_rad: float = deg_to_rad(60.0)
|
||||
@export var head_turn_lerp_speed: float = 15.0
|
||||
|
||||
## Equipment Slots
|
||||
var eva_suit_controller: EVASuitController = null
|
||||
# var reach_move_controller = null # Placeholder
|
||||
## Movement Components
|
||||
@onready var eva_suit_component: EVAMovementComponent = $EVAMovementComponent
|
||||
@onready var zero_g_movemement_component: ZeroGMovementComponent = $ZeroGMovementComponent
|
||||
|
||||
## Physics State (Managed by Pawn)
|
||||
var angular_velocity: Vector3 = Vector3.ZERO
|
||||
@ -44,39 +55,21 @@ var angular_velocity: Vector3 = Vector3.ZERO
|
||||
## Other State Variables
|
||||
var current_gravity: Vector3 = Vector3.ZERO # TODO: Implement gravity detection
|
||||
var overlapping_ladder_area: Area3D = null
|
||||
@onready var ladder_detector: Area3D = $LadderDetector # Ensure this exists in scene
|
||||
var grab_surface_normal: Vector3 = Vector3.ZERO
|
||||
var launch_direction: Vector3 = Vector3.ZERO
|
||||
var launch_charge: float = 0.0
|
||||
@export var launch_charge_rate: float = 20.0
|
||||
@export var max_launch_speed: float = 15.0
|
||||
@export var orientation_speed: float = 2.0 # Used for orienting body to camera
|
||||
@onready var grab_ray: RayCast3D = $GrabRay # Ensure this exists
|
||||
@export var grab_check_distance: float = 5
|
||||
@onready var grip_detector: Area3D = $GripDetector
|
||||
var nearby_grips: Array[GripArea3D] = [] # List of available grips
|
||||
|
||||
# Constants for State Checks
|
||||
const WALKABLE_GRAVITY_THRESHOLD: float = 1.0
|
||||
|
||||
func _ready():
|
||||
# Find equipped controllers
|
||||
eva_suit_controller = find_child("EVASuitController", false, false) # Non-recursive, ignore owner
|
||||
if eva_suit_controller: print("Found EVA Suit Controller")
|
||||
# TODO: Find ReachMoveController
|
||||
|
||||
# Connect ladder detector
|
||||
if ladder_detector:
|
||||
ladder_detector.area_entered.connect(_on_ladder_area_entered)
|
||||
ladder_detector.area_exited.connect(_on_ladder_area_exited)
|
||||
else:
|
||||
printerr("LadderDetector Area3D node not found on CharacterPawn!")
|
||||
# find movement components
|
||||
if eva_suit_component: print("Found EVA Suit Controller")
|
||||
if zero_g_movemement_component: print("Found Zero-G Movement Controller")
|
||||
|
||||
# Connect grip detector signals
|
||||
if grip_detector:
|
||||
if grip_detector and zero_g_movemement_component:
|
||||
print("GripDetector Area3D node found")
|
||||
grip_detector.area_entered.connect(_on_grip_area_entered)
|
||||
grip_detector.area_exited.connect(_on_grip_area_exited)
|
||||
grip_detector.area_entered.connect(zero_g_movemement_component.on_grip_area_entered)
|
||||
grip_detector.area_exited.connect(zero_g_movemement_component.on_grip_area_exited)
|
||||
else:
|
||||
printerr("GripDetector Area3D node not found on CharacterPawn!")
|
||||
|
||||
@ -85,7 +78,7 @@ func _ready():
|
||||
|
||||
func _physics_process(delta: float):
|
||||
# 1. Apply Mouse Rotation (Universal head look)
|
||||
_apply_mouse_rotation(delta)
|
||||
_apply_mouse_rotation()
|
||||
|
||||
# 2. Determine Potential State & Handle Transitions
|
||||
_update_state_transitions()
|
||||
@ -93,11 +86,14 @@ func _physics_process(delta: float):
|
||||
# 3. Execute State Logic (Delegate to Controllers / Handle Pawn-native states)
|
||||
match current_state:
|
||||
State.FLOATING:
|
||||
if eva_suit_controller:
|
||||
eva_suit_controller.process_movement(delta, _move_input, _vertical_input, _roll_input, _r_held)
|
||||
# Stabilization is handled within eva_suit_controller.process_movement
|
||||
else:
|
||||
pass # Pure freefall, velocity/angular velocity persist
|
||||
if eva_suit_component:
|
||||
eva_suit_component.process_movement(delta, _move_input, _vertical_input, _roll_input, _r_held)
|
||||
# Stabilization is handled within eva_suit_component.process_movement
|
||||
if zero_g_movemement_component: # Fallback to ZeroG controller (for initiating reach)
|
||||
print("Pawn: Reaching: ", _l_held)
|
||||
zero_g_movemement_component.process_movement(delta, _move_input, _l_pressed, _l_held, _l_released)
|
||||
|
||||
pass # Pure freefall, velocity/angular velocity persist
|
||||
|
||||
State.WALKING:
|
||||
_apply_walking_movement(delta)
|
||||
@ -105,10 +101,6 @@ func _physics_process(delta: float):
|
||||
_apply_ladder_floating_drag(delta)
|
||||
State.GRIPPING_LADDER:
|
||||
_apply_ladder_movement(delta)
|
||||
State.GRABBING_SURFACE:
|
||||
_handle_grabbed_surface_state(delta) # Sets vel/angular to zero
|
||||
State.CHARGING_LAUNCH:
|
||||
_handle_launch_charge(delta) # Sets vel/angular to zero
|
||||
State.REACHING_MOVE:
|
||||
# TODO: Delegate to ReachMoveController.process_movement(...)
|
||||
pass
|
||||
@ -130,8 +122,8 @@ func _physics_process(delta: float):
|
||||
if collision_count > 0:
|
||||
var collision = get_slide_collision(collision_count - 1) # Get last collision
|
||||
# Delegate or handle basic bounce
|
||||
if current_state == State.FLOATING and eva_suit_controller:
|
||||
eva_suit_controller.handle_collision(collision, collision_energy_loss)
|
||||
if current_state == State.FLOATING and eva_suit_component:
|
||||
eva_suit_component.handle_collision(collision, collision_energy_loss)
|
||||
else:
|
||||
_handle_basic_collision(collision)
|
||||
else:
|
||||
@ -148,17 +140,21 @@ func _on_enter_state(state: State):
|
||||
print("Entering State: ", State.keys()[state])
|
||||
match state:
|
||||
State.FLOATING:
|
||||
if eva_suit_controller: eva_suit_controller.on_enter_state()
|
||||
State.GRABBING_SURFACE, State.GRIPPING_LADDER, State.CHARGING_LAUNCH:
|
||||
if eva_suit_component: eva_suit_component.on_enter_state()
|
||||
State.GRABBING_GRIP, State.GRIPPING_LADDER, State.CHARGING_LAUNCH:
|
||||
velocity = Vector3.ZERO
|
||||
angular_velocity = Vector3.ZERO # Stop all motion immediately
|
||||
angular_velocity = Vector3.ZERO
|
||||
if zero_g_movemement_component and state == State.GRABBING_GRIP:
|
||||
zero_g_movemement_component.on_enter_state(state) # Notify controller
|
||||
# Add other state entry logic as needed
|
||||
|
||||
func _on_exit_state(state: State):
|
||||
# print("Exiting State: ", State.keys()[state]) # Optional debug
|
||||
match state:
|
||||
State.FLOATING:
|
||||
if eva_suit_controller: eva_suit_controller.on_exit_state()
|
||||
if eva_suit_component: eva_suit_component.on_exit_state()
|
||||
State.GRABBING_GRIP:
|
||||
if zero_g_movemement_component: zero_g_movemement_component.on_exit_state(state)
|
||||
# Add other state exit logic as needed
|
||||
|
||||
# --- State Transition Logic ---
|
||||
@ -172,10 +168,8 @@ func _update_state_transitions():
|
||||
State.FLOATING, State.WALKING, State.ON_LADDER:
|
||||
if _interact_pressed:
|
||||
if potential_state == State.ON_LADDER: self.current_state = State.GRIPPING_LADDER
|
||||
elif _check_for_grab_surface(): self.current_state = State.GRABBING_SURFACE
|
||||
elif current_state != potential_state and \
|
||||
current_state != State.GRIPPING_LADDER and \
|
||||
current_state != State.GRABBING_SURFACE and \
|
||||
current_state != State.CHARGING_LAUNCH and \
|
||||
current_state != State.REACHING_MOVE:
|
||||
self.current_state = potential_state
|
||||
@ -184,10 +178,6 @@ func _update_state_transitions():
|
||||
# if not _interact_held or not is_instance_valid(overlapping_ladder_area): self.current_state = State.ON_LADDER
|
||||
# elif _move_input != Vector2.ZERO: _start_charge(true)
|
||||
|
||||
# State.GRABBING_SURFACE:
|
||||
# if _interact_released: self.current_state = potential_state
|
||||
# elif _move_input != Vector2.ZERO: _start_charge(false)
|
||||
|
||||
# State.CHARGING_LAUNCH:
|
||||
# if _interact_released: _execute_launch(); self.current_state = potential_state
|
||||
# elif _move_input == Vector2.ZERO: _cancel_charge()
|
||||
@ -196,7 +186,7 @@ func _update_state_transitions():
|
||||
pass # TODO
|
||||
|
||||
# --- Universal Rotation ---
|
||||
func _apply_mouse_rotation(delta: float):
|
||||
func _apply_mouse_rotation():
|
||||
if _pitch_yaw_input != Vector2.ZERO:
|
||||
camera_pivot.rotate_y(-_pitch_yaw_input.x)
|
||||
|
||||
@ -227,53 +217,35 @@ func _handle_basic_collision(collision: KinematicCollision3D):
|
||||
# Applies torque affecting angular velocity
|
||||
func add_torque(torque_global: Vector3, delta: float):
|
||||
# Calculate effective inertia (base + suit multiplier if applicable)
|
||||
var effective_inertia = base_inertia * (eva_suit_controller.inertia_multiplier if eva_suit_controller else 1.0)
|
||||
var effective_inertia = base_inertia * (eva_suit_component.inertia_multiplier if eva_suit_component else 1.0)
|
||||
if effective_inertia <= 0: effective_inertia = 1.0 # Safety prevent division by zero
|
||||
# Apply change directly to angular velocity using the global torque
|
||||
|
||||
angular_velocity += (torque_global / effective_inertia) * delta
|
||||
|
||||
# --- Movement Implementations (Keep non-EVA ones here) ---
|
||||
func _apply_walking_movement(delta: float): pass # TODO
|
||||
func _apply_walking_movement(_delta: float): pass # TODO
|
||||
func _apply_ladder_floating_drag(delta: float):
|
||||
velocity = velocity.lerp(Vector3.ZERO, delta * 2.0);
|
||||
angular_velocity = angular_velocity.lerp(Vector3.ZERO, delta * 2.0)
|
||||
func _apply_ladder_movement(delta: float): pass # TODO
|
||||
func _handle_grabbed_surface_state(delta: float): velocity = Vector3.ZERO; angular_velocity = Vector3.ZERO
|
||||
func _handle_launch_charge(delta: float): launch_charge = min(launch_charge + launch_charge_rate * delta, max_launch_speed); velocity = Vector3.ZERO; angular_velocity = Vector3.ZERO
|
||||
func _execute_launch(): velocity = launch_direction * launch_charge; launch_charge = 0.0
|
||||
func _apply_ladder_movement(_delta: float): pass # TODO
|
||||
|
||||
# --- Input Setters/Resets (Add vertical to set_movement_input) ---
|
||||
func set_movement_input(move: Vector2, roll: float, vertical: float): _move_input = move; _roll_input = roll; _vertical_input = vertical
|
||||
func set_interaction_input(pressed: bool, released: bool): _interact_pressed = pressed; _interact_released = released
|
||||
func set_rotation_input(pitch_yaw_input: Vector2): _pitch_yaw_input += pitch_yaw_input
|
||||
func set_click_input(r_pressed: bool, r_held: bool, r_released: bool): _r_pressed = r_pressed; _r_held = r_held; _r_released = r_released
|
||||
func set_click_input(l_pressed: bool, l_held: bool, l_released: bool, r_pressed: bool, r_held: bool, r_released: bool):
|
||||
_l_pressed = l_pressed
|
||||
_l_held = l_held
|
||||
_l_released = l_released
|
||||
_r_pressed = r_pressed
|
||||
_r_held = r_held
|
||||
_r_released = r_released
|
||||
func _reset_inputs(): _move_input = Vector2.ZERO; _roll_input = 0.0; _vertical_input = 0.0; _interact_pressed = false; _interact_released = false; _r_pressed = false; _r_released = false; _pitch_yaw_input = Vector2.ZERO # Keep _r_held
|
||||
|
||||
# --- Helper Functions ---
|
||||
func _check_for_grab_surface() -> bool:
|
||||
grab_ray.target_position = camera_pivot.transform.basis.z * -grab_check_distance # Ray points forward from camera pivot
|
||||
grab_ray.force_raycast_update()
|
||||
if grab_ray.is_colliding(): grab_surface_normal = grab_ray.get_collision_normal(); return true
|
||||
return false
|
||||
|
||||
func _on_ladder_area_entered(area: Area3D): if area.is_in_group("Ladders"): overlapping_ladder_area = area
|
||||
func _on_ladder_area_exited(area: Area3D): if area == overlapping_ladder_area: overlapping_ladder_area = null
|
||||
func _reset_head_yaw(delta: float):
|
||||
# Smoothly apply the reset target to the actual pivot rotation
|
||||
camera_pivot.rotation.y = lerpf(camera_pivot.rotation.y, 0.0, delta * head_turn_lerp_speed)
|
||||
|
||||
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
|
||||
|
||||
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")
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://7yc6a07xoccy"]
|
||||
[gd_scene load_steps=8 format=3 uid="uid://7yc6a07xoccy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cdmmiixa75f3x" path="res://scenes/tests/3d/character_pawn_3d.gd" id="1_4frsu"]
|
||||
[ext_resource type="Script" uid="uid://vjfk3xnapfti" path="res://scenes/tests/3d/player_controller_3d.gd" id="2_r62el"]
|
||||
[ext_resource type="PackedScene" uid="uid://bm1rbv4tuppbc" path="res://eva_suit_controller.tscn" id="3_gnddn"]
|
||||
[ext_resource type="Script" uid="uid://y3vo40i16ek3" path="res://scenes/tests/3d/zero_g_movement_component.gd" id="4_8jhjh"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_6vm80"]
|
||||
|
||||
@ -21,11 +22,6 @@ shape = SubResource("CapsuleShape3D_6vm80")
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
mesh = SubResource("CapsuleMesh_6vm80")
|
||||
|
||||
[node name="GrabRay" type="RayCast3D" parent="."]
|
||||
target_position = Vector3(0, 0, -1)
|
||||
collide_with_areas = true
|
||||
debug_shape_custom_color = Color(0.443137, 0, 0, 0.615686)
|
||||
|
||||
[node name="PlayerController3D" type="Node" parent="."]
|
||||
script = ExtResource("2_r62el")
|
||||
metadata/_custom_type_script = "uid://vjfk3xnapfti"
|
||||
@ -39,8 +35,6 @@ spring_length = 3.0
|
||||
[node name="Camera3D" type="Camera3D" parent="CameraPivot/SpringArm"]
|
||||
current = true
|
||||
|
||||
[node name="EVASuitController" parent="." instance=ExtResource("3_gnddn")]
|
||||
|
||||
[node name="GripDetector" type="Area3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.985542)
|
||||
collision_layer = 0
|
||||
@ -49,3 +43,9 @@ monitorable = false
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="GripDetector"]
|
||||
shape = SubResource("SphereShape3D_gnddn")
|
||||
|
||||
[node name="ZeroGMovementComponent" type="Node3D" parent="."]
|
||||
script = ExtResource("4_8jhjh")
|
||||
metadata/_custom_type_script = "uid://y3vo40i16ek3"
|
||||
|
||||
[node name="EVAMovementComponent" parent="." instance=ExtResource("3_gnddn")]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# eva_suit_controller.gd
|
||||
extends Node # Or Node3D if thrusters need specific positions later
|
||||
class_name EVASuitController
|
||||
class_name EVAMovementComponent
|
||||
|
||||
## References (Set automatically in _ready)
|
||||
var pawn: CharacterBody3D
|
||||
@ -8,6 +8,7 @@ var camera_pivot: Node3D
|
||||
var camera: Camera3D
|
||||
|
||||
## EVA Parameters (Moved from ZeroGPawn)
|
||||
@export var orientation_speed: float = 2.0 # Used for orienting body to camera
|
||||
@export var move_speed: float = 2.0
|
||||
@export var roll_torque: float = 2.5
|
||||
@export var angular_damping: float = 0.95 # Base damping applied by pawn, suit might add more?
|
||||
@ -22,7 +23,7 @@ var stabilization_enabled: bool = false
|
||||
func _ready():
|
||||
pawn = get_parent() as CharacterBody3D
|
||||
if not pawn:
|
||||
printerr("EVASuitController must be a child of a CharacterBody3D pawn.")
|
||||
printerr("EVAMovementComponent must be a child of a CharacterBody3D pawn.")
|
||||
return
|
||||
# Make sure the paths match your CharacterPawn scene structure
|
||||
camera_pivot = pawn.get_node_or_null("CameraPivot")
|
||||
@ -30,7 +31,7 @@ func _ready():
|
||||
camera = camera_pivot.get_node_or_null("SpringArm/Camera3D") # Adjusted path for SpringArm
|
||||
|
||||
if not camera_pivot or not camera:
|
||||
printerr("EVASuitController could not find CameraPivot/SpringArm/Camera3D on pawn.")
|
||||
printerr("EVAMovementComponent could not find CameraPivot/SpringArm/Camera3D on pawn.")
|
||||
|
||||
# --- Standardized Movement API ---
|
||||
|
||||
@ -124,7 +125,7 @@ func _orient_pawn(delta: float):
|
||||
|
||||
# 2. Smoothly Interpolate Towards Target Basis
|
||||
var current_basis = pawn.global_transform.basis
|
||||
var new_basis = current_basis.slerp(target_basis, delta * pawn.orientation_speed)
|
||||
var new_basis = current_basis.slerp(target_basis, delta * orientation_speed)
|
||||
|
||||
# Store the body's yaw *before* applying the new basis
|
||||
var old_body_yaw = current_basis.get_euler().y
|
||||
@ -65,3 +65,6 @@ func release(pawn: CharacterPawn3D):
|
||||
# Re-enable collision?
|
||||
return true
|
||||
return false
|
||||
|
||||
func is_occupied() -> bool:
|
||||
return occupant != null
|
||||
|
||||
@ -24,6 +24,7 @@ radius = 0.01
|
||||
|
||||
[node name="Grip" type="StaticBody3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.1)
|
||||
collision_layer = 32769
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Grip"]
|
||||
mesh = SubResource("CylinderMesh_c81dj")
|
||||
|
||||
@ -30,13 +30,17 @@ func _physics_process(_delta):
|
||||
var vertical_input = Input.get_action_strength("move_up_3d") - Input.get_action_strength("move_down_3d")
|
||||
var interact_pressed = Input.is_action_just_pressed("spacebar_3d")
|
||||
var interact_released = Input.is_action_just_released("spacebar_3d") # Send release too
|
||||
var right_click_pressed = Input.is_action_just_pressed("right_click")
|
||||
var right_click_held = Input.is_action_pressed("right_click")
|
||||
var right_click_released = Input.is_action_just_released("right_click")
|
||||
|
||||
server_process_movement_input.rpc_id(1, move_vec, roll_input, vertical_input)
|
||||
server_process_interaction_input.rpc_id(1, interact_pressed, interact_released)
|
||||
server_process_clicks.rpc_id(1, right_click_pressed, right_click_held, right_click_released)
|
||||
server_process_clicks.rpc_id(1,
|
||||
Input.is_action_just_pressed("left_click"),
|
||||
Input.is_action_pressed("left_click"),
|
||||
Input.is_action_just_released("left_click"),
|
||||
Input.is_action_just_pressed("right_click"),
|
||||
Input.is_action_pressed("right_click"),
|
||||
Input.is_action_just_released("right_click")
|
||||
)
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func server_process_movement_input(move: Vector2, roll: float, vertical: float):
|
||||
@ -54,9 +58,9 @@ func server_process_rotation_input(input: Vector2):
|
||||
possessed_pawn.set_rotation_input(input)
|
||||
|
||||
@rpc("any_peer", "call_local")
|
||||
func server_process_clicks(r_pressed, r_held, r_released):
|
||||
func server_process_clicks(l_pressed, l_held, l_released, r_pressed, r_held, r_released):
|
||||
if is_instance_valid(possessed_pawn):
|
||||
possessed_pawn.set_click_input(r_pressed, r_held, r_released)
|
||||
possessed_pawn.set_click_input(l_pressed, l_held, l_released, r_pressed, r_held, r_released)
|
||||
|
||||
func possess(pawn_to_control: CharacterPawn3D):
|
||||
possessed_pawn = pawn_to_control
|
||||
|
||||
218
scenes/tests/3d/zero_g_movement_component.gd
Normal file
218
scenes/tests/3d/zero_g_movement_component.gd
Normal file
@ -0,0 +1,218 @@
|
||||
# zero_g_move_controller.gd
|
||||
extends Node
|
||||
class_name ZeroGMovementComponent
|
||||
|
||||
## References
|
||||
var pawn: CharacterPawn3D
|
||||
var camera_pivot: Node3D
|
||||
|
||||
## State & Parameters
|
||||
var current_grip: GripArea3D = null # Use GripArea3D type hint
|
||||
var nearby_grips: Array[GripArea3D] = []
|
||||
@export var reach_speed: float = 10.0 # Speed pawn moves towards grip
|
||||
@export var reach_orient_speed: float = 10.0 # Speed pawn orients to grip
|
||||
@export var launch_charge_rate: float = 20.0
|
||||
@export var max_launch_speed: float = 15.0
|
||||
var launch_direction: Vector3 = Vector3.ZERO
|
||||
var launch_charge: float = 0.0
|
||||
|
||||
# Enum for internal state (distinct from pawn's main state)
|
||||
enum ReachState { IDLE, REACHING, GRIPPING, CHARGING_LAUNCH }
|
||||
var reach_state: ReachState = ReachState.IDLE
|
||||
|
||||
func _ready():
|
||||
pawn = get_parent() as CharacterPawn3D
|
||||
if not pawn: printerr("ZeroGMovementComponent must be child of CharacterPawn3D")
|
||||
camera_pivot = pawn.get_node_or_null("CameraPivot")
|
||||
if not camera_pivot: printerr("ZeroGMovementComponent couldn't find CameraPivot")
|
||||
|
||||
# --- Standardized Movement API ---
|
||||
|
||||
## Called by Pawn when relevant state is active (e.g., GRABBING_GRIP, REACHING_MOVE)
|
||||
func process_movement(delta: float, move_input: Vector2, reaching_pressed: bool, reaching_held: bool, reaching_released: bool):
|
||||
if not is_instance_valid(pawn): return
|
||||
|
||||
_update_reach_state(delta, move_input, reaching_pressed, reaching_held, reaching_released)
|
||||
|
||||
match reach_state:
|
||||
ReachState.IDLE:
|
||||
# If pawn is FLOATING and interact pressed, try initiating a reach
|
||||
print("ZerGoMovementComponent: Reaching: ", reaching_held)
|
||||
ReachState.REACHING:
|
||||
if pawn.current_state == CharacterPawn3D.State.FLOATING and reaching_held:
|
||||
print("bar")
|
||||
_try_initiate_reach()
|
||||
_process_reaching(delta)
|
||||
ReachState.GRIPPING:
|
||||
_handle_gripping_state(delta, move_input)
|
||||
ReachState.CHARGING_LAUNCH:
|
||||
_handle_launch_charge(delta)
|
||||
|
||||
|
||||
## Called by Pawn for collision (optional, might not be needed if grabbing stops movement)
|
||||
func handle_collision(collision: KinematicCollision3D, collision_energy_loss: float):
|
||||
# Basic bounce if somehow colliding while using this controller
|
||||
var surface_normal = collision.get_normal()
|
||||
pawn.velocity = pawn.velocity.bounce(surface_normal)
|
||||
pawn.velocity *= (1.0 - collision_energy_loss * 0.5)
|
||||
|
||||
## Called by Pawn when entering a state managed by this controller
|
||||
func on_enter_state(pawn_state: CharacterPawn3D.State):
|
||||
print("ZeroGMovementComponent activated for state: ", CharacterPawn3D.State.keys()[pawn_state])
|
||||
if pawn_state == CharacterPawn3D.State.GRABBING_GRIP:
|
||||
reach_state = ReachState.GRIPPING
|
||||
pawn.velocity = Vector3.ZERO
|
||||
pawn.angular_velocity = Vector3.ZERO
|
||||
# else: # e.g., REACHING_MOVE?
|
||||
# reach_state = ReachState.IDLE # Or SEARCHING?
|
||||
|
||||
## Called by Pawn when exiting a state managed by this controller
|
||||
func on_exit_state(pawn_state: CharacterPawn3D.State):
|
||||
print("ZeroGMovementComponent deactivated for state: ", CharacterPawn3D.State.keys()[pawn_state])
|
||||
# Ensure grip is released if pawn state changes unexpectedly
|
||||
_release_current_grip()
|
||||
reach_state = ReachState.IDLE
|
||||
|
||||
|
||||
# --- Internal Logic ---
|
||||
|
||||
func _update_reach_state(_delta: float, _move_input: Vector2, reaching_pressed: bool, reaching_held: bool, _reaching_released: bool):
|
||||
match reach_state:
|
||||
ReachState.IDLE:
|
||||
# Already handled initiating reach in process_movement
|
||||
if reaching_pressed or reaching_held:
|
||||
reach_state = ReachState.REACHING
|
||||
ReachState.REACHING:
|
||||
# TODO: If reach animation completes/hand near target -> GRIPPING
|
||||
# If interact released during reach -> CANCEL -> IDLE
|
||||
|
||||
if not reaching_held:
|
||||
_cancel_reach()
|
||||
ReachState.GRIPPING:
|
||||
if not is_instance_valid(current_grip):
|
||||
_release_current_grip()
|
||||
# Pawn's main state machine will handle transition out
|
||||
# elif move_input != Vector2.ZERO:
|
||||
# _start_charge(move_input) # Start charging launch
|
||||
# ReachState.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
|
||||
# reach_state = ReachState.GRIPPING
|
||||
# print("ZeroGMovementComponent: Cancelled Launch Charge")
|
||||
|
||||
|
||||
func _try_initiate_reach():
|
||||
print("foo")
|
||||
print("ZeroGMovementComponent: Trying to Initiate Grab on", nearby_grips)
|
||||
|
||||
var closest_grip: GripArea3D = _find_closest_available_grip()
|
||||
print(closest_grip)
|
||||
if is_instance_valid(closest_grip):
|
||||
# if closest_grip.grab(pawn):
|
||||
current_grip = closest_grip
|
||||
# Instead of directly setting pawn state, maybe pawn checks if grip was successful?
|
||||
# For now, assume direct control:
|
||||
pawn.current_state = CharacterPawn3D.State.GRABBING_GRIP # Transition pawn state
|
||||
reach_state = ReachState.GRIPPING # Set internal state
|
||||
print("ZeroGMovementComponent: Initiated grab on ", current_grip.get_parent().name)
|
||||
# else:
|
||||
# print("ZeroGMovementComponent: Grab failed (grip occupied?)")
|
||||
else:
|
||||
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_to(grip.global_transform.origin)
|
||||
if distance < closest_distance:
|
||||
closest_distance = distance
|
||||
closest_grip = grip
|
||||
return closest_grip
|
||||
|
||||
func _process_reaching(_delta: float):
|
||||
# TODO: Drive IK target towards current_grip.get_grip_transform().origin
|
||||
# TODO: Monitor distance / animation state
|
||||
# When close enough: reach_state = ReachState.GRIPPING
|
||||
pass
|
||||
|
||||
|
||||
func _handle_gripping_state(delta: float, _move_input: Vector2):
|
||||
pawn.velocity = Vector3.ZERO
|
||||
pawn.angular_velocity = Vector3.ZERO
|
||||
if not is_instance_valid(current_grip): _cancel_reach(); return # Safety check
|
||||
|
||||
# --- Simple Visualization: Lerp towards Grip Transform ---
|
||||
var target_transform = current_grip.get_grip_transform(pawn.global_position)
|
||||
pawn.global_transform.origin = pawn.global_transform.origin.lerp(target_transform.origin, delta * reach_speed)
|
||||
pawn.global_transform.basis = pawn.global_transform.basis.slerp(target_transform.basis, delta * reach_orient_speed)
|
||||
|
||||
|
||||
func _start_charge(move_input: Vector2):
|
||||
if not is_instance_valid(current_grip): return
|
||||
reach_state = ReachState.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()
|
||||
# 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")
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
func _execute_launch():
|
||||
if not is_instance_valid(current_grip): return # Safety check
|
||||
_release_current_grip() # Release AFTER calculating direction
|
||||
pawn.velocity = launch_direction * launch_charge # Apply launch velocity to pawn
|
||||
launch_charge = 0.0
|
||||
reach_state = ReachState.IDLE
|
||||
print("ZeroGMovementComponent: Launched with speed ", pawn.velocity.length())
|
||||
|
||||
|
||||
func _release_current_grip():
|
||||
if is_instance_valid(current_grip):
|
||||
current_grip.release(pawn)
|
||||
current_grip = null
|
||||
reach_state = ReachState.IDLE
|
||||
|
||||
|
||||
func _cancel_reach():
|
||||
# TODO: Logic to stop IK/animation if reach is cancelled mid-way
|
||||
_release_current_grip() # Ensure grip reference is cleared
|
||||
reach_state = ReachState.IDLE
|
||||
print("ZeroGMovementComponent: Reach cancelled.")
|
||||
|
||||
|
||||
# --- 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
|
||||
|
||||
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")
|
||||
1
scenes/tests/3d/zero_g_movement_component.gd.uid
Normal file
1
scenes/tests/3d/zero_g_movement_component.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://y3vo40i16ek3
|
||||
Reference in New Issue
Block a user