NPCs get laggy a couple of seconds for no reason

I’m trying to make a system that create a clone and it replay your previous life movement and action

This is the video, you can see some npcs just kinda lagged mid air and some teleported at some point.

my npc local script

local player = game:GetService('Players').LocalPlayer
local RunService = game:GetService('RunService')
local ContextActionService = game:GetService('ContextActionService')
local uis = game:GetService("UserInputService")
 
local jumping = false
local leftValue, rightValue = 0, 0 

local function tick()
	return DateTime.now().UnixTimestampMillis / 1000
end

local startTime = tick()

local cam = workspace.CurrentCamera

local char = player.Character or player.CharacterAdded:Wait()
local hum = char:WaitForChild("Humanoid")
local animator = hum:WaitForChild("Animator")
local hrp = char:WaitForChild("HumanoidRootPart")

local rs = game:GetService("ReplicatedStorage")
local animations = rs:WaitForChild("Animations")
local remote = rs.Remote
local turnRemote = remote.Turn

local animationPack = animations:WaitForChild("Private")
local idleLeft = animationPack:WaitForChild("IdleLeft")
local idleTrackLeft = animator:LoadAnimation(idleLeft)
local idleRight = animationPack:WaitForChild("IdleRight")
local idleTrackRight = animator:LoadAnimation(idleRight)

idleTrackLeft:Play()

local side = ""
char:SetAttribute("Side", "D")

local data = {}

local holdingA = false
local holdingD = false
local holdingM1 = false
local holdingJump = false
local state = "Idle"


local function onLeft(actionName, inputState)
	if inputState == Enum.UserInputState.Begin then
		
		if side == "D" or side == "" then
			idleTrackLeft:Stop()
			idleTrackRight:Play()
			hrp.CFrame = CFrame.new(hrp.CFrame.Position) * CFrame.Angles(0,math.pi*.5,0)
			side = "A"
			char:SetAttribute("Side", "A")
		end
		holdingA = true
		
		leftValue = 1
	elseif inputState == Enum.UserInputState.End then
		leftValue = 0
		holdingA = false
	end
end


 
local function onRight(actionName, inputState)
	if inputState == Enum.UserInputState.Begin then

		if side == "A" or side == "" then
			idleTrackRight:Stop()
			idleTrackLeft:Play()
			hrp.CFrame = CFrame.new(hrp.CFrame.Position) * CFrame.Angles(0,-math.pi*.5,0)
			side = "D"
			char:SetAttribute("Side", "D")
		end
		holdingD = true	
		
		rightValue = 1	
	elseif inputState == Enum.UserInputState.End then		
		rightValue = 0	
		holdingD = false
	end
end
 
local function onJump(actionName, inputState)
	if inputState == Enum.UserInputState.Begin then		
		--table.insert(data,{"Action";tick() - startTime; "dW"})
		jumping = true	
	elseif inputState == Enum.UserInputState.End then	
		--table.insert(data,{"Action";tick() - startTime; "uW"})
		jumping = false	
	end
end
 
local function onUpdate()
	if player.Character and player.Character:FindFirstChild('Humanoid') then		
		if jumping then			
			player.Character.Humanoid.Jump = true		
		end		
		local moveDirection = rightValue - leftValue		
		player.Character.Humanoid:Move(Vector3.new(moveDirection,0,0), false)
	end
end
 
RunService:BindToRenderStep('Control', Enum.RenderPriority.Input.Value, onUpdate)
 
ContextActionService:BindAction('Left', onLeft, true, 'a', Enum.KeyCode.A, Enum.KeyCode.DPadLeft)
ContextActionService:BindAction('Right', onRight, true, 'd', Enum.KeyCode.D, Enum.KeyCode.DPadRight)
ContextActionService:BindAction('Jump', onJump, true, 'w', Enum.KeyCode.W, Enum.KeyCode.ButtonA)



game:GetService("RunService").RenderStepped:Connect(function()

	cam.CameraType = Enum.CameraType.Scriptable

	cam.CFrame = cam.CFrame:Lerp(CFrame.new(hrp.Position + Vector3.new(0, 5, 30), hrp.Position), 0.05)
end)


uis.InputBegan:Connect(function(input, isTyping)
	if isTyping then return end

	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		table.insert(data,{"Action";tick() - startTime; "dM1"})

	end
end)

uis.InputEnded:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		table.insert(data,{"Action";tick() - startTime; "uM1"})
	end
end)



hum.Died:Connect(function()
	print(data)
	remote:WaitForChild("SendData"):FireServer(data)
end)


local startPos = hrp.Position

hum.Jumping:Connect(function(IsJumping)
	if IsJumping then
		table.insert(data,{"Jump";hrp.Position;tick() - startTime})
	end
end)

local moveWait

