Physics, State machines, and weirdness

Godot Version

4.4.1

Question

`Hello! im currently working on a First Person controller and im having a weird bug that rotating the camera and pressing jump prevents the jump,

I am simply trying at this moment to get the player to jump approximately 2.3units high, which it does as long as the camera isn’t rotating around the y axis, when it is the player kind of jolts but doesn’t actually jump

the way the camera currently rotates the the player is rotated on its local Y axis to move the camera left and right, and the head node is rotated on its local x axis to move the camera up and down,

moving the camera up and down does not prevent the player from jumping, moving it left and right does though

what Ive tried so far is adding another node to rotate the camera left and right independently from the players hit box but that does not seem to change anything

some additional details, im using the Jolt physics engine and my velocity assignment will look a bit weird but that is because the player is able to walk on walls and ceilings, attached are my node tree and snippets of each bit of code that I believe is part of the problem`

image2

I believe using rotate_object_local() and/or using Vector3.RIGHT may be your problem. Try this:

func _input(event) -> void:
	var look: Vector2
	if event is InputEventMouseMotion:
		look = -event.relative * mouseSens	
	self.rotate_y(look.x)
	head.rotate_x(look.y)
	head.rotation.x = clampf(head.rotation.x, deg_to_rad(-70), deg_to_rad(70))

Either that or this script is attached to your StateMachine and it’s not a Node3D so you’re getting weird things happening when you try to rotate it.

So, after replicating the code example here with my own variables i did need to change my mouse sensitivity from 0.25 to 0.005

I’m not entirely sure as to why, BUT it does seem to have worked! i can now jump while moving my mouse left and right but, the issue I’m having now is while walking on the walls, the camera cannot tilt correctly. to fix this i had to change self.rotate_y(look.x) to self.rotate_object_local(Vector3.UP, look.x) which unfortunately just reverts it back to the original problem which tells me me that the issues specifically seems to be with the rotate_object_local() function.

Would you have any advice on where to go with this new knowledge, my first thought is to add a “neck” node to move the head along the horizontal view and prevent the player from rotating at all but i suspect that will cause more issues down the line when i attempt to add wall running in unless i change the parent of the ray casts.

Edit: I went ahead and added in the neck node and modified some things so the player walks in the correct direction (aka forward facing baised on the necks rotation) and jumping while moving the mouse left and right still eats the jump input

I do have some advice. Create two Node3D nodes at the same level as the things you want to rotate, and apply the changes to them. Then copy the changes over. For whatever reason it works. You might also consider resetting the horizontal and vertical pivots to reflect whatever angle change you’ve made when jumping on a wall or ceiling whenever gravity changes.

Here’s an example from my CameraMount3D addon.

@onready var spring_arm_3d: SpringArm3D = $SpringArm3D
@onready var camera_3d: Camera3D = $SpringArm3D/Camera3D
@onready var horizontal_pivot: Node3D = $"Horizontal Pivot"
@onready var vertical_pivot: Node3D = $"Horizontal Pivot/Vertical Pivot"

func update_rotation() -> void:
	horizontal_pivot.rotate_y(Controller.look.x)
	vertical_pivot.rotate_x(Controller.look.y)
	vertical_pivot.rotation.x = clampf(vertical_pivot.rotation.x,
		deg_to_rad(upwards_rotation_limit),
		deg_to_rad(downwards_rotation_limit)
	)
	apply_rotation()

func apply_rotation() -> void:
	spring_arm_3d.rotation.y = horizontal_pivot.rotation.y
	camera_3d.rotation.x = vertical_pivot.rotation.x

Okay so im providing a screen shot, this feels jank, but it works in the desired and intended way. the code snippet you provided above, allows the player to jump while rotating the camera horizontally, but for some reason the head node (a node3D) which contains the camera node will not rotate vertically at all, even when swapping the vertical pivot rotation to rotate_object_local. what i did find that works is the following:

