How to flip VehicleBody3D upright?

Godot Version

4.3

Question

I am making a vehicle game using the default VehicleBody3D class.
In… many circumstances, my vehicles get flipped upside-down.
This is fine, but I want to have a key that will flip them upright.

I tried several approaches:

  • look_at(forward, floor)
  • rotate_object_local( Vector3(0.0, 0.0, 1.0), PI )
  • set_global_rotation(Vector3(0.0, 0.0, 1.0))
  • rotate_z(PI)

The only thing I found that did anything to my vehicle was add_constant_torque()… which typically results in the vehicle flipping end over end for a while, but sometimes does get it upright.

Is there a better way to just set the rotation of a VehicleBody3D 90 or 180 degrees or upright?

I googled some more and found that a PhysicsBody3D (inherited by VehicleBody3d) can be manipulated by telling the PhysicsServer3D to change the physics body’s state.

I ended up doing this:

  • In _ready(): Save the initial transformation of the VehicleBody3D
  • Check for the InputEvent and call the flip_vehicle_upright() function
  • Change the .origin of the saved initial transform to the current transform.origin.
  • Get the RID (resource ID) of this vehicle
  • Tell the Physics Server (PhysicsServer3D) to change the .body_set_state of:
    this vehicle’s RID
    to have a new transform state
    that is the adjusted initial transform state.
  • Also set linear and rotational velocities to 0 for good measure. #This might not be appropriate in all cases.
extends VehicleBody3

var initial_transform : Transform3D

func _ready():
	initial_transform = transform


func _input(event: InputEvent) -> void:
	if event.is_action_pressed("Upright"):
		flip_vehicle_upright()


func flip_vehicle_upright():
	#print(name," DEBUG:  flip vehicle upright")
	initial_transform.origin = transform.origin
	var my_resource_ID = get_rid()
	PhysicsServer3D.body_set_state( \
		my_resource_ID, \
		PhysicsServer3D.BODY_STATE_TRANSFORM, \
		initial_transform)
	linear_velocity = Vector3.ZERO
	angular_velocity = Vector3.ZERO

By saving and using the initial transform, the vehicle will be flipped into the same orientation that it started in when _ready() was called.
I.E. If it was facing “South-West with a 17 degree tilt up”, that’s the orientation the flip_vehicle_upright() function will put it in.

This transform could be tweaked to different angles in code, or set to otherwise known values, but this method works for what I needed (and I’d already spent too many hours messing around with this :sweat_smile:).

2 Likes

EDIT: my other comment is awaiting approval so this won’t make sense until you see that one, but I uploaded a demo project.

And here’s the answer I wrote before I decided to just make the demo:

It sounds like you could use some insight into how vectors work if you want to refine it in the future. This is a really important thing to learn in game dev so I personally think it’s a time investment worth making.

There’s some good info here: Vector math — Godot Engine (stable) documentation in English

It’s extremely useful to learn about things like dot and cross products and what they mean, but for this application you only need to understand how the vectors relate to vertical and the horizontal plane, which is very easy.

For instance, if you can figure out what the foward and up vectors of the vehicle are, you can more easily figure out how to adjust them to an orientation that makes sense. I believe looking at the docs that the vehicle’s forward vector is equal to -transform.basis.z, but it would depend on how the transform is oriented with respect to the vehicle itself, which depends on how you made it.

I personally would figure out which vector points in which direction then create properties for vehicle.RIGHT, vehicle.FORWARD and vehicle.UP that simply retrieve the appropriate vectors from transform.basis. Then once you have those it becomes really easy to learn exactly what the vehicle is doing. If need be you can always create an empty Node3D object that is parented to the vehicle and provides convenient vectors. This is pretty cheap performance-wise.

Then you could take the vehicle.FORWARD vector and project it to the horizontal plane. In this special case it’s very easy to do this by just zeroing out the vertical component (we’ll call Y vertical for now but it depends on your settings) and then normalising the vector afterwards. In the ultra-rare edge case that the vector is perfectly vertical you can just pick an arbitrary world direction.