RunService.RenderStepped:Connect(function()
	for i,v in pairs(workspace:WaitForChild("Platforms"):GetChildren()) do
		if v.Position.Y < hrp.Position.Y-2.5 then
			v.CanCollide = true
		else
			v.CanCollide = false
		end
	end

	--print(tostring(holdingA).."--"..tostring(holdingD))

	if holdingA and not holdingD then
		if state == "MovingRight" then
			table.insert(data, {"Move";hrp.Position;"Right";0})
		elseif state == "Idle" then
			moveWait = tick() - startTime
		end
		
		state = "MovingLeft"
		--print("MovingLeft")
	elseif holdingD and not holdingA then
		if state == "MovingLeft" then
			table.insert(data, {"Move";hrp.Position;"Left";0})
		elseif state == "Idle" then
			moveWait = tick() - startTime
		end
		
		state = "MovingRight"
		--print("MovingRight")
	end

	if holdingA == false and holdingD == false and state ~= "Idle" then
		if state == "MovingRight" then
			table.insert(data, {"Move";hrp.Position;"Right";moveWait})
		elseif state == "MovingLeft" then
			table.insert(data, {"Move";hrp.Position;"Left";moveWait})
		end
		
		state = "Idle"
		--print("idle")
	end

end)

my npc server code

local CD = 0.5

for i,v in pairs(script.Parent:GetDescendants()) do
	if v:IsA("Part") or v:IsA("MeshPart") then
		v:SetNetworkOwner(nil)
	end
end

local rs = game:GetService("ReplicatedStorage")
local Queue = require(rs.Module:WaitForChild("Queue"))

local npcFold = script.Parent
local cloneNumber = npcFold.CloneNumber.Value
local char = npcFold.Parent
local myTeam = char.Parent
local hum = char.Humanoid
local hrp = char.HumanoidRootPart
local animator = hum.Animator

char.PrivateServerScript.Enabled = false
char["2DMovement"].Enabled = false
char.CharacterScript.Enabled = false

local cloneData
if myTeam == workspace.Team1 then
	cloneData = _G.Team1CloneData[cloneNumber]
else
	cloneData = _G.Team2CloneData[cloneNumber]
end

local RunService = game:GetService("RunService")




local ss = game:GetService("ServerStorage")
local module = rs.Module
local fastCast = require(module.FastCastRedux)
local models = ss.Models
local remote = rs.Remote
local animation = rs.Animations.Private

local bullet = models.Bullet

local hum = char.Humanoid
local hrp = char.HumanoidRootPart
local animator = hum.Animator


local caster = fastCast.new()	

local behavior = fastCast.newBehavior()

local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {char; workspace.Platforms; script.Parent.Parent.Parent}

behavior.RaycastParams = params
behavior.Acceleration = Vector3.new(0,0,0)
behavior.AutoIgnoreContainer = true
behavior.CosmeticBulletTemplate = bullet
behavior.CosmeticBulletContainer = workspace.Projectiles
behavior.MaxDistance = 35

caster.LengthChanged:Connect(function(ActiveCast, lastPoint, rayDir, displacement, segmentVelocity, cosmeticBulletObject)

	local newPos = lastPoint + (rayDir * displacement)

	cosmeticBulletObject.Position = newPos

end)

caster.RayHit:Connect(function(ActiveCast, RaycastResult, segmentVelocity, cosmeticBulletObject)

	--print("hit "..RaycastResult.Position.X)
	cosmeticBulletObject:Destroy()

end)

caster.CastTerminating:Connect(function(ActiveCast, a)
	local boolet = ActiveCast.RayInfo.CosmeticBulletObject
	boolet:Destroy()
end)

function shoot(side, pos)
	local dir
	if side == "A" then
		dir = Vector3.new(-50,0,0).Unit
	else
		dir = Vector3.new(50,0,0).Unit
	end
	caster:Fire(pos, dir, 40, behavior)
end


local holdingA = false
local holdingD = false
local holdingM1 = false
local holdingJump = false
local speed = hum.WalkSpeed

local walkTrack = animator:LoadAnimation(animation.Parent.WalkAnim)
local idleRightTrack = animator:LoadAnimation(animation.IdleRight)
local idleLeftTrack = animator:LoadAnimation(animation.IdleLeft)


local canMove = true

local turnPart = Instance.new("Part", workspace)
turnPart.Transparency = 1
turnPart.CanTouch = false
turnPart.CanCollide = false
turnPart.CanQuery = false
turnPart.Anchored = true
turnPart.CFrame = turnPart.CFrame * CFrame.Angles(0,- math.pi / 2,0)
local att1 = Instance.new("Attachment", turnPart)
hrp.AlignOrientation.Attachment1 = att1


