Godot Version
Godot_4.5.1-stable
Question
I am essentially creating arcade racing-style physics (cars). I am having this problem where, slowly over time, the car will start to rubberband and turn sharper. The video attached showcases this. I am not too sure what the problem is or when it started; I just noticed it now.
This is the script for the car:
extends RigidBody3D
# CREDIT: https://www.youtube.com/watch?v=fe-8J7_WAq0
@export var debug: bool = false
@export var is_player: bool = true
@export var engine_power: float = 1
@export var suspension_rest_dist: float = 0.5
@export var spring_strengh: float = 10
@export var spring_damper: float = 1
@export var wheel_radius: float = 0.33
@export var steering_angle: float = 30.0
@export var front_tire_grip: float = 2.0
@export var rear_tire_grip: float = 2.0
@export var raycast_collisions: Node3D
var accel_input = Input.get_axis("down", "up")
var steering_input
var direction = "up"
var can_turn = true
func _ready() -> void:
if is_player:
Global.car = self
await get_tree().create_timer(2).timeout
#direction = "left"
func _physics_process(delta: float) -> void:
if is_player:
player(delta)
else:
npc(delta)
func player(delta):
accel_input = Input.get_axis("down", "up")
steering_input = Input.get_axis("right", "left")
var steering_rotation = steering_input * steering_angle
var front_right: RayCast3D = $Wheels/FrontRight
var front_left: RayCast3D = $Wheels/FrontLeft
if steering_rotation != 0:
var angle = clamp(front_left.rotation.y + steering_rotation, -steering_angle, steering_angle)
var new_rotation = angle * delta
front_left.rotation.y = lerp(front_left.rotation.y, new_rotation, 0.3)
front_right.rotation.y = lerp(front_right.rotation.y, new_rotation, 0.3)
else:
front_left.rotation.y = lerp(front_left.rotation.y, 0.0, 0.2)
front_right.rotation.y = lerp(front_right.rotation.y, 0.0, 0.2)
#Drifting
rear_tire_grip = 0.2 if Input.is_action_pressed("drift") else 0.5
front_tire_grip = 2 if Input.is_action_pressed("drift") else 1
# Gets direction car is facing
func get_facing_dir():
var facing_direction = global_transform.basis.z
#print((facing_direction - Vector3(1, 0.0, 0.0)).length())
if (facing_direction - Vector3(1, 0.0, 0.0)).length() < 0.1:
return "left"
elif (facing_direction - Vector3(-1, 0.0, 0.0)).length() < 0.1:
return "right"
elif (facing_direction - Vector3(0.0, 0.0, 1)).length() < 0.1:
return "up"
elif (facing_direction - Vector3(0.0, 0.0, -1)).length() < 0.1:
return "down"
func npc(delta):
var directions = {
"left" = [1,1],
"right" = [1,-1],
"up" = [1,0],
"down" = [-1,0],
"stop" = [0,0],
}
accel_input = directions[direction][0]
steering_input = directions[direction][1]
raycasts()
var steering_rotation = steering_input * steering_angle
var front_right: RayCast3D = $Wheels/FrontRight
var front_left: RayCast3D = $Wheels/FrontLeft
if steering_rotation != 0:
var angle = clamp(front_left.rotation.y + steering_rotation, -steering_angle, steering_angle)
var new_rotation = angle * delta
front_left.rotation.y = lerp(front_left.rotation.y, new_rotation, 0.3)
front_right.rotation.y = lerp(front_right.rotation.y, new_rotation, 0.3)
else:
front_left.rotation.y = lerp(front_left.rotation.y, 0.0, 0.2)
front_right.rotation.y = lerp(front_right.rotation.y, 0.0, 0.2)
# Turn left or right
func change_directions(time, new_direction, stop_buffer):
can_turn = false
direction = "up"
await get_tree().create_timer(time).timeout
direction = new_direction
# Buffer to wait for direction to change
await get_tree().create_timer(stop_buffer).timeout
# Wait until direction is changed
var facing_dir = null
while facing_dir == null:
facing_dir = get_facing_dir()
await get_tree().create_timer(0.01).timeout
direction = "up"
# Snap its rotation so it goes straight
await get_tree().create_timer(0.1).timeout
if facing_dir == "up":
global_rotation.y = deg_to_rad(0)
elif facing_dir == "left":
global_rotation.y = deg_to_rad(90)
elif facing_dir == "down":
global_rotation.y = deg_to_rad(180)
elif facing_dir == "right":
global_rotation.y = deg_to_rad(270)
steering_angle = 45
can_turn = true
func raycasts():
for raycast: RayCast3D in raycast_collisions.get_children():
# snap all of the raycasts in place so the do not move
raycast.global_position = Vector3(raycast.global_position.x, 3, raycast.global_position.z)
raycast.global_rotation = Vector3(0, raycast.global_rotation.y, PI/2)
# Checks for collision in front of it
if raycast.name == "RayCast3D90" and raycast.is_colliding() and direction == "up" and can_turn == true:
direction = "down"
await get_tree().create_timer(0.5).timeout
direction = "stop"
await get_tree().create_timer(0.5).timeout
# Get a direction to move in
var directions_can_move = []
for direction_raycast: RayCast3D in raycast_collisions.get_children():
#Right
if direction_raycast.name == "RayCast3D15":
if not direction_raycast.is_colliding():
directions_can_move.append("right")
#Left
elif direction_raycast.name == "RayCast3D165":
if not direction_raycast.is_colliding():
directions_can_move.append("left")
# Choose a random possible direction to move
if directions_can_move:
var chosen_direction = directions_can_move[randi_range(0, directions_can_move.size() - 1)]
if chosen_direction == "left":
change_directions(0.75, "left", 0.5)
elif chosen_direction == "right":
change_directions(0.25, "right", 0.5)
# U-turn
else:
steering_angle = 75
change_directions(0, "left", 1.5)
This is the script for the car’s wheels:
extends RayCast3D
@export var is_front_wheel: bool
@onready var car: RigidBody3D = get_parent().get_parent()
@onready var wheel = $Wheel
@onready var turn_debug = $"../FrontRight/turn_debug"
var previous_spring_length: float = 0.0
func _ready() -> void:
add_exception(car)
func _physics_process(delta: float) -> void:
if is_colliding():
suspension(delta, get_collision_point())
acceleration(get_collision_point())
apply_z_force(get_collision_point())
apply_x_force(delta, get_collision_point())
set_wheel_position(to_local(get_collision_point()).y + car.wheel_radius)
rotate_wheel(delta)
else:
set_wheel_position(-car.suspension_rest_dist)
func apply_x_force(delta, collision_point):
var dir = global_basis.x
var state := PhysicsServer3D.body_get_direct_state(car.get_rid())
var tire_world_vel := state.get_velocity_at_local_position(global_position - car.global_position)
var lateral_vel = dir.dot(tire_world_vel)
var grip = car.rear_tire_grip
if is_front_wheel:
grip = car.front_tire_grip
var max_change = 5.0
var desired_vel_change = -lateral_vel * grip
desired_vel_change = clamp(desired_vel_change, -max_change, max_change)
var x_force = desired_vel_change / delta
car.apply_force(dir * x_force, collision_point - car.global_position)
if car.debug:
DebugDraw3D.draw_arrow(global_position, global_position + (dir * x_force / 20), Color.RED, 0.1, true)
func apply_z_force(collision_point):
var dir = global_basis.z
var state := PhysicsServer3D.body_get_direct_state(car.get_rid())
var tire_world_vel = state.get_velocity_at_local_position(global_position - car.global_position)
var z_force = dir.dot(tire_world_vel) * car.mass / 5
car.apply_force(-dir *z_force, collision_point - car.global_position)
var point = Vector3(collision_point.x, collision_point.y + car.wheel_radius, collision_point.z)
if car.debug:
DebugDraw3D.draw_arrow(point, point + (-dir * z_force / 5), Color.BLUE_VIOLET, 0.1, true)
func set_wheel_position(new_y_position: float):
wheel.position.y = lerp(wheel.position.y, new_y_position, 0.6)
if car.debug:
turn_debug.visible = true
func rotate_wheel(delta):
var dir = car.basis.z
var rotation_direction = 1 if car.linear_velocity.dot(dir) > 0 else -1
wheel.rotate_x(rotation_direction * car.linear_velocity.length() * delta)
func acceleration(collision_point):
if is_front_wheel:
return
var accel_dir = -global_basis.z
var torque = car.accel_input * car.engine_power
var point = Vector3(collision_point.x, collision_point.y + car.wheel_radius, collision_point.z)
car.apply_force(accel_dir * torque, point - car.global_position)
if car.debug:
DebugDraw3D.draw_arrow(point, point + (accel_dir * torque / 20), Color.BLUE, 0.1, true)
func suspension(delta, collision_point):
# The direction the force will be applied
var susp_dir = global_basis.y
var raycast_origin = global_position
var raycast_dest = collision_point
var distance = raycast_dest.distance_to(raycast_origin)
var spring_length = clamp(distance - car.wheel_radius, 0, car.suspension_rest_dist)
var spring_force = car.spring_strengh * (car.suspension_rest_dist - spring_length)
var spring_velocity = (previous_spring_length - spring_length) / delta
var damper_force = car.spring_damper * spring_velocity
var suspension_force = basis.y * (spring_force + damper_force)
previous_spring_length = spring_length
var point = Vector3(raycast_dest.x, raycast_dest.y + car.wheel_radius, raycast_dest.z)
car.apply_force(susp_dir * suspension_force, point - car.global_position)
if car.debug:
#DebugDraw3D.draw_sphere(point, 0.1)
var target = position + Vector3(-position.x + 0.001, suspension_force.y / 20, -position.z)
DebugDraw3D.draw_arrow(global_position, to_global(target), Color.GREEN, 0.1, true)
DebugDraw3D.draw_line_hit_offset(global_position, to_global(position + Vector3(-position.x, -1, -position.z)), true, distance, 0.2, Color.RED, Color.RED)
Again, not really sure if the problem arises from these scripts.
Here is a video of the problem:
Any response is a big help. Thank you!