Then, have the vehicle look at this resultant vector with its up vector defaulting to world up. This should make the vehicle face the direction it was already facing but upright. This would work to find a good teleport direction for the reset. Again if the vehicle transform isn’t facing in a good direction, you can always create an intermediate helper transform. Make the transform look the direction you want, then make the vehicle look at whichever pair of vectors on the helper makes the vehicle point where you want.

If you wanted to do this with a physics force rather than a teleport, just check the vehicle.UP vector and if the UP.y component passes some positive value like >0.5, LERP the force or torque to zero. To prevent the flip mechanic being abused, just disable the function unless UP.y is below this value. Adjust the cutoff value to whatever feels right.

To find the correct direction to flip it in, you could point the torque in the direction of the vehicle’s forward vector, then choose a clockwise or counterclockwise force depending on which is closest to upright already. You would determine this by taking vehicle.RIGHT.y and checking its value. If it’s positive apply a clockwise force around vehicle.FORWARD, otherwise counterclockwise (if this is wrong just flip the values, it’s what I usually do rather than the rigorous maths).

Alternatively, you could apply a force to push the high side of the vehicle “down” relative to the up vector (either take the down vector, or the negative of the up vector). Imagine a big boot kicking down on the vehicle’s running boards to push it over, then adjust the push to be parallel to the vehicle down each frame until it’s righted. If vehicle.RIGHT.y is positive, push on the right side, otherwise the left. This is probably the easiest way.

I like the physics flip idea because it gives the player the ability to shove the vehicle until it’s out of whatever situation flipped it. Sometimes a reset leaves you stuck and there’s not much you can do.

Since the project link hasn’t been approved yet and I don’t know how long that will take, I will also add the script I wrote:

extends Node

# all the objects we need to refer to
@export var body : RigidBody3D
@export var forwardDebugSphere : Node3D
@export var rightDebugSphere : Node3D
@export var upDebugSphere : Node3D
@export var flatForwardDebugSphere : Node3D

# just a few tuning variables
@export var FlibbStrength := 3.0
@export var UnFlibbShoveStrength := 3.5
@export var UnFlibbTorqueStrength := 6.0
@export var debugSphereDistance := 2.5

# 0.5 is arbitrary, but represents a tilt angle of acos(0.5), or 60 degrees, which feels like a reasonable angle
# 0.0 would be 90 degrees
# if the vehicle is tilted less than 60 degrees, the flip functions shouldn't work
@export var VerticalComponentCutoff := 0.5
@export var NoseButtThreshold := 0.9

# this reduces the momentum of the object
# it will keep a small amount, which makes it feel more dynamic imho
# set this to zero to completely kill momentum
@export var MultiplyMomentumOnReset := 0.2
# shifts the vehicle up a bit to prevent clipping into the ground
@export var VerticalShiftOnReset := 0.2

# these getters are wrappers around the basis vectors
# they give you directions relative to the rigidbody object
# this should honestly just be standard in GODOT
# they probably shouldn't go here if you want to use them across your project
# I don't know enough about Godot yet to know where they should go
# but it would be nice to add these getters to every transform for convenience
var UP:
	get:
		return body.basis.y
var FORWARD:
	get:
		return -body.basis.z
var RIGHT:
	get:
		return body.basis.x

# I was using this test in 4 separate places
# so it was time to turn it into a getter
# could also call it "IsUpsideDown"
# but I have picked my branding and I am sticking to it
var IsFlibbed:
	get:
		return UP.y < VerticalComponentCutoff

# these detect if the vehicle has landed on its nose or butt
# it can get stuck if this happens, so we add a little shove
# I didn't take the time to fix this in the torque method
# because I frankly don't like it very much
var IsOnNose:
	get:
		return FORWARD.y < -NoseButtThreshold
var IsOnButt:
	get:
		return FORWARD.y > NoseButtThreshold

# if the right vector has a positive y component, then that's the high side
# otherwise, the left side is high
var RightSideHigh:
	get:
		return RIGHT.y > 0.0

