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
+186
View File
@@ -0,0 +1,186 @@
# 🚀 Cosmic Clash Flight Manual
Welcome to space, pilot! This guide will teach you everything you need to know about piloting your ship in the vast cosmos.
## 🎮 Control Schemes
### Keyboard Controls
#### Translation (Movement)
- `W` - Forward Thrust (Main Engines)
- `S` - Reverse Thrust (Retro Engines)
- `A` - Strafe Left (Port Thrusters)
- `D` - Strafe Right (Starboard Thrusters)
- `E` - Thrust Up (Dorsal Thrusters)
- `Q` - Thrust Down (Ventral Thrusters)
- `Shift` - Turbo Boost (Forward Only)
#### Rotation (Attitude Control)
- `J` - Turn Left (Yaw Left)
- `L` - Turn Right (Yaw Right)
- `I` - Pitch Up (Nose Up)
- `K` - Pitch Down (Nose Down)
- `U` - Roll Left (Bank Left)
- `O` - Roll Right (Bank Right)
### Controller Controls
#### Translation
- Left Stick - Strafe (Left/Right) + Thrust (Forward/Back)
- Right Trigger - Forward Thrust
- Left Trigger - Reverse Thrust
- Face Buttons - Up/Down Thrust
#### Rotation
- Right Stick - Pitch/Yaw
- Shoulder Buttons - Roll
- `A Button` - Turbo Boost
## 🛸 Basic Flight Principles
### Understanding Your Ship
Your ship is a **realistic space vehicle** with the following characteristics:
- **Momentum Conservation**: You keep moving even when not thrusting
- **Independent Rotation**: Your ship can face any direction while moving in another
- **Thruster-Based Movement**: Different thrusters provide different amounts of force
### Thruster Systems
1. **Main Engines** (Forward/Reverse): Most powerful, used for primary acceleration
2. **Maneuvering Thrusters** (Strafe/Vertical): Less powerful, for precise positioning
3. **Attitude Control** (Rotation): Independent rotational control around all three axes
## 🎯 Flight Techniques
### Beginner Maneuvers
#### Basic Movement
1. **Forward Flight**: Hold `W` to accelerate forward
2. **Turning**: Use `J`/`L` to turn left/right while moving
3. **Stopping**: Turn around 180° and thrust forward to decelerate
4. **Hovering**: Use vertical thrusters (`E`/`Q`) to maintain altitude
#### Camera Control
- **Ball Cam**: Press `Enter` to toggle ball tracking camera
- **Ship Cam**: Normal follow camera that looks where your ship points
### Intermediate Maneuvers
#### Efficient Movement
1. **Momentum Management**: Coast when possible to conserve energy
2. **Precision Landing**: Use gentle bursts of opposing thrust
3. **Quick Turns**: Combine rotation with strafe thrusters for sharp direction changes
#### Combat Positioning
1. **Strafing**: Move sideways while keeping guns on target
2. **Evasive Maneuvers**: Unpredictable movement patterns using all thrusters
### Advanced Techniques
#### Orbital Mechanics
1. **Prograde Burn**: Thrust in direction of movement to speed up
2. **Retrograde Burn**: Thrust opposite to movement to slow down
3. **Radial Burns**: Thrust perpendicular to movement to change orbit
#### Expert Maneuvers
1. **Flip and Burn**:
- Accelerate forward until at desired speed
- Rotate 180° while maintaining momentum
- Fire main engines to decelerate efficiently
- Most fuel-efficient way to stop
2. **Translation Sliding**:
- Build up forward momentum
- Use strafe thrusters to "slide" sideways
- Maintain forward velocity while moving laterally
3. **Zero-G Ballet**:
- Combine all rotation axes for complex orientation changes
- Use momentum to coast through tight spaces
- Master the "six degrees of freedom"
## 🎮 Ball Cam vs Ship Cam
### Ball Cam Mode (`Enter` to toggle)
- **Camera**: Always looks toward the ball
- **Ship Control**: Based on ship orientation (NOT camera view)
- **Best For**: Tracking the ball during gameplay
- **Challenge**: Movement direction may not match what you're looking at
### Ship Cam Mode
- **Camera**: Looks where your ship is pointing
- **Ship Control**: Intuitive - forward is where you're looking
- **Best For**: Precise navigation and docking
- **Advantage**: What you see is where you'll go
## ⚡ Energy Management
### Turbo System
- **Activation**: Hold `Shift` (keyboard) or `A` (controller) while thrusting forward
- **Effect**: 2.5x thrust multiplier on main engines only
- **Strategy**: Use for quick acceleration or emergency maneuvers
### Thrust Efficiency
- **Main Engines**: Most powerful, use for primary movement
- **Maneuvering Thrusters**: Less powerful but more precise
- **Conservation**: Coast on momentum when possible
## 🏆 Mastery Tips
### "Easy to Learn, Hard to Master"
1. **Start Simple**: Learn basic forward movement and turning
2. **Add Complexity**: Gradually incorporate strafe and vertical movement
3. **Master Physics**: Understand momentum and how forces combine
4. **Think in 3D**: Space has no "up" - use all six degrees of freedom
### Skill Development Path
1. **Novice**: Can fly forward and turn
2. **Pilot**: Comfortable with all basic movements
3. **Ace**: Uses momentum and efficient maneuvering
4. **Commander**: Masters complex orbital mechanics and combat flying
### Practice Exercises
1. **Figure-8 Flying**: Practice smooth turns and momentum control
2. **Precision Landing**: Land gently on targets using minimal thrust
3. **Obstacle Course**: Navigate tight spaces using translation controls
4. **Combat Training**: Practice keeping guns on target while evading
## 🔧 Tuning Your Ship
The following parameters can be adjusted for different flight characteristics:
- **Thrust Power**: How quickly you accelerate
- **Maneuvering Thrust**: Precision vs power for side movement
- **Rotation Power**: How quickly you can turn
- **Drag Coefficients**: How much momentum you retain
## 🌌 Welcome to Space
Remember: In space, every action has an equal and opposite reaction. Master the physics, and you'll master the game. Your ship responds to real forces - there's no magic "turn toward movement" or artificial limitations.
**Fly safe, pilot. The cosmos awaits!**
---
_"The universe is not only stranger than we imagine, it is stranger than we can imagine." - But your ship controls make perfect sense once you learn them._
+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