1
\$\begingroup\$

I am making a game that takes place in space and I want you to be able to stay upright in a spaceship even if it rotates. I have this set up: enter image description here

Its a platform and an area that rotates. I have calculated a Vector based on the area's rotation that I use for the gravity and applied it to the cube and the player. It works somewhat but not well. When it rotates the cube slides off and hen the player is on it it doesn't go well. Basically I just take the rotation of the area and add it to the player and it works but not well. I need another way for approaching this.

My Player is a CharacterBody3D and the rotating ground is a RigidBody3D, but I am willing to change them.

\$\endgroup\$
4
  • \$\begingroup\$ Is the player a RigidBody3D or a CharacterBody3D? Do you need this to work for other objects "in the spaceship"? Also, is the ground a StaticBody3D or something else? \$\endgroup\$
    – Theraot
    Sep 8 at 19:52
  • \$\begingroup\$ @Theraot Yes I need for other things to work in a "spaceship". The player is a CharacterBody3D and the platform is a RigidBody3D, but I am willing to change those if neccessary. \$\endgroup\$ Sep 8 at 22:06
  • \$\begingroup\$ A bit of a hack you can try: instead of rotating the ship the player is on, you can rotate the world outside the ship around them, or rotate their view of the world through rendering transformations without even moving things "for real". This can often be much more stable in the physics engine, and avoid jitter/clipping or objects tunneling out of the ship because it "moved out from under them". Done right, it can be indistinguishable to the player — they used this trick in one of the Uncharted games when you're navigating a sinking sea vessel tossing on the waves, if I recall correctly. \$\endgroup\$
    – DMGregory
    Sep 8 at 22:22
  • \$\begingroup\$ Thank you for this advice and for providing an example. This really helped \$\endgroup\$ Sep 9 at 4:13

1 Answer 1

1
\$\begingroup\$

Direction of gravity

Since you might need RigidBody3D to change their direction of gravity… Have the Area3D override gravity (you can set its gravity_space_override, and set its gravity_direction). This direction is global, so you need to update it with the rotation of your Area3D:

Code in the Area3D

gravity_space_override = Area3D.SPACE_OVERRIDE_REPLACE
gravity_direction = global_transform.basis * Vector3.DOWN

Since you might need CharacterBody3D to follow the modified direction of gravity, have it read the gravity from the world space. For example:

Code in the CharacterBody3D

var direct_state := PhysicsServer3D.body_get_direct_state(get_rid())
var total_gravity := direct_state.total_gravity

However, you also need to tell the CharacterBody3D what is the new up which is used to identify what is a wall, a floor, or a ceiling:

up_direction = -total_gravity.normalized()

Then it gets tricky, because presumably you want the CharacterBody3D to rotate so it is standing up according to the new gravity direction.

I do not know how your code looks like, but you will have to review anywhere you have hardcoded Vector3.UP, and consider if it should be up_direction instead. For example, anywhere you are using look_at, you probably need to change it.

Character controllers often assume Vector3.UP is more implicit ways, for example mapping motion input to the horizonal axis, that code might need to be updated too.


Moving inside the spaceship

Up to this point I have only talked about gravity. If the "spaceship" need to actually move it will bring more trouble.

First of all, having the CharacterBody3D is snapping to the floor will take care of situations of the floor moving away from it (at least at slow speeds).

The recommendation for a moving platform is an AnimatableBody3D and with sync_to_physics set to true. The CharacterBody3D when standing on the AnimatableBody3D moves with it.

Thus: make the spaceship an AnimatableBody3D.

But the CharacterBody3D won't change its orientation with whatever it is standing on. For that you need to detect the ground (e.g. use a RayCast3D), identify if it is the rotating platform, get how much it rotated last frame and rotate the CharacterBody3D.

I suggest you decompose the rotation in two parts:

  • Rotation to align with the up_direction (make global_transform.basis * Vector3.UP go to up_direction). Do this one first.
  • Rotation around the up_direction (the same way as the ground does).

Be aware that the CharacterBody3D rotates around its own origin. If you want to have the origin in one place, but rotate around another ("center of mass") you need to handle do the translation that compensate for the difference after you rotated the CharacterBody3D.

To do either of the mentioned rotations you want to get an axis and angle to have the CharacterBody3D rotate.

You might find it useful to use the cross product between vectors to use as axis. Be aware that cross product will be the zero vector if the inputs are either the same direction or opposite. You can use the dot product to discriminate which is the case.

It is also very useful to get an arbitrary perpendicular vector to another vector for these sort of things. This is what I'm currently using for that:

func get_perpendicular(direction:Vector3) -> Vector3:
    if direction.is_zero_approx():
        return Vector3.ZERO

    direction = direction.normalized()
    var vectors:= PackedVector3Array([Vector3.RIGHT, Vector3.UP, Vector3.BACK])
    for vec in vectors:
        var attempt = vec.slide(direction)
        if not attempt.is_equal_approx(Vector3.ZERO):
            return attempt.normalized()

    return Vector3.ONE.slide(direction).normalized()

This will return Vector3.ZERO if the direction given is zero. Otherwise it will try to get a perpendicular vector by taking the X, Y, Z axis vectors and substracting their projection on direction. Since there is no way the direction is perpendicular to all of them, one of them is bound to give you a valid perpendicular vector. As a result the last line shouldn't run ever, but it is there so Godot does not complain that the method does not return, which is fine, since it does not handle pathological cases (i.e. NAN and INF coordinates), you should get an error trying to project on those.

You might also find this Q&A useful: How to compare a vector to a rotation in Godot 4.1?.


High speed

The above solution is bound to break a high speed. While you can try continuous collision detection on the RigidBody3D, the CharacterBody3D will always discrete collision detection.

Furthermore, at long distances you might run into floating point issues.

See Moving player inside of moving spaceship?.

Thus DMGregory suggestion in comments is not unfounded.

You can keep move everything so the "spaceship" is near the origin, by translating the root of the 3D world.

Yet, for rotating everything except your "spaceship", you want to set top_level on the "spaceship" so it is not affected by the transformation of the root of the 3D world (so you would also have to handle its offset translation). When you rotate the 3D world, since, presumably you want the rotation to happen around the "spaceship" you translate so the origin of the 3D world is on the "spaceship", rotate, and translate back.


And yes, it could also be done visually...

Doing it with materials is possible. But this is not as easy in Godot, since it imply making a transformation in the vertex shader for everything and before their regular material. So it can be quite an undertaking to set it up. At least, if you use Shader Global (you define them in project settings) you could control the transformation centrally.

Instead, I'd suggest to use a SubViewports and Camera3Ds trick: Have a SubViewport set to be rendered on top the regular world, and will have own_world_3d set to true. And inside of it you will have the "spaceship", and it will not move at all. To fake the motion, synchronize a Camera3D moving on the regular world with the local Camera3D of the "spaceship" scene plus the pretended motion of the "spaceship" itself.

Although this means that lights from the world won't affect the interior of the "spaceship", so you might want to replicate directional light and any other nearby lights that might affect it... And there will be edge cases (e.g. the light in the world would be inside the ship), but presumably you would have the "spaceship" fly at a safe distance from everything by design.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for this explanation. I will try and implement this right now! \$\endgroup\$ Sep 9 at 3:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .