Fix camera jitter with disabled v-sync

This commit is contained in:
olof.pettersson
2025-11-05 18:35:19 +01:00
parent 71ad2f09ff
commit 4da8bcaec2
5 changed files with 40 additions and 36 deletions

View File

@ -22,6 +22,10 @@ GameManager="*res://scripts/singletons/game_manager.gd"
Constants="*res://scripts/singletons/constants.gd"
NetworkHandler="*res://scripts/network/network_handler.gd"
[display]
window/vsync/vsync_mode=0
[dotnet]
project/assembly_name="space_simulation"
@ -166,12 +170,14 @@ left_click={
[physics]
common/physics_jitter_fix=0.0
3d/default_linear_damp=0.0
3d/sleep_threshold_linear=0.0
2d/default_gravity=0.0
2d/default_gravity_vector=Vector2(0, 0)
2d/default_linear_damp=0.0
2d/sleep_threshold_linear=0.0
common/physics_interpolation=true
[plugins]

View File

@ -16,6 +16,7 @@ var _r_click_input: PlayerController3D.KeyInput = PlayerController3D.KeyInput.ne
var _pitch_yaw_input: Vector2 = Vector2.ZERO
## Rotation Variables
@onready var camera_anchor: Marker3D = $CameraAnchor
@onready var camera_pivot: Node3D = $CameraPivot
@onready var camera: Camera3D = $CameraPivot/SpringArm/Camera3D
@export_range(0.1, PI / 2.0) var max_yaw_rad: float = deg_to_rad(80.0)
@ -54,7 +55,12 @@ func _ready():
if is_multiplayer_authority():
camera.make_current()
camera.process_mode = Node.PROCESS_MODE_ALWAYS
func _process(delta: float) -> void:
camera_pivot.global_transform = camera_anchor.get_global_transform_interpolated()
func _physics_process(delta: float):
# 1. Apply Mouse Rotation (Universal head look)
@ -86,15 +92,15 @@ func _physics_process(delta: float):
# --- Universal Rotation ---
func _apply_mouse_rotation():
if _pitch_yaw_input != Vector2.ZERO:
camera_pivot.rotate_y(-_pitch_yaw_input.x)
camera_anchor.rotate_y(-_pitch_yaw_input.x)
# Apply Pitch LOCALLY to pivot
camera_pivot.rotate_object_local(Vector3.RIGHT, _pitch_yaw_input.y)
camera_pivot.rotation.x = clamp(camera_pivot.rotation.x, min_pitch_rad, max_pitch_rad)
camera_anchor.rotate_object_local(Vector3.RIGHT, _pitch_yaw_input.y)
camera_anchor.rotation.x = clamp(camera_anchor.rotation.x, min_pitch_rad, max_pitch_rad)
_pitch_yaw_input = Vector2.ZERO
camera_pivot.rotation.z = 0.0
camera_anchor.rotation.z = 0.0
# --- Universal Integration & Collision ---
func _integrate_angular_velocity(delta: float):
@ -149,7 +155,7 @@ func _on_ladder_area_entered(area: Area3D): if area.is_in_group("Ladders"): over
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)
camera_anchor.rotation.y = lerpf(camera_anchor.rotation.y, 0.0, delta * head_turn_lerp_speed)
func _notification(what: int) -> void:
match what:

View File

@ -19,18 +19,9 @@ properties/0/replication_mode = 1
properties/1/path = NodePath(".:rotation")
properties/1/spawn = true
properties/1/replication_mode = 1
properties/2/path = NodePath("CameraPivot/SpringArm/Camera3D:position")
properties/2/path = NodePath("CameraPivot:rotation")
properties/2/spawn = true
properties/2/replication_mode = 1
properties/3/path = NodePath("CameraPivot/SpringArm/Camera3D:rotation")
properties/3/spawn = true
properties/3/replication_mode = 1
properties/4/path = NodePath("CameraPivot:position")
properties/4/spawn = true
properties/4/replication_mode = 1
properties/5/path = NodePath("CameraPivot:rotation")
properties/5/spawn = true
properties/5/replication_mode = 1
properties/2/replication_mode = 2
[node name="CharacterPawn3D" type="CharacterBody3D"]
script = ExtResource("1_4frsu")
@ -42,8 +33,12 @@ shape = SubResource("CapsuleShape3D_6vm80")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("CapsuleMesh_6vm80")
[node name="CameraAnchor" type="Marker3D" parent="."]
[node name="CameraPivot" type="Node3D" parent="."]
physics_interpolation_mode = 1
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0)
top_level = true
[node name="SpringArm" type="SpringArm3D" parent="CameraPivot"]
spring_length = 3.0

