feat(*): Add much better ship controls

This commit is contained in:
Josh Creek
2025-07-13 21:01:57 +01:00
parent 709bc947f5
commit cba9a51b5f
5 changed files with 379 additions and 52 deletions
+11 -3
View File
@@ -1,12 +1,20 @@
[gd_scene load_steps=3 format=3 uid="uid://27u3tdc5yqnl"]
[gd_scene load_steps=4 format=3 uid="uid://27u3tdc5yqnl"]
[ext_resource type="ArrayMesh" uid="uid://ucw1lyo43yi4" path="res://assets/models/gold_ball.res" id="1_ct1s3"]
[sub_resource type="SphereShape3D" id="SphereShape3D_c5p07"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_ball"]
bounce = 0.8
friction = 0.3
[node name="Ball" type="RigidBody3D"]
mass = 100.0
inertia = Vector3(100, 100, 100)
mass = 3
physics_material_override = SubResource("PhysicsMaterial_ball")
inertia = Vector3(3, 3, 3)
gravity_scale = 0.8
linear_damp = 0.1
angular_damp = 0.1
metadata/_edit_group_ = true
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
+37
View File
@@ -65,6 +65,43 @@ move_down={
turbo={
"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":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
]
}
turn_left={
"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":74,"key_label":0,"unicode":106,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
]
}
turn_right={
"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":76,"key_label":0,"unicode":108,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
]
}
pitch_up={
"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":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
]
}
pitch_down={
"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":75,"key_label":0,"unicode":107,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
]
}
roll_left={
"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":85,"key_label":0,"unicode":117,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":false,"script":null)
]
}
roll_right={
"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":79,"key_label":0,"unicode":111,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":false,"script":null)
]
}
+2 -2
View File
@@ -42,10 +42,10 @@ process_callback = 0
autostart = true
[node name="Ship" parent="." instance=ExtResource("4_iywne")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.16266, 2.82216, 16.1299)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.497204, 2.82216, 27.8553)
lock_rotation = true
[node name="Ball" parent="." instance=ExtResource("5_iywne")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 18.2942, 1.46401, -2.82393)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.464103, 1.46401, -2.82393)
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
+143 -47
View File
@@ -2,10 +2,15 @@ class_name Ship
extends RigidBody3D
@export_group("Movement")
var max_speed = 15.0
var acceleration = 50.0
var air_resistance = 0.98
var turn_speed = 2.0
@export var thrust_power = 150.0 # Main thruster power
@export var maneuvering_thrust = 75.0 # Side/vertical thruster power
@export var vertical_thrust = 120.0 # Up/down thruster power
@export var turbo_multiplier = 2.5 # Turbo boost multiplier
@export var max_speed = 25.0 # Maximum velocity
@export var rotation_power = 20.0 # Angular thrust power
@export var max_angular_speed = 3.0 # Maximum rotation speed
@export var drag_coefficient = 0.98 # Linear drag (air resistance)
@export var angular_drag = 0.95 # Rotational drag
@export_group("Camera")
var camera_distance = 8.0
@@ -19,12 +24,35 @@ var camera_smoothing = 10.0
var ball_cam_enabled = true
func _ready():
mass = 5
gravity_scale = 1.0
# Set custom inertia for better rotation
inertia = Vector3(1.0, 1.0, 1.0)
# Create and apply low-friction physics material
var ship_material = PhysicsMaterial.new()
ship_material.friction = 0.1 # Very low friction
ship_material.bounce = 0.2 # Slight bounce
physics_material_override = ship_material
# Make sure the RigidBody is completely free to move and rotate
freeze = false
lock_rotation = false
# Ensure all axes can rotate
axis_lock_angular_x = false
axis_lock_angular_y = false
axis_lock_angular_z = false
var game_scene = get_parent()
if game_scene and game_scene.has_signal("timer_updated"):
game_scene.timer_updated.connect(_on_timer_updated)
print("Connected to game scene timer signal")
else:
print("Game scene not found or doesn't have timer_updated signal")
print("Ship physics configured - Mass: ", mass, " Gravity scale: ", gravity_scale, " Inertia: ", inertia)
print("Rotation locks - X:", axis_lock_angular_x, " Y:", axis_lock_angular_y, " Z:", axis_lock_angular_z)
func _input(event):
if event.is_action_pressed("ui_accept"): # Enter key
@@ -88,51 +116,119 @@ func _update_ship_cam(delta):
camera.look_at(look_target, Vector3.UP)
func _integrate_forces(state):
# Get input in ship's local coordinate system
var input = get_ship_input()
# Get thruster input
var thrust_input = get_thrust_input()
var rotation_input = get_rotation_input()
# Apply forces based on ship's orientation
if input.length() > 0.01:
var ship_forward = -global_transform.basis.z
var ship_right = global_transform.basis.x
# Calculate movement direction based on ship orientation
var movement_dir = Vector3.ZERO
movement_dir += ship_forward * input.z # Forward/backward
movement_dir += ship_right * input.x # Left/right
movement_dir.y += input.y # Up/down
# Apply acceleration force
var target_velocity = movement_dir.normalized() * max_speed
var velocity_diff = target_velocity - state.linear_velocity
var force = velocity_diff * acceleration * mass
state.apply_central_force(force)
# Turn the ship to face movement direction (only if moving horizontally)
var horizontal_movement = Vector3(movement_dir.x, 0, movement_dir.z)
if horizontal_movement.length() > 0.1:
var target_transform = global_transform.looking_at(global_transform.origin + horizontal_movement, Vector3.UP)
state.transform.basis = state.transform.basis.slerp(target_transform.basis, turn_speed * state.step)
# === TRANSLATION (Movement) ===
apply_thruster_forces(state, thrust_input)
# Apply air resistance
state.linear_velocity *= air_resistance
# === ROTATION (Turning) ===
apply_rotation_forces(state, rotation_input)
# === DRAG AND LIMITS ===
apply_drag_and_limits(state, rotation_input)
func get_ship_input() -> Vector3:
var input = Vector3.ZERO
func get_thrust_input() -> Vector3:
var thrust = Vector3.ZERO
# Get input as if controlling a ship (forward/backward/left/right relative to ship)
# Forward/Backward thrust (main engines)
if Input.is_action_pressed("move_forward"):
input.z += 1.0
if Input.is_action_pressed("move_backward"):
input.z -= 1.0
if Input.is_action_pressed("move_left"):
input.x -= 1.0
if Input.is_action_pressed("move_right"):
input.x += 1.0
if Input.is_action_pressed("move_up"):
input.y += 1.0
if Input.is_action_pressed("move_down"):
input.y -= 1.0
thrust.z += 1.0
if Input.is_action_pressed("move_back"):
thrust.z -= 1.0
return input
# Strafe thrusters (left/right)
if Input.is_action_pressed("move_left"):
thrust.x -= 1.0
if Input.is_action_pressed("move_right"):
thrust.x += 1.0
# Vertical thrusters (up/down)
if Input.is_action_pressed("move_up"):
thrust.y += 1.0
if Input.is_action_pressed("move_down"):
thrust.y -= 1.0
return thrust
func get_rotation_input() -> Vector3:
var rotation = Vector3.ZERO
# Yaw (turn left/right around Y axis) - only use these if they exist
if Input.is_action_pressed("turn_left"):
rotation.y += 1.0
if Input.is_action_pressed("turn_right"):
rotation.y -= 1.0
# Pitch (nose up/down around X axis)
if Input.is_action_pressed("pitch_up"):
rotation.x -= 1.0
if Input.is_action_pressed("pitch_down"):
rotation.x += 1.0
# Roll (bank left/right around Z axis)
if Input.is_action_pressed("roll_left"):
rotation.z += 1.0
if Input.is_action_pressed("roll_right"):
rotation.z -= 1.0
return rotation
func apply_thruster_forces(state: PhysicsDirectBodyState3D, thrust_input: Vector3):
if thrust_input.length() < 0.01:
return
# Convert thrust input to world space forces based on ship orientation
var ship_basis = global_transform.basis
var world_thrust = Vector3.ZERO
# All thrusters should work relative to ship orientation
# Forward/backward thrust (main engines)
world_thrust += -ship_basis.z * thrust_input.z * thrust_power
# Strafe thrust (left/right maneuvering thrusters)
world_thrust += ship_basis.x * thrust_input.x * maneuvering_thrust
# Vertical thrust (up/down thrusters relative to ship orientation)
world_thrust += ship_basis.y * thrust_input.y * vertical_thrust
# Check for turbo
var is_turbo = Input.is_action_pressed("turbo") and thrust_input.z > 0
if is_turbo:
world_thrust *= turbo_multiplier
# Apply the force
state.apply_central_force(world_thrust)
func apply_rotation_forces(state: PhysicsDirectBodyState3D, rotation_input: Vector3):
if rotation_input.length() < 0.01:
return
# Apply torque for rotation - simple and effective
var torque = Vector3(
rotation_input.x * rotation_power, # Pitch
rotation_input.y * rotation_power, # Yaw
rotation_input.z * rotation_power # Roll
)
state.apply_torque(torque)
func apply_drag_and_limits(state: PhysicsDirectBodyState3D, rotation_input: Vector3):
# Linear drag (air resistance)
state.linear_velocity *= drag_coefficient
# Angular drag (rotational resistance) - more aggressive when not rotating
if rotation_input.length() < 0.01:
# More drag when not actively rotating to stop quicker
state.angular_velocity *= 0.9
else:
# Normal drag when actively rotating
state.angular_velocity *= angular_drag
# Limit maximum speeds
if state.linear_velocity.length() > max_speed:
state.linear_velocity = state.linear_velocity.normalized() * max_speed
if state.angular_velocity.length() > max_angular_speed:
state.angular_velocity = state.angular_velocity.normalized() * max_angular_speed