Godot Version
Godot 4.6.3
Hello Godot community,
I’m currently trying to create a 2D road trip game with Hill Climb Racing physics and mechanics, but I’m currently stuck and I tried many solution to find the issue, but it still doesn’t work as I wish. When landing, one of the wheel will just not reset to its rest pose (like in the picture, the rear wheel is supposed to be at the same height as the front one). Does anyone could help me on this problem? I just don’t really like to use AI and things like this, so this is why I went on this forum to see for people that knows a lot better than I do.
(Some part of my comments in script are in French, sorry)
This is the car script
extends RigidBody2D
var wheel_fr: RigidBody2D
var wheel_rr: RigidBody2D
@export var rest_length := 60.0
@export var stiffness := 800.0
@export var damping := 80.0
@export var power := 400.0
@export var max_speed := 40.0
func _physics_process(_delta: float) -> void:
if wheel_fr == null or wheel_rr == null:
return
_apply_suspension($AnchorFR.global_position, wheel_fr)
_apply_suspension($AnchorRR.global_position, wheel_rr)
_handle_input()
func _apply_suspension(origin: Vector2, wheel: RigidBody2D) -> void:
var down := global_transform.y.normalized()
var to_wheel := wheel.global_position - origin
var current_dist : float = to_wheel.dot(down)
var compression : float = rest_length - current_dist
var wheel_vel : float = wheel.linear_velocity.dot(down)
var chassis_vel : float = linear_velocity.dot(down)
var rel_vel : float = wheel_vel - chassis_vel
# Amortissement asymétrique / asymmetric damping :
# - compression rapide (roue monte) → amorti fort / fast compression (wheel up) → strong damping
# - extension (roue descend) → amorti plus doux pour garder le contact sol / extension (wheel lowers) → softer damping to maintain contact with the ground
var damping_factor: float
if rel_vel > 0.0:
# Roue remonte → compression → amorti fort / Wheel lift → compression → firm damping
damping_factor = damping * 2.5
else:
# Roue descend → extension → amorti doux / Wheel comes down → extension → soft landing
damping_factor = damping * 0.8
var force_mag: float = clamp(
compression * stiffness - rel_vel * damping_factor,
-6000.0, 6000.0
)
var force := down * force_mag
apply_central_force(-force)
wheel.apply_central_force(force)
# Réaction du sol / Ground Reaction
var space := get_world_2d().direct_space_state
var query := PhysicsRayQueryParameters2D.create(
origin, origin + down * (rest_length + 20.0)
)
query.exclude = [get_rid(), wheel.get_rid()]
var hit := space.intersect_ray(query)
if not hit.is_empty():
var ground_dist : float = origin.distance_to(hit.position)
var ground_comp : float = clamp((rest_length + 20.0) - ground_dist, 0.0, 20.0)
apply_central_force(-down * ground_comp * stiffness * 0.5)
func _handle_input() -> void:
if Input.is_action_pressed("right"):
apply_torque(-150.0)
if wheel_fr.angular_velocity < max_speed:
wheel_fr.apply_torque(power)
if wheel_rr.angular_velocity < max_speed:
wheel_rr.apply_torque(power)
elif Input.is_action_pressed("left"):
apply_torque(150.0)
if wheel_fr.angular_velocity > -max_speed:
wheel_fr.apply_torque(-power)
if wheel_rr.angular_velocity > -max_speed:
wheel_rr.apply_torque(-power)
I also have this code that initialize the level and the car (the player).
extends Node2D
func _ready() -> void:
var player := $Player
var wheel_fr := $WheelFR
var wheel_rr := $WheelRR
wheel_fr.global_position = player.get_node("AnchorFR").global_position
wheel_rr.global_position = player.get_node("AnchorRR").global_position
_make_groove(player, wheel_fr, player.get_node("AnchorFR").global_position)
_make_groove(player, wheel_rr, player.get_node("AnchorRR").global_position)
player.wheel_fr = wheel_fr
player.wheel_rr = wheel_rr
func _make_groove(player: RigidBody2D, wheel: RigidBody2D, anchor: Vector2) -> void:
var joint := GrooveJoint2D.new()
joint.global_position = anchor
# L'axe du groove suit la rotation du chassis — pas besoin de rotation manuelle
joint.rotation = player.rotation
joint.length = 60.0 # Débattement de la suspension (en pixels)
joint.initial_offset = 1.0 # Minimum imposé par Godot
joint.node_a = player.get_path()
joint.node_b = wheel.get_path()
add_child(joint)
