Unexpected rotation in plane controls

Godot Version



I am trying to program a simple airplane simulation. I followed the following tutorial:

I have made changes to it in order to be compatible with 4.2.1, the version I am using.

I have a CharacterBody3D with a mesh child and a collision shape child. My script is attached to the CharacterBody3D.

When I accelerate and immediately try to fly straight up everything works as normal. However, if I move left or right, and then attempt to fly straight up, the plane begins spinning along its local z axis. I have mulled over this for a while and all I have been able to figure out is that for some reason, rotation.z does not fully reach 0 on line 66. Instead it simply reaches an extremely low float value (e.g. 0.000233556). Is there some quirk about rotation or the engine that I don’t know?

Here is my code:

extends CharacterBody3D

# Can't fly below this speed
@export var min_flight_speed = 10.0
# Maximum airspeed
@export var max_flight_speed = 30.0

# Turn rate
@export var turn_speed = 0.75
# Climb/dive rate
@export var pitch_speed = 0.5
# Wings "autolevel" speed
@export var level_speed = 3

@export_category("Acceleration and deceleration")
# Throttle change speed
@export var throttle_delta = 30.0
# Acceleration/deceleration
@export var accelertation = 6.0

@export var plane_mesh: MeshInstance3D

# Current speed
var forward_speed = 0.0
# Throttle input speed
var target_speed = 0.0
# Lets us change behavior when grounded
var grounded = false

var turn_input = 0.0
var pitch_input = 0.0

func get_input(delta):
	# Throttle input
	if Input.is_action_pressed("throttle_up"):
		target_speed = min(forward_speed + throttle_delta * delta, max_flight_speed)
	if Input.is_action_pressed("throttle_down"):
		var limit = 0.0 if grounded else min_flight_speed
		target_speed = max(forward_speed - throttle_delta * delta, limit)

	# Turn (roll/yaw) input
	turn_input = 0.0
	if forward_speed > 0.5:
		turn_input -= Input.get_action_strength("roll_right")
		turn_input += Input.get_action_strength("roll_left")
	# Pitch (climb/dive) input
	pitch_input = 0.0
	if not grounded:
		pitch_input -= Input.get_action_strength("pitch_up")
	if forward_speed >= min_flight_speed:
		pitch_input += Input.get_action_strength("pitch_down")

func _physics_process(delta: float) -> void:

	transform.basis = transform.basis.rotated(transform.basis.x, pitch_input * pitch_speed * delta)
	transform.basis = transform.basis.rotated(Vector3.UP, turn_input * turn_speed * delta)

	if grounded:
		rotation.z = 0
		rotation.z = move_toward(rotation.z, turn_input, level_speed * delta)

	# Accelerate/decelerate
	forward_speed = lerp(forward_speed, target_speed, accelertation * delta)
	# Movement is always forward
	velocity = -transform.basis.z * forward_speed

	if is_on_floor():
		if not grounded:
			rotation.x = 0
		velocity.y -= 1
		grounded = true
		grounded = false


Please let me know if I need to provide any other resources. This is my first time posting here.

Thanks in advance!

have you tried


I just tried and it did not fix the issue.

this one is different from the script given in the tutorial
it should be

    if not grounded:
        pitch_input -= Input.get_action_strength("pitch_down")
    if forward_speed >= min_flight_speed:
        pitch_input += Input.get_action_strength("pitch_up")

Swapping that just seems to swap the controls. The issue persists.

Here is a recording of the issue if that helps: Imgur: The magic of the Internet

i just cross-check your code with the original, you sure you converted it right? for example the line on _physics_process function, instead of rotate the body to y direction, you made it to z?

I changed it to Z because rotating on the Y axis didn’t make a lot of sense for what it was supposed to do. Rotating by Y simply rotates it along the yaw more, but the plane is supposed to roll somewhat when turning. Changing it to Y does technically fix the issue, but causes an even bigger problem.

also if you noticed, it rotate the body instead whole plane, did you change that too?

1 Like

That seems to have been my issue. I set up an exported variable to use to manipulate the mesh (since I prefer to do that instead of hardcoding node paths), but forgot to actually use it.

I went over things so many times. Feels dumb that I missed it in retrospect haha.

Thanks so much!

1 Like