Player Movment on Sphere/ Planet

Godot Version

4.2.1

Question

Hey there,
I am having trouble with my project, where I want to move the player character along a planet surface, so that the the local -y Axis is always oriented towards the planet center. So far I got it working if I move along the local z axis of my player model, but for some reason it does not work when I move along the local x axis, can somebody give me a tip whats wrong with my code?

Thanks!

extends RigidBody3D

var move_force = 5
var planet_center = Vector3.ZERO
var gravity_direction

func _physics_process(delta):
	move()

func _integrate_forces(state):
	gravity_direction = (planet_center - global_transform.origin)
	state.transform.basis.y = -gravity_direction

func move():
	if Input.is_action_pressed("move_forward"):
		apply_force(move_force* transform.basis.z,)
	if Input.is_action_pressed("move_back"):
		apply_central_force(move_force* -transform.basis.z)
	if Input.is_action_pressed("move_left"):
		apply_central_force(move_force* transform.basis.x)
	if Input.is_action_pressed("move_right"):
		apply_central_force(move_force* -transform.basis.x)

Unfortunately I cannot upload any Images/videos as a new user :-/

It may have to do with the fact that you’re not updating the rotation as you walk around. You’re uh… setting the vertical translation of the basis to the negative of the gravity force???

1 Like

I bleev Area nodes can do local gravity. It might help you.

Here is a video to clarify what it currently looks like.

As you can see, when I move in the direction of the local z axis, the player always gets rotated so that local y is pointing “up”
So my idea was (and i think thats the way my code works) : Calculate a vector pointing towards the planet center, I called this vector “gravity_direction” and then simply transform the -y axis so it points towards this vector.

Yes, that is what I am using, I should have mentioned that. There is local gravity from an area3D node around the planet, which pulls the player towards the planet center.

Okay. I have not tried that stuff. Looks like it doesn’t correct the rotation then, so you have to.

This stuff is always, for me, trial and error. I wish Godot placed more value in quality of life for non-mathy devs. There are so many funcs they could provide to just do stuff that would take me months to figure out.

I think to do this correctly you will need Quaternion.
This is because if you use euler angles, you will get into a problem where going near the poles makes movement really confusing.

So for your code, it should look something like this…

func update_angle():
	var vertical: Vector3 = player.position.direction_to(planet.position)
	var verticalq: Quaternion = Quaternion.from_euler(Vector3.FORWARD.signed_angle_to(vertical.x, Vector3.RIGHT), Vector3.FORWARD.signed_angle_to(vertical.y, Vector3.UP), Vector3.FORWARD.signed_angle_to(vertical.z, Vector3.FORWARD))
	var prev_angleq: Quaternion = player.quaternion
	var adjustq: Quaternion = (- prev_angleq) * verticalq
	var adjust: Vector3 = adjustq.get_angle()
	player.rotate(adjust)

Of course, you can simplify it, I just split it into easier to read steps.

1 Like

Note that if you are moving on a single plane, you could always use a flat surface and distort it using a vertex shader. (You may have to subdivide meshes if they lack detail if the curvature is strong.)

This way, you don’t have to deal with any of the potential physics issues you may encounter when trying to stick to a sphere with gravity. This is how games like Animal Crossing work.

Something like this: Godot-Runner-Game/shaders/environment.shader at master · hman278/Godot-Runner-Game · GitHub

1 Like

Hi, here is what i used to orient my player ( CharacterBody3D ) to the surface of a planet.

This gets the vector of the gravity, that is it gives you the direction the players feet should be facing ,which is the planets surface.

var gravityVec = planet.transform.origin - player.transgorm.origin.normalized()

Then you put this

if transform.basis.y.normalized().cross(gravityVec ) != Vector3():
	look_at(planet.global_transform.origin, transform.basis.y)
elif transform.basis.x.normalized().cross(gravityVec ) != Vector3():
	look_at(planet.global_transform.origin, transform.basis.x)

then for the controls ,

Forward

	position -= transform.basis.y * speed

Backward

         position += transform.basis.y * speed

you can change position with velocity if you wish.

Turn Left

transform.basis = transform.basis.rotated(gravityVec , -speed)

Turn Right

transform.basis = transform.basis.rotated(gravityVec , speed)

And you apply gravity with this

velocity -= transform.basis.z * gravityForce

3 Likes