I used a raycast (from the car to the ground) to get the normal, then aligned the car with the normal:
if ground_ray.is_colliding():
var n = ground_ray.get_collision_normal()
var xform = align_with_y(car_mesh.global_transform, n)
car_mesh.global_transform = car_mesh.global_transform.interpolate_with(xform, 10.0 * delta)
func align_with_y(xform, new_y):
xform.basis.y = new_y
xform.basis.x = -xform.basis.z.cross(new_y)
return xform.orthonormalized()
This is the entirety of the car script:
extends RigidBody3D
@onready var car_mesh = $CarMesh
@onready var body_mesh = $CarMesh/Body
@onready var right_wheel = $CarMesh/FrontRight
@onready var left_wheel = $CarMesh/FrontLeft
@onready var ground_ray = $CarMesh/GroundRay
@export var steering = 5
@export var body_tilt = 45
@export var acceleration = 10
@export var steering_limit = 1.5
var throttle_input = 0
var steering_input = 0
func _ready():
car_mesh.set_as_top_level(true)
ground_ray.add_exception(self)
func _process(delta):
throttle_input = Input.get_axis("move_back", "move_forward") * acceleration
steering_input = Input.get_axis("turn_right", "turn_left") * deg_to_rad(steering)
right_wheel.rotation.y = (steering_input * 2)
left_wheel.rotation.y = (steering_input * 2) + deg_to_rad(180)
func _physics_process(delta):
mesh_to_ball()
if not ground_ray.is_colliding():
return
if linear_velocity.length() > steering_limit:
var new_basis = car_mesh.global_transform.basis.rotated(car_mesh.global_transform.basis.y, steering_input * steering)
car_mesh.global_transform.basis = car_mesh.global_transform.basis.slerp(new_basis, steering * delta)
car_mesh.global_transform = car_mesh.global_transform.orthonormalized()
var t = -steering_input * linear_velocity.length() / 15
body_mesh.rotation.z = lerp(body_mesh.rotation.z, t, body_tilt * delta)
apply_central_force(-car_mesh.global_transform.basis.z * throttle_input)
if ground_ray.is_colliding():
var n = ground_ray.get_collision_normal()
var xform = align_with_y(car_mesh.global_transform, n)
car_mesh.global_transform = car_mesh.global_transform.interpolate_with(xform, 10.0 * delta)
func mesh_to_ball():
car_mesh.global_transform.origin = global_transform.origin
car_mesh.position = position + Vector3.DOWN
func align_with_y(xform, new_y):
xform.basis.y = new_y
xform.basis.x = -xform.basis.z.cross(new_y)
return xform.orthonormalized()
How to I get it so that it slides down in line with where the car is pointed?