Character is glitching when moving for other clients (when not moving fully fowards)

This is a weird glitch that is practically game ruining because it triggers all the time. The only time the character movement is fully smooth for other clients is when the character is moving forwards with no side movement (only holding W).

The video is very low quality, but you can still clearly see the character not moving smoothly backwards like it does on their client.

I don’t know what could cause this. The only thing I can think of is that the camera script is changing the HumanoidRootPart CFrame in a RenderStepped loop. This happens locally through a module script.

2 Likes

Weird thing is, I experienced something really similar with a Rain World Like game I am making where one of the characters would glitch like that when going backwards (Retreating, while when attaking or going forwards it was perfectly fine) while looking at the player. I think (I don’t remember very good) that I solved this issue by making the character walk like 5 studs in the direction it wants to go instead of just 1, but I guess this isn’t the solution for this since its a player.

My best bet is that some character script you coded is making this weird behaviour, or it is something with the Starter Character. So, could you share more information?

3 Likes

What information should I share? :thinking:

  1. There is the RenderStepped loop that changes the HRP CFrame.
camera.CFrame = CFrame.new(raycastResult.Position - rayDirection.unit, targetPart.Position)
  1. I have the characters parented to a folder that is inside of workspace, instead of directly parenting the character to the workspace.
    image

  2. Modifed the idle animation to not have the secondary animation play.

local player = game.Players.LocalPlayer
local character = player.Character
local animateScript = character:WaitForChild("Animate")

animateScript:WaitForChild("idle"):WaitForChild("Animation2").Weight.Value = 0
animateScript:WaitForChild("idle"):WaitForChild("Animation1").Weight.Value = 10
  1. My StarterCharacter setup.

  2. Teleportation method after loading.

	if savedPosition.x then
		root.CFrame = CFrame.new(savedPosition.x,savedPosition.y,savedPosition.z)
	else
		root.CFrame = spawnPart.CFrame
	end
2 Likes

Which of those are server sided and client sided?

1 Like

Client sided: 1,3
Server sided: 2,5
I’m gonna test the movement with camera script disabled. Just takes a while to test since I need to set it up.

So, did it worked? Also, what is the StarterCharacter Primary part? Also, what does the first one (1) do exactly?

Well, the camerascript is actually what makes it possible to walk non-forwards, so yes disabling it does remove the bug. Although it also removes the camerascript of course.

I don’t have a primarypart.

The first one is indeed the camerascript. It sets the CFrame every frame (RenderStepped). I’m assuming it is not replicating to the other clients fast enough or something. Dunno.

1 Like

Set the Starter Character primary part to its root since its just nicer and it avoids Roblox’s weird physics bugs. Also, how does moving the camera make the player be able to go backwards? I’m confused here, could you show more of the Camera code?

1 Like

It’s a bit complicated due to having a raycast that creates a hitbox for the camera.
The core of the script is that it gets the player’s mouse movement and moves the humanoidRootPart (targetPart = humanoidrootpart) and camera based on it.
This is all in a RenderStepped loop, so I am assuming the issue is with runservice replication.

	local delta = UserInputService:GetMouseDelta()

	cameraRotationX = cameraRotationX - delta.Y * cameraSensitivity
	cameraRotationY = cameraRotationY - delta.X * cameraSensitivity

	cameraRotationX = math.clamp(cameraRotationX, -45, 45)


		-- Calculate the new camera position and rotation
		local desiredCameraCFrame = CFrame.new(targetPart.Position)
			* CFrame.Angles(0, math.rad(cameraRotationY), 0)
			* CFrame.Angles(math.rad(cameraRotationX), 0, 0)
			* CFrame.new(cameraOffset)

  --Setting humanoidrootpart cframe and camera cframe...
      targetPart.CFrame = CFrame.new(targetPart.Position) * CFrame.Angles(0, math.rad(cameraRotationY), 0)
        camera.CFrame = desiredCameraCFrame

Ohhh, so I guess you are doing this because you want them to turn around the camera, right? Well, I guess that’s probably why. Try setting the camera to fixed (Or what it is by default) and disabling the camera script, if it glitches that means that it isn’t the camera script fault

yes that is what I did. It is not glitching when the camerascript is turned off.

Was afk but now I am back

Maybe you can try Heart Beat instead or While task.wait()? I’m not really sure. Maybe try other CameraTypes? I dunno, though I think we’re on the right path

I’ve discovered something interesting.
Heartbeat changes nothing,. however changing the loop to a “Stepped” loop also makes it glitchy on the client that the loop fires from.

So the glitchyness happens because the other clients sees the CFrame change as stepped? Not sure what it means by “prior to physics simulation”, but it’s clearly bad for replication.

Honestly, I don’t really know, but maybe I can try to fix it if I know more about how the script works. So, what is the target part and what it does? Could you share more of the code, maybe?

I don’t think that the loop itself is the cause, but rather the method of looping and how changes inside the loop replicates to other clients. Granted I have no clue on how to fix this.

Here is the full script though (without the start variables and cameralock value changes);

