Initial 3d controller and pawn

This commit is contained in:
olof.pettersson
2025-10-22 14:40:19 +02:00
parent d8055752d5
commit 8645d2bdc4
7 changed files with 280 additions and 0 deletions

View File

@ -87,6 +87,46 @@ toggle_wiring_panel={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
move_left_3d={
"deadzone": 0.2,
"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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
move_right_3d={
"deadzone": 0.2,
"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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
move_forward_3d={
"deadzone": 0.2,
"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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
move_backward_3d={
"deadzone": 0.2,
"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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
roll_right_3d={
"deadzone": 0.2,
"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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
roll_left_3d={
"deadzone": 0.2,
"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":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
]
}
interact_3d={
"deadzone": 0.2,
"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":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
]
}
spacebar_3d={
"deadzone": 0.2,
"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":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
]
}
[layer_names]

View File

@ -0,0 +1,31 @@
# PlayerController3D.gd
extends Node
class_name PlayerController3D
@onready var possessed_pawn: ZeroGPawn = get_parent()
func _physics_process(_delta):
if not is_multiplayer_authority() or not is_instance_valid(possessed_pawn):
return
# 1. Gather Input
var move_vec = Input.get_vector("move_left_3d", "move_right_3d", "move_forward_3d", "move_backward_3d")
var roll_input = Input.get_action_strength("roll_right_3d") - Input.get_action_strength("roll_left_3d")
var interact_pressed = Input.is_action_just_pressed("interact_3d")
var interact_released = Input.is_action_just_released("interact_3d") # Send release too
# 2. Send via RPC
server_process_input.rpc_id(1, move_vec, roll_input, interact_pressed, interact_released)
@rpc("any_peer", "call_local")
func server_process_input(move: Vector2, roll: float, interact_p: bool, interact_r: bool):
if is_instance_valid(possessed_pawn):
possessed_pawn.set_movement_input(move, roll)
possessed_pawn.set_interaction_input(interact_p, interact_r)
func possess(pawn_to_control: ZeroGPawn):
possessed_pawn = pawn_to_control
# You might want reparenting logic here depending on your scene structure
print("PlayerController3D possessed: ", possessed_pawn.name)

View File

@ -0,0 +1 @@
uid://vjfk3xnapfti

View File

@ -0,0 +1,27 @@
[gd_scene load_steps=4 format=3 uid="uid://ddfsn0rtdnfda"]
[ext_resource type="PackedScene" uid="uid://7yc6a07xoccy" path="res://scenes/tests/3d/zero_g_pawn.tscn" id="1_nvgim"]
[sub_resource type="BoxMesh" id="BoxMesh_kateb"]
size = Vector3(10, 1, 10)
[sub_resource type="BoxShape3D" id="BoxShape3D_25xtv"]
size = Vector3(10, 1, 10)
[node name="ZeroG3DTest" type="Node3D"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.743413, -0.535317, 0.400964, 0, 0.599499, 0.800376, -0.668832, -0.59501, 0.445675, 0, 0, 0)
[node name="StaticBody3D" type="StaticBody3D" parent="."]
[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D"]
mesh = SubResource("BoxMesh_kateb")
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"]
shape = SubResource("BoxShape3D_25xtv")
[node name="ZeroGPawn" parent="." instance=ExtResource("1_nvgim")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)

View File

@ -0,0 +1,149 @@
# ZeroGPawn.gd
extends CharacterBody3D
class_name ZeroGPawn
# --- Movement Parameters ---
@export var move_speed: float = 5.0
@export var roll_speed: float = 1.0
@export var grab_check_distance: float = 1.0 # How close to a surface to attempt grabbing
@export var launch_charge_rate: float = 20.0
@export var max_launch_speed: float = 15.0
# --- State ---
enum State { FLOATING, GRABBING, CHARGING_LAUNCH }
var angular_velocity: Vector3 = Vector3.ZERO
var current_state: State = State.FLOATING
var grab_surface_normal: Vector3 = Vector3.ZERO # Normal of the surface we're grabbing
var launch_direction: Vector3 = Vector3.ZERO
var launch_charge: float = 0.0
# --- Input Buffers (Set by PlayerController3D) ---
var _move_input: Vector2 = Vector2.ZERO # (Strafe, Forward/Backward)
var _roll_input: float = 0.0
var _interact_pressed: bool = false
var _interact_released: bool = false
@onready var grab_ray: RayCast3D = $GrabRay # We'll need to add this node
func _physics_process(delta: float):
# --- Update State ---
_update_state()
# --- Apply Movement Based on State ---
match current_state:
State.FLOATING:
_apply_floating_movement(delta)
_handle_grab_attempt()
State.GRABBING:
_handle_grabbed_state(delta)
State.CHARGING_LAUNCH:
_handle_launch_charge(delta)
# --- Apply Velocity ---
# In 3D, CharacterBody3D doesn't have move_and_slide for velocity directly.
# We'll manage velocity manually for zero-G.
position += velocity * delta
# --- Reset Inputs ---
_move_input = Vector2.ZERO
_roll_input = 0.0
_interact_pressed = false
_interact_released = false
func _update_state():
match current_state:
State.FLOATING:
if _interact_pressed:
# Attempt to grab if near a surface
if _check_for_grab_surface():
current_state = State.GRABBING
velocity = Vector3.ZERO # Stop movement instantly
angular_velocity = Vector3.ZERO # Stop rotation instantly
print("State: GRABBING")
State.GRABBING:
if _interact_released:
current_state = State.FLOATING
print("State: FLOATING (Released Grab)")
elif _move_input != Vector2.ZERO: # Holding direction + interact
current_state = State.CHARGING_LAUNCH
launch_direction = (transform.basis.z * -_move_input.y + transform.basis.x * _move_input.x).normalized()
launch_charge = 0.0
print("State: CHARGING_LAUNCH")
State.CHARGING_LAUNCH:
if _interact_released:
_execute_launch()
current_state = State.FLOATING
print("State: FLOATING (Launched)")
elif _move_input == Vector2.ZERO: # Released direction while still holding interact
current_state = State.GRABBING # Go back to just grabbing
print("State: GRABBING (Cancelled Launch Charge)")
func _apply_floating_movement(delta: float):
# Apply linear velocity based on input relative to orientation
var move_dir = (transform.basis.z * -_move_input.y + transform.basis.x * _move_input.x).normalized()
velocity += move_dir * move_speed * delta
# Apply angular velocity for roll
# Note: Managing 3D angular velocity directly is complex.
# For simplicity, we directly rotate based on input.
rotate_object_local(Vector3.FORWARD, _roll_input * roll_speed * delta)
func _handle_grab_attempt():
# If interact was pressed this frame AND we are floating
if _interact_pressed:
if _check_for_grab_surface():
current_state = State.GRABBING
velocity = Vector3.ZERO
angular_velocity = Vector3.ZERO
print("State: GRABBING")
func _handle_grabbed_state(delta: float):
# While grabbing, stay perfectly still
velocity = Vector3.ZERO
angular_velocity = Vector3.ZERO
# Check if starting to charge launch
if _move_input != Vector2.ZERO:
current_state = State.CHARGING_LAUNCH
launch_direction = (transform.basis.z * -_move_input.y + transform.basis.x * _move_input.x).normalized()
launch_charge = 0.0
print("State: CHARGING_LAUNCH")
func _handle_launch_charge(delta: float):
# Increase charge while holding direction
launch_charge = min(launch_charge + launch_charge_rate * delta, max_launch_speed)
# Still holding interact key, so stay put
velocity = Vector3.ZERO
angular_velocity = Vector3.ZERO
func _execute_launch():
velocity = launch_direction * launch_charge
print("Launched with speed: ", launch_charge)
launch_charge = 0.0
func _check_for_grab_surface() -> bool:
# Use RayCast3D to check in multiple directions or based on movement
# For simplicity, let's just check directly forward
grab_ray.target_position = Vector3.FORWARD * -grab_check_distance # Ray points forward locally
grab_ray.force_raycast_update()
if grab_ray.is_colliding():
grab_surface_normal = grab_ray.get_collision_normal()
# Optional: Align player rotation to surface normal here
return true
return false
# --- Public Input Setters ---
func set_movement_input(move: Vector2, roll: float):
_move_input = move
_roll_input = roll
func set_interaction_input(pressed: bool, released: bool):
_interact_pressed = pressed
_interact_released = released

View File

@ -0,0 +1 @@
uid://buu6a8p00s05p

View File

@ -0,0 +1,31 @@
[gd_scene load_steps=5 format=3 uid="uid://7yc6a07xoccy"]
[ext_resource type="Script" uid="uid://buu6a8p00s05p" path="res://scenes/tests/3d/zero_g_pawn.gd" id="1_lm0u7"]
[ext_resource type="Script" uid="uid://vjfk3xnapfti" path="res://scenes/tests/3d/player_controller_3d.gd" id="2_r62el"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_6vm80"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_6vm80"]
[node name="ZeroGPawn" type="CharacterBody3D"]
script = ExtResource("1_lm0u7")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
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"
[node name="Node3D" type="Node3D" parent="."]
[node name="Camera3D" type="Camera3D" parent="Node3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 2)