Implementing LookAt functionality with RigidBody forces

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By djex81

Hi, I’m new to Godot so bare with me. I’ve worked with Unity a lot and decided to give Godot a try. I have a 3D top down game in which the player is moved with WASD and steered with the mouse. With a non-rigid body this was simple to do using the LookAt method to always face the player towards the mouse cursor but since I do not want to simulate the physics myself I am trying to get this working with a RigidBody instead using forces.

I based my code off this example here: RigidBody — Godot Engine (3.1) documentation in English

My code works but it’s not great. Player movement works well but rotation is not great. At points the player will spin for no reason or will move position by moving the mouse when it should only rotate. My code is below:

Vector3 target;  

public override void _Input(InputEvent @event)
{
	if (@event is InputEventMouseMotion eventMouseMotion)
	{
		Vector3 from = camera.ProjectRayOrigin(eventMouseMotion.Position);
		Vector3 to = from + camera.ProjectRayNormal(eventMouseMotion.Position) * rayLength;

		PhysicsDirectSpaceState directState = PhysicsServer.SpaceGetDirectState(camera.GetWorld().GetSpace());
		Godot.Collections.Dictionary result = directState.IntersectRay(from, to);
		
		if (result["collider"] is CSGMesh)
		{
			Vector3 resultVector = (Vector3)result["position"];
			Vector3 xyVector = new Vector3(resultVector.x, Translation.y, resultVector.z);

			target = xyVector;
		}
	}
}

public override void _IntegrateForces(PhysicsDirectBodyState state)
{
	Vector3 upDir = new Vector3(0, 1, 0);
	Vector3 curDir = Transform.basis.Xform(new Vector3(0, 0, 1));
	Vector3 targetDir = (target - Transform.origin).Normalized();
	float rotationAngle = Mathf.Acos(curDir.x) - Mathf.Acos(targetDir.x);

	if (targetDir.z < 0)
	{
		rotationAngle = -rotationAngle;
	}

	state.SetAngularVelocity(upDir * (rotationAngle / state.GetStep()));

	if (Input.IsActionPressed("player_move_forward"))
	{
		AddForce(GetGlobalTransform().basis.z * movementSpeed, -GetGlobalTransform().basis.z);
	}
	else if (Input.IsActionPressed("player_move_backward"))
	{
		AddForce(-GetGlobalTransform().basis.z * movementSpeed, GetGlobalTransform().basis.z);
	}

	if (Input.IsActionPressed("player_move_left"))
	{
		AddForce(GetGlobalTransform().basis.x * movementSpeed, -GetGlobalTransform().basis.x); 
	}
	else if (Input.IsActionPressed("player_move_right"))
	{
		AddForce(-GetGlobalTransform().basis.x * movementSpeed, GetGlobalTransform().basis.x);
	}
}

What it does is a RayCast from the camera down at the mouse location. There is an invisible mesh plane that it will collide with which returns back a Vector3 position. Next I take the x and z component and create a new Vector3 with the y set to the players current y. This is our world target to point our player towards. Now I may be doing this completely wrong so any suggestions a greatly appreciated. I then use the code from the example to rotate the player to face the target Vector3.

Any suggestions to get this working properly or a better method would be appreciated. Also code examples in GDScript or C# will work.

Edit: I will also note that using the example code does not work fully. It will only rotate 180 and not the full 360 and so I had to add in the code below but I feel this isn’t the correct way to make this work.

if (targetDir.z < 0)
 {
     rotationAngle = -rotationAngle;
 }