-- Update the camera every frame
local function updateCamera()
	
	--Get the player's mouse values
	local delta = UserInputService:GetMouseDelta()

	cameraRotationX = cameraRotationX - delta.Y * cameraSensitivity
	cameraRotationY = cameraRotationY - delta.X * cameraSensitivity

	--Clamp the vertical rotation to prevent flipping
	cameraRotationX = math.clamp(cameraRotationX, -45, 45)
	
	local targetPart = nil

	if isFirstPerson.Value == false then
		
		local plrParts = player.Character:GetDescendants()
		local rod = player.Character:FindFirstChildWhichIsA("Tool")
		for index, child in pairs(plrParts) do
			if child:IsA("Part") and (rod == nil or not child:IsDescendantOf(rod)) then
				child.LocalTransparencyModifier = 0
			end
		end
		player.CameraMaxZoomDistance = 10
		player.CameraMinZoomDistance = 10
		cameraOffset = Vector3.new(1.3, 2, 10)
		targetPart = player.Character:FindFirstChild("HumanoidRootPart")
		
	elseif isFirstPerson.Value == true then
		
		local plrParts = player.Character:GetDescendants()
		local rod = player.Character:FindFirstChildWhichIsA("Tool")
		for index, child in pairs(plrParts) do
			if child:IsA("Part") and (rod == nil or not child:IsDescendantOf(rod)) then
				child.LocalTransparencyModifier = 1
			end
		end
		camera.CFrame = player.Character.Head.CFrame
		player.CameraMaxZoomDistance = 0.5
		player.CameraMinZoomDistance = 0.5
		cameraOffset = Vector3.new(0, 0, 0)
		targetPart = player.Character:FindFirstChild("Head")
		
	end

	if targetPart then
		
		if cameraLocked then
			targetPart.CFrame = CFrame.new(targetPart.Position) * CFrame.Angles(0, math.rad(cameraRotationY), 0)
		end

		-- Adjust the ray origin to be slightly above the character's head
		local rayOrigin = targetPart.Position + Vector3.new(0, 2, 0)

		-- Calculate the new camera position and rotation
		local desiredCameraCFrame = CFrame.new(targetPart.Position)
			* CFrame.Angles(0, math.rad(cameraRotationY), 0)
			* CFrame.Angles(math.rad(cameraRotationX), 0, 0)
			* CFrame.new(cameraOffset)

		-- Raycast to detect if the camera would clip through objects or hit water
		local raycastParams = RaycastParams.new()
		local plrs = game.Players:GetPlayers()
		local tableOfCharacters = {}
		for index,plr in pairs(plrs) do
			if plr.Character then
				table.insert(tableOfCharacters,plr.Character)
			end
		end
		raycastParams.FilterDescendantsInstances = tableOfCharacters
		raycastParams.FilterType = Enum.RaycastFilterType.Exclude

		local rayDirection = (desiredCameraCFrame.Position - rayOrigin).unit * (cameraOffset.Magnitude + 1)  
		local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)

		if raycastResult then
 
			local hitPart = raycastResult.Instance

			if hitPart:IsA("Part") and hitPart.Name:match("Water") then
				local rayDirection = (desiredCameraCFrame.Position - rayOrigin).unit * (cameraOffset.Magnitude)  
				local newCameraPosition = raycastResult.Position - rayDirection.unit * 0.5
				camera.CFrame = CFrame.new(newCameraPosition, targetPart.Position)
			else
				local rayDirection = (desiredCameraCFrame.Position - rayOrigin).unit * (cameraOffset.Magnitude + 1)  
				camera.CFrame = CFrame.new(raycastResult.Position - rayDirection.unit, targetPart.Position)
			end
		else
			camera.CFrame = desiredCameraCFrame
		end
	end
end

-- Connect the updateCamera function to RunService's RenderStepped to run every frame
RunService.RenderStepped:Connect(updateCamera)

-- Function to handle character respawn
local function onCharacterAdded(newCharacter)
	camera.CameraSubject = newCharacter:WaitForChild("Humanoid")
	character = newCharacter
	setCameraLock(true)  -- Default to locked when character respawns
end

-- Connect to character respawn event
player.CharacterAdded:Connect(onCharacterAdded)

-- Expose the setCameraLock function
local module = {}

function module:SetCameraLock(locked)
	setCameraLock(locked)
end

return module

I looked at this code for 9 minutes and I don’t think there’s anything wrong with it, so I guess its the loop. Maybe try alternating and if you can, you can maybe change the code so it works with while task.wait() do and etc. Another possible solution which is kinda a shot on the dark is that maybe you could try to make this partially server-sided so you could send to all clients the cframe of the player, since on their screen it doesn’t glitch?

Yeah I thought about moving the position/rotation of the targetPart to be set on the server, however I have a feeling it would be very laggy to play since you’d need to wait for the remote event.

Will report back with this method.

I mean… It doesn’t hurt to try, I guess. Its the best solution I can think of now… Also, I think I may have another solution (And another shot on the dark): Have you tried to use pivot instead of setting the player by the root? If I remember right, I had a few problems back then with using the cframe of the root, but I’m not sure if it was because it was on the server or not, but you could try!

My conclusion is that it probably isn’t fault of the code itself but instead by some weird shenanigans with clients and methods. Also, if it is possible, did you try to see on the server if the glitch was also present in it?

With this setup I get the same issue now for my client as well. So it is clear that waiting for the server to process the information glitches it out for the client that recieves the information.

			--targetPart.CFrame = CFrame.new(targetPart.Position) * CFrame.Angles(0, math.rad(cameraRotationY), 0)
			replicationEvent:FireServer(targetPart,cameraRotationY)
replicationEvent.OnServerEvent:Connect(function(plr,targetPart,cameraRotationY)
	replicationEvent:FireAllClients(targetPart,cameraRotationY)
end)
replicationEvent.OnClientEvent:Connect(function(targetPart,cameraRotationY)
	targetPart.CFrame = CFrame.new(targetPart.Position) * CFrame.Angles(0, math.rad(cameraRotationY), 0)
end)

PivotTo() brings same results :frowning:

1 Like