How Would I Rotate Player To MoveDirection Reliably Using CFrames?

I’m making a custom character engine, and I’ve been having issues with the player’s rotation for all of the time I’ve been working on this game.

I need to get the player to rotate towards their MoveDirection, and I’ve tried everything to accomplish that - with limited success - since I relied on the unreliable AlignOrientation constraint; the contraint is good on paper but bad in practice, as you can’t control the player’s turning speed very well.

I tried to use CFrames because of this, they allow for more control over the turning speed of the player. However, in my experiences, they make me violently angry and evil the way they cause the player to seize the moment the lay a finger on an incline.

BETTER YET just existing in the first place causes the player to tear reality to shreds like a wet piece of paper; see for yourself :sob:

here’s my poorly written code.

function phys.GroundCheck(self)
	local oldCFrame
	rs.RenderStepped:Connect(function(dt)
		
		local ray = rayfuncts.DownRay(self)
		local orentation
		
		if ray == nil or self.jump_Request == true then
			local MD_Rot = CFrame.lookAt(self.hrp.Position, self.hrp.Position + Vector3.new(self.move_Direction.Unit.X, 0, self.move_Direction.Unit.Z))

			rayfuncts.visualizeRay(self.hrp.Position, self.hrp.Position + Vector3.new(0, -self.humanoid.HipHeight, 0), Color3.fromRGB(0, 0, 255))
			
			if self.move_Direction ~= Vector3.zero then
				orentation = CFrame.lookAt(self.hrp.Position, self.hrp.Position + self.hrp.CFrame.LookVector)
			else
				orentation = CFrame.lookAt(self.hrp.Position, self.hrp.Position + self.hrp.CFrame.LookVector)
			end
			self.hrp.AssemblyAngularVelocity = Vector3.zero
			self.hrp.CFrame = orentation
			self.alignPosition.Enabled = false
			self.grounded = false
			return
		end

		if ray ~= nil and self.jump_Request == false then
			self.grounded = true
			oldCFrame = self.hrp.CFrame
			local offset = self.humanoid.HipHeight
			local hitNormal = ray.Normal
			local currentRightVector = self.hrp.CFrame.RightVector
			local upVector = hitNormal
			local newFacialVector = self.move_Direction
		
			local Rot = CFrame.fromMatrix(self.hrp.Position, currentRightVector, upVector, newFacialVector) + self.hrp.Position
			local appliedRot:CFrame = CFrame.lookAt(self.hrp.Position, self.hrp.Position + Vector3.new(self.move_Direction.Unit.X, 0, self.move_Direction.Unit.Z))

			self.angle = math.deg(math.acos(hitNormal:Dot(Vector3.zAxis)))
			self.normal = ray.Normal
			self.material = ray.Material
			
			self.alignPosition.Enabled = true
			self.alignPosition.Position = ray.Position + self.hrp.CFrame.UpVector * offset
			
			self.hrp.AssemblyAngularVelocity = Vector3.zero

			if self.humanoid.MoveDirection ~= Vector3.zero and self.state ~= 5 and not (self.state == 2 and self.sub_State == 1) then
		
				self.hrp.CFrame = self.hrp.CFrame:Lerp(CFrame.lookAt(self.hrp.Position, self.hrp.Position + Vector3.new(self.humanoid.MoveDirection.X, 0, self.humanoid.MoveDirection.Unit.Z)), self.turn_speed)
				
			elseif (self.state == 2 and self.sub_State == 1) or self.state == 5 then
				
				self.hrp.CFrame = CFrame.lookAt(self.hrp.Position, self.hrp.Position + self.hrp.CFrame.LookVector)
				
			else
				self.hrp.CFrame = CFrame.lookAt(self.hrp.Position, self.hrp.Position + self.hrp.CFrame.LookVector)
				
			end
			
			rayfuncts.visualizeRay(self.hrp.Position, ray.Position, Color3.fromRGB(255,0,0))
		end
	end)
end

I’ve tried and tried and TRIED to get this stupid player rotation under control but I JUST CAN T FIGURE I TOUT IU CANT I CAN T IC CANT HELP HELP EHLPE HPEL

I’ll add more to this post later, I’m writing this down in a rush

Thanks in advance!

If you are trying to acheive a sort of wallstick then I would implore you to check out EgoMoose’s Gravity controller.


As for your problem, you code is very messy and is hard to go through. I would suggest you keeping your code organized so that you can easily identify what is not working. I do not like using the CFrame.lookAt function as that could be unpredictable at times by causing gimble lock which could be what you are dealing with.

You are simply rotating the look vector. Here is a simple script I made doing that.

local RunService = game:GetService("RunService")

local Part = script.Parent

RunService.Heartbeat:Connect(function()
	local angle = os.clock()

	local up = Vector3.yAxis --0, 1, 0

	local forward = Vector3.new(
		math.sin(angle),
		0,
		math.cos(angle)
	)

	local right = forward:Cross(up).Unit --perpendicular axis to the y and z axis

	Part.CFrame = CFrame.fromMatrix(
		Part.Position,
		right,
		up,
		-forward
	)
end)

I would suggest getting a deeper understanding of CFrames if you could, it is easier than it looks.

I’ve seen and tried that Gravity Controller, but I’ve managed to make one on my own. (and that’s not what I’m looking for right now.)

As for the whole gimble lock thing, I’ll have to look into that stuff, and i tried the solution you gave, but it turned the player’s mesh inside out; I’ve seen this issue before in other games and in past posts about this issue, but I don’t know what’s causing it.

And the messy code is something I’m trying to work on (but again, that’s not the issue right now.)

Thank you though, I’ll look into gimble lock.

You have some variables that point to no values. You also do not have any comments to explain your values. This could help with organization.

I have quickly fixed it. My bad. :smiling_face_with_tear:

To avoid gimble lock I would suggest looking into quaternions.

I wouldn’t advise this; if you’re confused by CFrames, trying to reason in quaternions is only going to make things more difficult. As long as you are not converting CFrame rotations to Euler angles and trying to do per-axis math with the Euler values, you should not have gimbal lock issues. You can convert CFrame rotations to axis and angle to avoid gimbal locking while still being able to reason with a scalar angle value when it makes sense to.

I agree with pehq that what most developers really need to master is their understanding of CFrames, how rotation ordering works, and how to properly construct orthogonal bases. A lot of times, for custom orientation stuff, you’ll need to construct an orientation matrix from some known axes (e.g. you know what direction forward or up should be, and you need the other axes). This basically comes down to understanding cross products and how to use CFrame.fromMatrix().

1 Like