-----------------------------------------------MOVEMENT
function moveToPoint(pos, dir)
	canMove = false
	
	if dir == "Left" then--hrp.Position.X >= pos.X then
		local moveCF = CFrame.new(hrp.CFrame.Position) * CFrame.Angles(0,math.pi*.5,0)

		local lookat = CFrame.lookAt(hrp.Position, Vector3.new(hrp.Position.X-10, hrp.Position.Y, 0))
		turnPart.CFrame = lookat

		char:SetAttribute("Side","A")
		hum:Move(moveCF.LookVector)

		idleLeftTrack:Stop()
		idleRightTrack:Play()
		
		task.spawn(function()
			local endL = false
			while task.wait() and not endL do
				if hrp.Position.X <= pos.X then
					hum:Move(Vector3.new(0,0,0))
					canMove = true
					endL = true
				end
			end
		end)
	elseif dir == "Right" then --hrp.Position.X < pos.X then
		local moveCF = CFrame.new(hrp.CFrame.Position) * CFrame.Angles(0,-math.pi*.5,0)

		local lookat = CFrame.lookAt(hrp.Position, Vector3.new(hrp.Position.X+10, hrp.Position.Y, 0))
		turnPart.CFrame = lookat

		char:SetAttribute("Side","D")
		hum:Move(moveCF.LookVector)

		idleLeftTrack:Play()
		idleRightTrack:Stop()

		task.spawn(function()
			local endL = false
			while task.wait() and not endL do
				if hrp.Position.X >= pos.X then
					hum:Move(Vector3.new(0,0,0))
					canMove = true
					endL = true
				end
			end
		end)
	end

end

local startTime = tick()

task.spawn(function()
	for i,v in pairs(cloneData) do
		--print(cloneData[i][2])
		if cloneData[i][1] == "Move" then
			while canMove == false do
				task.wait()
			end

			task.wait(cloneData[i][4] + startTime - tick())

			moveToPoint(cloneData[i][2],cloneData[i][3])
			--print("move")
		end
			
		if cloneData[i][1] == "Action" then
			if cloneData[i][3] == "dM1" then
				task.spawn(function()
					task.wait(cloneData[i][2] + startTime - tick())
					holdingM1 = true
				end)
			end

			if cloneData[i][3] == "uM1" then
				task.spawn(function()
					task.wait(cloneData[i][2] + startTime - tick())
					holdingM1 = false
				end)
			end
		end
		
		
		
	end
end)

-----------------------------------JUMPING
task.spawn(function()
	for i,v in pairs(cloneData) do
		if cloneData[i][1] == "Jump" then
			task.wait(cloneData[i][3] + startTime - tick())
			
			local jumped = false
			local endLoop = false
			while task.wait() and not endLoop do
				hum.Jump = true
				jumped = true
				hum.Jumping:Connect(function(IsJumping)
					if IsJumping then
						endLoop = true
					end
				end)
			end
		end
	end
	
end)

local lookat = CFrame.lookAt(hrp.Position, Vector3.new(10, hrp.Position.Y, 0))
hrp.CFrame = lookat

local currTime = tick()

while task.wait() do
	if holdingM1 then
		if currTime + CD <= tick() then
			currTime = tick()
			
			if char:GetAttribute("Side") == "D" then

				local shootLeftTrack =  animator:LoadAnimation(animation:WaitForChild("ShootLeft"))
				shootLeftTrack:Play()

				shootLeftTrack:GetMarkerReachedSignal("Shoot"):Connect(function()
					shoot(char:GetAttribute("Side"), hrp.Position + Vector3.new(0,1,0))
				end)

			else
				local shootRightTrack =  animator:LoadAnimation(animation:WaitForChild("ShootRight"))
				shootRightTrack:Play()

				shootRightTrack:GetMarkerReachedSignal("Shoot"):Connect(function()
					shoot(char:GetAttribute("Side"), hrp.Position+ Vector3.new(0,1,0))
				end)

			end
			
			
		end
		
	end
	
end

Some explanation for my scripts:

  • In local i record player position every time they turn and also the waiting time if the player stand still.

  • I also record the position and time every time they jump.

  • After player die, i send the recorded data to the server and store it in a global variable.

  • In server script i run 2 while loop together using task.spawn, 1 for looping through the movement and 1 looping through jumping. Ignore the shooting bc im revamping it soon.

  • The problem is not server latency bc it lagged on the server perspective too

1 Like

off topic but is this just clone armies? :sob:

4 Likes

By the way, I think this is just a studio issue. If you go into the real game you probably won’t experience lag. But if you STILL do, then I’d recommend setting the network ownership of those clones to client (Network ownership | Documentation - Roblox Creator Hub)

i experienced this in the real game too, i tested it myself, also why would i want to set the network ownership to client bc they belong to the server

I really don’t use network ownership often but I thought it might help. I hope you find something that will help because I really don’t know how to, not much of a programmer.

Oh wait, there are actually no lag in game, the last time i tested it was just my wifi acting up

edit: nvm it still lags alot

Well I think this is unsolvable, roblox physics is just unreliable