Model Shakes when moved long distance - Godot

Viewed 56

I am making a space game in Godot and whenever my ship is a big distance away from (0,0,0) every time I move the camera or the ship, it shakes violently. Here is my code for moving the ship:

extends KinematicBody

export var default_speed = 500000
export var max_speed = 5000
export var acceleration = 100
export var pitch_speed = 1.5
export var roll_speed = 1.9
export var yaw_speed = 1.25
export var input_response = 8.0

var velocity = Vector3.ZERO
var forward_speed = 0
var vert_speed = 0
var pitch_input = 0
var roll_input = 0
var yaw_input = 0
var alt_input = 0
var system = "System1"

func _ready():
    look_at(get_parent().get_node("Star").translation, Vector3.UP)
func get_input(delta):
    if Input.is_action_pressed("boost"):
        max_speed = 299792458
        acceleration = 100
    else:
        max_speed = default_speed
        acceleration = 100
    if Input.is_action_pressed("throttle_up"):
        forward_speed = lerp(forward_speed, max_speed, acceleration * delta)
    if Input.is_action_pressed("throttle_down"):
        forward_speed = lerp(forward_speed, 0, acceleration * delta)
    pitch_input = lerp(pitch_input, Input.get_action_strength("pitch_up") - Input.get_action_strength("pitch_down"), input_response * delta)
    roll_input = lerp(roll_input, Input.get_action_strength("roll_left") - Input.get_action_strength("roll_right"), input_response * delta)
    yaw_input = lerp(yaw_input, Input.get_action_strength("yaw_left") - Input.get_action_strength("yaw_right"), input_response * delta)
        
func _physics_process(delta):
    get_input(delta)
    transform.basis = transform.basis.rotated(transform.basis.z, roll_input * roll_speed * delta)
    transform.basis = transform.basis.rotated(transform.basis.x, pitch_input * pitch_speed * delta)
    transform.basis = transform.basis.rotated(transform.basis.y, yaw_input * yaw_speed * delta)
    transform.basis = transform.basis.orthonormalized()
    velocity = -transform.basis.z * forward_speed * delta
    move_and_collide(velocity * delta)

func _on_System1_area_entered(area):
    print(area, area.name)
    system = "E"

func _on_System2_area_entered(area):
    print(area, area.name)
    system = "System1"

Is there any way to prevent this from happening?

1 Answers

First of all, I want to point out, that this is not a problem unique to Godot. Although other engines have automatic fixes for it.

This happens because the precision of floating point numbers decreases as it goes away form the origin. In other words, the gap between one floating number and the next becomes wider.


The issue is covered in more detail over the game development sister site:


Godot uses single precision. Support for double precision has been added in Godot 4, but that just reduces the problem, it does not eliminate it.

The general solution is to warp everything, in such way that the player is near the origin again. So, let us do that.

We will need a reference to the node we want to keep near the origin. I'm going to assume which node it is does not change during game play.

export var target_node_path:NodePath


onready var _target_node:Spatial = get_node(target_node_path)

And we will need a reference to the world we need to move. I'm also assuming it does not change. Furthermore, I'm also assuming the node we want to keep near the origin is a child of it, directly or indirectly.

export var world_node_path:NodePath


onready var _world_node:Node = get_node(target_node_path)

And we need a maximum distance at which we perform the shift:

export var max_distance_from_origin:float = 10000.0

We will not move the world itself, but its children.

func _process() -> void:
    var target_origin := _target_node.global_transform.origin
    if target_origin.length() < max_distance_from_origin:
        return

    for child in _world_node.get_children():
        var spatial := child as Spatial
        if spatial != null:
            spatial.global_translate(-target_origin)

Now, something I have not seen discussed is what happens with physics objects. The concern is that The physics server might be trying to move them in the old position (in practice this is only a problem with RigidBody), and it will overwrite what we did.

So, if that is a problem… We can handle physic objects with a teleport. For example:

func _process() -> void:
    var target_origin := _target_node.global_transform.origin
    if target_origin.length() < max_distance_from_origin:
        return

    for child in _world_node.get_children():
        var spatial := child as Spatial
        if spatial != null:
            var body_transform := physics_body.global_transform
            var new_transform := Transform(
                body_transform.basis,
                body_transform.origin - target_origin
            )
            spatial.global_transform = new_transform
            var physics_body := spatial as PhysicsBody # Check for RigidBody instead?
            if physics_body != null:
                PhysicsServer.body_set_state(
                    physics_body.get_rid(),
                    PhysicsServer.BODY_STATE_TRANSFORM,
                    new_transform
                )

But be aware that the above code does not consider any physics objects deeper in the scene tree.

Related