View File

@ -3,9 +3,7 @@ extends Node # Or Node3D if thrusters need specific positions later
class_name EVAMovementComponent
## References (Set automatically in _ready)
var pawn: CharacterBody3D
var camera_pivot: Node3D
var camera: Camera3D
var pawn: CharacterPawn3D
## EVA Parameters (Moved from ZeroGPawn)
@export var orientation_speed: float = 2.0 # Used for orienting body to camera
@ -21,24 +19,23 @@ var stabilization_target: Node3D = null
var stabilization_enabled: bool = false
func _ready():
pawn = get_parent() as CharacterBody3D
pawn = get_parent() as CharacterPawn3D
if not 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")
if camera_pivot:
camera = camera_pivot.get_node_or_null("SpringArm/Camera3D") # Adjusted path for SpringArm
# if camera_anchor:
# camera = camera_anchor.get_node_or_null("SpringArm/Camera3D") # Adjusted path for SpringArm
if not camera_pivot or not camera:
printerr("EVAMovementComponent could not find CameraPivot/SpringArm/Camera3D on pawn.")
# if not camera_anchor or not camera:
# printerr("EVAMovementComponent could not find CameraPivot/SpringArm/Camera3D on pawn.")
# --- Standardized Movement API ---
## Called by Pawn's _physics_process when in FLOATING state with suit equipped
func process_movement(delta: float, move_input: Vector2, vertical_input: float, roll_input: float, orienting_input: PlayerController3D.KeyInput):
var orienting = orienting_input.held
if not is_instance_valid(pawn) or not camera: return
if not is_instance_valid(pawn): return
if orienting:
_orient_pawn(delta)
@ -109,14 +106,14 @@ func _apply_floating_movement(delta: float, move_input: Vector2, vertical_input:
# --- Auto-Orientation Logic ---
func _orient_pawn(delta: float):
# 1. Determine Target Orientation Basis
var initial_cam_basis = camera_pivot.global_basis
var target_forward = -camera_pivot.global_basis.z # Look where camera looks
var initial_cam_basis = pawn.camera_anchor.global_basis
var target_forward = -pawn.camera_anchor.global_basis.z # Look where camera looks
var target_up = Vector3.UP # Default up initially
# --- THE FIX: Adjust how target_up is calculated ---
# Calculate velocity components relative to camera orientation
var _forward_velocity_component = pawn.velocity.dot(target_forward)
var _right_velocity_component = pawn.velocity.dot(camera_pivot.global_basis.x)
var _right_velocity_component = pawn.velocity.dot(pawn.camera_anchor.global_basis.x)
# Only apply strong "feet trailing" if significant forward/backward movement dominates
# and we are actually moving.
@ -154,7 +151,7 @@ func _orient_pawn(delta: float):
pawn.global_basis = new_basis
# 4. Reset camera pivot to rotation to what it was before we rotated the parent
camera_pivot.global_basis = initial_cam_basis
pawn.camera_anchor.global_basis = initial_cam_basis
# --- Add new function placeholder ---
# TODO: Implement Rotation Stabilization Logic

View File

@ -49,22 +49,22 @@ func _physics_process(_delta):
server_process_interaction_input.rpc_id(multiplayer.get_unique_id(), interact_input)
server_process_clicks.rpc_id(multiplayer.get_unique_id(), l_input, r_input)
@rpc("any_peer", "call_local")
@rpc("authority", "call_local")
func server_process_movement_input(move: Vector2, roll: float, vertical: float):
if is_instance_valid(possessed_pawn):
possessed_pawn.set_movement_input(move, roll, vertical)
@rpc("any_peer", "call_local")
@rpc("authority", "call_local")
func server_process_interaction_input(interact_input: KeyInput):
if is_instance_valid(possessed_pawn):
possessed_pawn.set_interaction_input(interact_input)
@rpc("any_peer", "call_local")
func server_process_rotation_input(input: Vector2):
@rpc("authority", "call_local")
func server_process_rotation_input(input: Vector2):
if is_instance_valid(possessed_pawn):
possessed_pawn.set_rotation_input(input)
@rpc("any_peer", "call_local")
@rpc("authority", "call_local")
func server_process_clicks(l_action: KeyInput, r_action: KeyInput):
if is_instance_valid(possessed_pawn):
possessed_pawn.set_click_input(l_action, r_action)