What is the best way to handle walking on a moving ship?

Godot Version

4.7.1

Question

I’m working on a 3D survival game where the player travels between islands by piloting a sky ship.
The world is infinite and the ship is customizable. Very similar to the game Raft, but in the air, and the islands/levels should persist.

The player can jump to other ships, glide and climb. These traversal options are important because they are a fundamental aspect of aerial combat.

The main issues are:

  1. Handling movement inside a ship (There is technically no “inside” or “outside”. The transition must be seamless, so using different scenes wouldn’t work that well).

  2. Mitigating floating point errors (Because the map is infinite, the player will encounter them at some point)

  3. Landing at different places should also be smooth

Real Moving Ships and Origin Rebasing
I was thinking of making the ships actually move and when the player’s position exceeds a threshold, shift the map back to the origin.
I would have to keep track of the “real position” of the player by using floats (they’re 64 bit by default) and not Vector3s and pass that to the chunks system, instead of the node position.
This is more flexible and intuitive. But the issues with moving ships remain.

I’ve tried making ships an AnimatableBody3D and moving them in code:

class_name Ship
extends AnimatableBody3D

@export var speed : float = 20.0

func _physics_process(delta : float) -> void:
	move_forward(delta)

func move_forward(delta : float) -> void:
	var forward := global_basis.z
	var velocity := forward * speed
	global_position += velocity * delta

It makes the player inherit the ship’s velocity automatically and works well for most cases: Such as walking and jumping. However, there are still a lot of side effects.

Climbing doesn’t work, because CharacterBodies only apply the velocity of the AnimatableBody if it’s the floor, not a wall. I was able to fix this to an extent by applying the AnimatableBody’s velocity to the player.

But it’s a partial fix. Climbing itself does work, but during the first frames, it’s possible for the ship to move so fast, that it makes the player detach quickly and stop colliding with the wall.

Same with vaulting, taking cover or other states that use ray casts to make the player “snap” to a position, but that position was intended to be static. The state finds a position, but by the time the character reaches that position, the ship has already left it behind, even if it only takes a few ms.

Foot placement IK breaks because the difference between the previous and current position changes quickly and drastically.

Rigid bodies break at high speeds. They remain on the ship, but interaction with the player stops working.

And ragdolls break partially too. Because I check whether the velocity of the hips bone is stationary in order to make a character stand back up. But this is never true because the velocity of the hips bone is always moving.

Some of these issues would be somewhat easy to fix and some could be pretty challenging (Mainly the ones that involve a position being “left behind”, as it’s inherent to moving ships).

So it makes me wonder if I should go for a “simpler” solution:

Ships that don’t actually move. The world does

Another option is to not actually move any ships and move the world around them. This gets rid of all traversal and floating point issues, but things like landing at a specific island or ships with different velocities become harder. It is less flexible in case I wanted to change how ships move and I don’t know how efficient it is to move the entire map every frame. It might introduce issues with lighting, physics or jitter.

Reparenting the player to the ship
I thought this would be the most reliable option, but once I tried it, I found it to be the worst one. It doesn’t fix any of the issues and the player’s movement seems to be constantly “fighting” the ship’s movement regardless of whether the Ship is a CharacterBody3D or AnimatableBody3D.

I would really appreciate it if someone could guide me in the right direction. Thanks!

Don’t reparent the player. That would fight move_and_slide hard.

AnimatableBody3D ships + origin shift is the flexible one. The bugs you’re hitting are mostly “logic assumed a static world,” not a reason to fake move the whole map.

Fix the systems to be ship-relative as:

  • Climb / vault / cover snaps: store the target as a local offset on the ship, convert to global every physics frame. Don’t cache a world position.
  • While climbing: keep adding the ship’s velocity every frame (not only when it’s the floor).
  • IK: solve from the ship’s transform / previous local foot positions.
  • Ragdoll “am I still?”: use velocity relative to the ship (hips_vel - ship_vel), not world velocity.
  • Rigidbodies at speed: watch tunneling; sometimes easier to freeze/parent them while they’re sitting on the deck.

–For the infinite map: yes, rebase the world when the player gets far. Keep a separate true origin offset for chunks. You don’t need to reinvent positions with raw floats everywhere if the rebase is solid.

–“World moves instead” only really wins if there’s basically one active ship and islands are props around it. Multiple ships with different speeds / jumping between them gets messy fast.

So, keep real moving ships, rebase origin, and make climb/vault/IK/ragdoll speak “ship local.”

Thanks. Yeah, I agree.

It’s the most flexible approach, but it is true that it’ll require many changes.
Another idea could be to use a dummy Node3D as a pivot for the Climb/Vault/Cover snap positions, reparent it to the colliding node and get that position instead. It should work even outside the ship, even though it feels like a hack.

I’ll add more details to the thread if I find another issue or solution

That’s not really a hack; it’s the same “keep the snap in the ship’s space” idea, just letting the scene tree do the transform math. Reparent a marker to whatever you hit, read global_position each frame. Works.