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
Code in the
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
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
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
I suggest you decompose the rotation in two parts:
- Rotation to align with the
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
To do either of the mentioned rotations you want to get an axis and angle to have the
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:
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):
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.
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?.
The above solution is bound to break a high speed. While you can try continuous collision detection on 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
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.