Delaying when trying to replicate motor6D movement from server to client( FPS system)

Here is the topic that i made before ( How to move your gun smoothly along with your cursor in FPS game?) . It turns out, it wasn’t because it doesn’t have spring or anything like that to simulate recoil or something similiar to it. The problem is the server doesn’t replicate everything perfectly to the client like this video has demonstrated:

The question is how would i avoid this delay?
Here is the code:

localscript inside a tool(aka to gun):

repeat

wait()

until game:IsLoaded()

local runservice = game:GetService("RunService")

local player = game.Players.LocalPlayer

local char = player.Character

local coordinate = game:GetService("ReplicatedStorage").coordinate

repeat

wait()

until char.Head

local head = char:WaitForChild("Head")

local leftupperarm = char:WaitForChild("LeftUpperArm")

local rightupperarm = char:WaitForChild("RightUpperArm")

local neck = head:WaitForChild("Neck")

local mouse = player:GetMouse()

local originCFrame = neck.C0

local leftorigin = leftupperarm.LeftShoulder.C0

local rightorigin = rightupperarm.RightShoulder.C0

local tool = script.Parent

local replicatedstorage = game:GetService("ReplicatedStorage")

local reverse = replicatedstorage:WaitForChild("reverse")

local connection

local poseanim = replicatedstorage:WaitForChild("poseanim")

local anim = game:GetService("ReplicatedStorage").anim

local debounce = false

tool.Equipped:Connect(function(mouse)

poseanim:FireServer()

connection = mouse.Move:Connect(function(dt)

if neck then

local mousepos = mouse.Hit.p

local headcframe = head.CFrame.p

local height = mousepos.Y-headcframe.Y

local distance = (mousepos - headcframe).Magnitude

local angle = math.asin(height/distance)

local turn = CFrame.fromAxisAngle(originCFrame.RightVector,angle)

coordinate:FireServer(neck,turn,leftupperarm,rightupperarm,leftorigin,rightorigin,originCFrame)

debounce = false

anim:FireServer(debounce)

end

end)

end)

tool.Unequipped:Connect(function(mouse)

debounce = true

anim:FireServer(debounce)

reverse:FireServer()

connection:Disconnect()

end)