# physics forces are added on physics frames, not render frames
func _physics_process(delta: float) -> void:
	
	# these spheres are to visualise where each of the vectors are in space
	# this was also for me to confirm that the vectors I thought I was getting were in fact correct
	# I added the coloured bars on the body to make absolutely certain
	forwardDebugSphere.position = body.position + FORWARD * debugSphereDistance * 1.5
	rightDebugSphere.position = body.position + RIGHT * debugSphereDistance
	upDebugSphere.position = body.position + UP * debugSphereDistance
	
	# we're just zeroing the vertical component
	# this gets a projection of the forward vector onto the horizontal plane
	var flatForward : Vector3 = Vector3(FORWARD.x, 0, FORWARD.z).normalized()
	flatForwardDebugSphere.position = body.position + flatForward * debugSphereDistance *1.6
	
	
	# let's flibb baybee (platonic)
	# just a random shove from underneath basically
	# Input actions are located in Project -> Project Settings -> Input Map
	if Input.is_action_just_pressed("Flibb"):
		if !IsFlibbed:
			# pick a random direction
			var randomDir = randf_range(0,TAU)
			# make a vector that describes that direction
			var randomForcePos = Vector3(cos(randomDir), 0.0, sin(randomDir))
			# give the body an upwards shove from that direction
			body.apply_impulse(UP*FlibbStrength, randomForcePos)
	
	# we will reset to a flat attitude
	if Input.is_action_just_pressed("UnFlibbReset"):
		# only allow flipping if the vehicle is already flibbed
		# you could remove this check if you want to be able to spam it
		
		if IsFlibbed:
			# we add flatForward to the body position in order to point in a direction
			# look_at operates in global space, not in local
			# no need for an up vector since world up is already default
			body.look_at(body.position + flatForward)
			
			# reduce momentum
			body.linear_velocity = body.linear_velocity * MultiplyMomentumOnReset
			body.angular_velocity = body.angular_velocity * MultiplyMomentumOnReset
			
			# shift it up a bit 
			body.position = body.position + Vector3.UP * VerticalShiftOnReset
	
	# shoves the vehicle from the side
	# this creates a more natural movement than torque
	# because it's what you'd do if you were actually pushing it over
	if Input.is_action_pressed("UnFlibbShove"):
		if IsFlibbed:
			if RightSideHigh:
				# right is the high side, push on the right
				body.apply_force(-UP * UnFlibbShoveStrength, RIGHT)
			else:
				# left is the high side, push on the left
				body.apply_force(-UP * UnFlibbShoveStrength, -RIGHT)
			
			if IsOnNose:
				body.apply_force(-UP*UnFlibbShoveStrength * 0.5, -FORWARD)
			elif IsOnButt:
				body.apply_force(-UP*UnFlibbShoveStrength * 0.5, FORWARD)
	
	# torques the vehicle
	# this is okay, but you can do better imho
	if Input.is_action_pressed("UnFlibbTorque"):
		if IsFlibbed:
			if RightSideHigh:
				# right side is high, do a clockwise torque
				body.apply_torque(FORWARD * UnFlibbTorqueStrength)
			else:
				# left side is high, do a counterclockwise torque
				body.apply_torque(-FORWARD * UnFlibbTorqueStrength)

For a start: add_constant_force/add_constant_torque adds influences that will continue forever. You probably want apply_torque, apply_force or apply_impulse if you just want to hold down the button, or apply a momentary shove.

Also, you need to add the body’s own position to any direction you want it to look_at, because it is looking at a position in world space, not local space.

I actually took the time to make a demo project of the three solutions I came up with, mainly because I am just learning Godot and had to make sure I was telling the truth. This was a learning exercise for me lol.

I named it “Flibby” because my brain is poisoned by late 2000s tech marketing BS and I can no longer be normal. Also though, like Clippy this is just intended to be helpful.

There is copious commentary in the script to explain everything that’s happening.

You case use whatever in this script, it’s just for educational purposes. I release it into the public domain, go nuts.

The reset method works great but is very game-y. The shove method works well, the torque not so great. Both physics-based methods can turn you into a spinning top if you try hard enough. Just hold down S or T then keep hitting F and you’ll see what I mean. You should find a solution to that.

I have no idea if dropbox links will work here, but here is the project: https://www.dropbox.com/scl/fi/ykver41kpn16yddxmfv6i/flibby.7z?rlkey=dvkpskex9zbqh7btmsu97llml&e=2&dl=0

EDIT: Also, apologies for the vehicle shape. I just wanted the quickest thing I could make with an uneven roof and a clear front, and it came out looking like that, and I didn’t care enough to change it.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.