its very strange but it works as intended and the answer was a combination of a couple of things!
Thank you for your assistance! :glowing_star:

Edit: I was incorrect from the start it turns out, i did not remember to note this in the start of the post, but the jump eating occurs while moving and rotating the camera and jumping at the same time, i forgot to walk and turn and jump while testing most recently and did not notice this untill now, wish i had noticed it 5 minutes ago before i had marked the response as the solution, whoops

1 Like

I think you can unmark it.

So my next thought is you basically need a filter. This is my player.gd code for getting input. I have a Cameras object I created that allows the player to cycle through various cameras like 1st person, 3rd person free look, etc. One is an isometric view. So what I’m doing is changing the direction of movement based on the camera in some cases.

If the camera’s y rotation isn’t 0 (because I rotate the SpringArm on the y-axis.) then I know to take the vector I have and rotate it by the camera’s y rotation.

@onready var cameras: Cameras = $Cameras


var direction := Vector3.ZERO


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

	direction = get_input_direction()
	

func get_input_direction() -> Vector3:
	var camera = cameras.active_camera
	var input_dir := Input.get_vector(GameConstants.INPUT_MOVE_LEFT, GameConstants.INPUT_MOVE_RIGHT,
									GameConstants.INPUT_MOVE_FORWARD, GameConstants.INPUT_MOVE_BACKWARD)
	var input_vector := Vector3(input_dir.x, 0, input_dir.y).normalized()
	if camera is CameraMount3D:
		return camera.horizontal_pivot.global_transform.basis * input_vector
	elif camera.rotation.y != 0.0:
		return input_vector.rotated(Vector3.UP, camera.rotation.y).normalized()
	else:
		return transform.basis * input_vector

What I believe is happening for you is that you are running into what would actually happen in real life when multiple gravity fields collide - they cancel each other out where they intersect. So I believe the solution is to decide which gravity is the current one acting on the player, and then apply the global_transform.basis of that gravity when you want the gravity to switch. I also think you need to go into Properties and set physics/3d/default_gravity = 0.0 (if you haven’t already) and then apply current gravity whichever one that is) at the appropriate time. You may be doing some of this, but I think the transform.basis is the key here.

so ive realized ive left out alot of information so far by accident, im providing snippits of my old input direction code as what you provided is MUCH more compacted additionally im going to provide a snippit of the code that changes gravity.


When attempting the code in your above message, it stopped the ability from going up ramps and attempting to continue forward up the ramp above a 90 degree angle, i managed to fix this by swapping the Vector3.UP to the gravity vector, which now that i think about it shouldve been obvious from the start, but for some odd reason, while moving even just on the ground, rotating the camera and attempting to jump just still is not working i do at this point have no clue as to what could be happening, it does go into the state for a split second, before immediately returning to the walking state

How come you only have 4 ground_normals and not 6?

On your CharacterBody3D there’s some settings that I think will help.

First, for the problem running up things, play with the Floor → Max Angle setting. Lowering it a bit might solve the problem.

Second, I think the solution might actually be to change the up_direction Vector3 for your character when the gravity switches to trueGravity.

Also I believe this might be a bit more compact and achieve the same thing (you’ll have to test it to be sure):

const SNAP := Vector3(0.1,0.1,0.1)

trueGravity = newGravity.snapped(SNAP).normalized

In your if/else, you are defining up to be the opposite of the direction you are jumping. That would be down. Doing this might help:

jumpVector = trueGravity
self.set_up_direction(trueGravity)
character_body_3d.up_direction = trueGravity # Unless this is what the code on the line above does.

Also I think for your input you can just get the input information and then rotate it around to apply to the direction the player is facing at that frame.

So the reason there are 4 raycasts for ground_normals is they are pointed at the ground/players feet in order to well detect the ground, they are the small purple boxes in this picture:

with the max floor angle i already fixed that issue but did adjust it for the time being to 30degrees just in case