client control( also another localscript but the purpose is to modify the default setting of the player:

repeat

wait()

until game:IsLoaded()

local player = game.Players.LocalPlayer

player.CameraMode = Enum.CameraMode.LockFirstPerson

repeat

wait()

until player.Character

local char = player.Character

local lefthand = char:WaitForChild("LeftHand")

local leftlowerarm = char:WaitForChild("LeftLowerArm")

local leftupperarm = char:WaitForChild("LeftUpperArm")

local righthand = char:WaitForChild("RightHand")

local rightlowerarm = char:WaitForChild("RightLowerArm")

local rightupperarm = char:WaitForChild("RightUpperArm")

local runservice = game:GetService("RunService")

runservice.RenderStepped:Connect(function(dt)

lefthand.LocalTransparencyModifier = 0

leftlowerarm.LocalTransparencyModifier = 0

leftupperarm.LocalTransparencyModifier = 0

righthand.LocalTransparencyModifier = 0

rightlowerarm.LocalTransparencyModifier = 0

rightupperarm.LocalTransparencyModifier = 0

end)

serverscript:

local char
game.Players.PlayerAdded:Connect(function(player)
	char = player.Character 
	if char then
		local animator = Instance.new("Animator")
		animator.Parent = char.Humanoid
	end
end)
local reverse = game:GetService("ReplicatedStorage").reverse
local poseanim = game:GetService("ReplicatedStorage").poseanim
local starterplayer = game:GetService("StarterPlayer")
local loading = starterplayer.StarterPlayerScripts.client.loading
local poseanimation = starterplayer.StarterPlayerScripts.client.poseanim
local a = 0
local debounce = false
poseanim.OnServerEvent:Connect(function(player)
	if player.Character then
		local playeranimator = player.Character.Humanoid.Animator	
		local playerloading = playeranimator:LoadAnimation(loading)
		playerloading:Play()
		playerloading.Stopped:Wait()
		local playerpose = playeranimator:LoadAnimation(poseanimation)
		playerpose:Play()
	end
end)

reverse.OnServerEvent:Connect(function(player)
	if player.Character then
		local playeranimator = player.Character.Humanoid.Animator
		local playerpose
		for i,v in pairs(playeranimator:GetPlayingAnimationTracks()) do
			print(i)
			if v.IsPlaying then
				print(v)
			end
			v.TimePosition = 0
			v:Stop()
		end
	end
end)
local coordinate = game:GetService("ReplicatedStorage").coordinate
coordinate.OnServerEvent:Connect(function(player,neck,turn,leftarm,rightarm,leftori,rightori,origin)
	
	neck.C0 = origin*turn
	leftarm.LeftShoulder.C0 = leftori*turn
	rightarm.RightShoulder.C0 = rightori*turn
end)
local anim = game:GetService("ReplicatedStorage").anim
anim.OnServerEvent:Connect(function(player,debounce)
	if debounce then
		print("god")
		player.Character.Animate.fall.FallAnim.AnimationId = "rbxassetid://507767968"
		player.Character.Animate.jump.JumpAnim.AnimationId = "rbxassetid://507765000"
		player.Character.Animate.run.RunAnim.AnimationId = "rbxassetid://913376220"
		player.Character.Animate.walk.WalkAnim.AnimationId = "rbxassetid://913402848"
		return
	end
	if player.Character then
		player.Character.Animate.fall.FallAnim.AnimationId ="rbxassetid://7394638408"
		player.Character.Animate.jump.JumpAnim.AnimationId = "rbxassetid://7394621028"
		player.Character.Animate.run.RunAnim.AnimationId = "rbxassetid://7393184029"
		player.Character.Animate.walk.WalkAnim.AnimationId = "rbxassetid://0"
	end
end)

This is a brief description about how do i make this system:

The equipped pose( the pose when you are holding the gun) was made by using an animation that loops constantly unlike traditional system that use fake arms to simulate the pose. The hand and the gun follow my cursor by calculating the angle using trigonometry as demonstrated in the code above. Extra information is that i change the default animation into my custom animation in order to make the arms not swinging too much because it would be difficult to aim if the hands swinging so hard while moving.

Note: some people might suggest that i should clone hands in the client side but i don’t want to do this for now

If anyone could help, feel free to give me some advice

2 Likes

If you are going with this route you should clone the motor6D locally in order to prevent the server from interupting with the C0 via replication, (turn on incoming replication lag the issue with your current method as you are relying on the server to change the C0 which will cause a delay)

:

And on the server tween or lerp the C0 in order to make it smoother for other players to see.

1 Like

Can i ask you what is self? I’ve tried to understand and done some debugs based on your advice but it is still pretty confused now. Anyways, i’m trying really hard

OH, it works, kind of. It does replicate but i currently have no way to make sure that it works smoother than before.but this method in my opinion might be really effective. But i have a few question to ask you. How can this way actually replicate it? When i test it, there is a phenomenon that when i change the angle of the original motor6D from the server, only the server see it but the client which is me can’t see it. How does this happen? Also, i don’t really understand why do we need to clone the motor6D in the localscript. You explained that this is for avoiding the conflict between the server and the client but can you pls explain it in more details because i need a clear understand about this. This is how i test it according to your advice. Pls tell me if i am doing it wrong
localscript:

repeat

wait()

until game:IsLoaded()

local player = game.Players.LocalPlayer

local char = player.Character

repeat

wait()

until char:WaitForChild("Head")

local head = char.Head

local mouse = player:GetMouse()

local neck = head:WaitForChild("Neck")

neck.Enabled = false

local neckclone = neck:Clone()

neckclone.Enabled = true

neckclone.Parent = neck.Parent

local origin = neckclone.C0

local sending = game:GetService("ReplicatedStorage").sending

mouse.Move:Connect(function()

local mouseposition = mouse.Hit.p

local start = head.CFrame.p

local distance = (mouseposition-start).Magnitude

local height = mouseposition.Y-start.Y

local turn = CFrame.fromAxisAngle(origin.RightVector,math.asin(height/distance))

neckclone.C0 = origin*turn

sending:FireServer(neck,origin,turn)

end)

server:

local sending = game:GetService("ReplicatedStorage").sending

sending.OnServerEvent:Connect(function(player,neck,origin,turn)

neck.Enabled = true

neck.C0 = origin*turn

end)

Yep you did the neck clone properly.

To understand why we need to clone immediately in the client remove the neck clone and use the normal neck.

You will get a jittering unsmooth effect simple as that.

it literally doesn’t even work when i tried to change it from the client even though Enabled = true. This is the new one: Am i doing it wrong?

repeat

wait()

until game:IsLoaded()

local player = game.Players.LocalPlayer

local char = player.Character

repeat

wait()

until char:WaitForChild("Head")

local head = char.Head

local mouse = player:GetMouse()

local neck = head:WaitForChild("Neck")

neck.Enabled = true

local neckclone = neck:Clone()

neckclone.Enabled = true

neckclone.Parent = neck.Parent

local origin = neckclone.C0

local origin1 = neck.C0

local sending = game:GetService("ReplicatedStorage").sending

mouse.Move:Connect(function()

local mouseposition = mouse.Hit.p

local start = head.CFrame.p

local distance = (mouseposition-start).Magnitude

local height = mouseposition.Y-start.Y

local turn = CFrame.fromAxisAngle(origin.RightVector,math.asin(height/distance))

neck.C0 = origin1*turn

sending:FireServer(neck,origin,turn)

end)

there is no jittering smoothness effect. It just doesn’t work when i tried to change it from the client

You still left the neckclone parented so it’s holding the head constant gotta delete it first.

Proper client code
repeat

	wait()

until game:IsLoaded()

local player = game.Players.LocalPlayer

local char = player.Character or player.CharacterAdded:Wait()

repeat

	wait()

until char:WaitForChild("Head")

local head = char.Head

local mouse = player:GetMouse()

local neck = head:WaitForChild("Neck")

neck.Enabled = true

--local neckclone = neck:Clone()

--neckclone.Enabled = true

--neckclone.Parent = neck.Parent

--local origin = neckclone.C0
local origin = neck.C0

local origin1 = neck.C0

local sending = game:GetService("ReplicatedStorage").sending

mouse.Move:Connect(function()

	local mouseposition = mouse.Hit.p

	local start = head.CFrame.p

	local distance = (mouseposition-start).Magnitude

	local height = mouseposition.Y-start.Y

	local turn = CFrame.fromAxisAngle(origin.RightVector,math.asin(height/distance))

	neck.C0 = origin1*turn

	sending:FireServer(neck,origin,turn)

end)

Anyway here is a video, where I set the incoming network replication lag to 0.5. The jittering lag becomes obvious and clear as the server has to catch up with the head movements:

Oh, i get it now. Let’s move on to the remaining questions

connection = mouse.Move:Connect(function(dt)

For this I don’t really see the delay in the video.

But I do know the only proper way of updating the viewmodel on client is to use RenderStepped to ensure the model doesn’t lag behind the camera (Standard practice for 100% of fps games on roblox)(Or Bind to renderstep similar thing).

https://developer.roblox.com/en-us/api-reference/event/RunService/RenderStepped

No, you are misunderstanding my idea. I mean : **

But i have a few question to ask you. How can this way actually replicate it? When i test it, there is a phenomenon that when i change the angle of the original motor6D from the server, only the server see it but the client which is me can’t see it. How does this happen?

**

Hello, i was wondering if you’re still there?