the up direction is being applied as -trueGravity because true gravity is while on the floor, a `Vector3(0, 1, 0) and just needs to be inverted to point downward towards the floor, wall, ceiling, whatever the player is standing on at the time,

provided are two more snippits that are the code that rotates the player object around its origin to basically just face up with gravity:

photo_2025-04-24_12-44-51

Ah ok, that makes a lot more sense. Also makes the math make more sense. I was wondering how adding them and dividing by 4 was working. I get it now.

Yeah, this is the logic I’m not getting. If I’m standing still and jumping up, the up vector and the jump direction are both Vector3(UP) which is Vector3(0,1,0). The default gravity vector (physics/3d/default_gravity_vector) is Vector3(0,-1,0).

When you set those two values using trueGravity, you set the up vector and the jump direction in opposite directions.

So let’s say our trueGravity vector is Vector3(1,0,0) (we are on the wall). Our up vector and jump direction should both be Vector3(-1,0,0). You always jump “up” in relation to gravity.

Am I missing something?

I am wondering if you can achieve the same thing by:

PhysicsServer3D.area_set_param(get_viewport().find_world_3d().space, PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR, trueGravity)

So, when calulating the true gravity it is just the sum of the ground_normals / 4 which in the case of on the floor is just simplyfied 1+1+1+1 = 4 / 4 = 1 aka Vector3(0,1,0) aka Vector3.UP which youve already stated you understand

but now that gravity is equal to that of Vector3.UP we need to invert it to point the gravity going down, now im actually confused at my own code now because everything ive ever seen says you need to add negative velocity to jump so you would just take your jump force and multiply it by the inverse of gravity?

when i attempted this it just didnt rotate the player object to face the direction of gravity, not sure why

Ok looking at your normals in the screenshot, they are all pointing up. Vector3(0,1,0)) so the average of all 4 is not Gravity, but Up. So I would rename trueGravity to relativeUp. Then:

var relativeUp = newGravity.snapped(SNAP).normalized #Or your old snapped() code if this didn't work
trueGravity = -relativeUp
jumpVector = relativeUp
self.set_up_direction(relativeUp)

You should just be able to add that on the next line after you alter gravity.

So ive updated the code to now have relative up and i’ll be updateing more as i go along as its not quite correct at the moment with many the states not fixed yet but it operates mostly as it did before

attached is the code for the falling state which gives the player air control as well as gravity at the same time

1 Like

I’m glad to hear it’s working as it did before. Has the bug been eliminated?

unfortuneately no, the jump input still gets ate when turning the camera horizontal and walking

I think it would benefit you to take a step back and simplify the problem. There are way too many variables in the equation right now, which is making it hard to debug the issue. Here’s what I would recommend.

  1. Create a new blank project.
  2. Add a simple character controller with as little as possible. No crazy gravity.
  3. Add in one gravity switch on the ceiling. Code just that so it behaves as you would like.
  4. Add in horizontal direction after that.

What I would expect is that the player would jump and it would act normal. If they say, double jump, they reach the new gravity zone that is opposite. At that point, their velocity is arrested and they are slowly pulled up until the gravity reverses. At the same time, the character model rotates so that when they land, their feet are on the ceiling. (Like a cat always landing on their feet.)

I don’t know what you would expect to happen, but laying it out will help you figure it out. As I wrote that, I was thinking that the way I would code it is to create an Area3D with a CollisionShape3D rectangluar box or cylinder extending from the gravity pad. If the player triggers body_entered for the Area3D, a signal is sent to the body (or a method is called directly on it) that passes in the gravity pad’s relative UP direction and gravity strength. Then let the player process that as the new gravity until a new value is passed - either by entering a new Area3D or getting a body_exited signal to set it back to normal.

Negative velocity to jump is only in 2D. The y coordinates are inverted in 2D vs 3D. I don’t know if this is your problem, but I hope you find that helpful!