Jittery NPC Movement

This moves the NPC with a server side script. However the “movement” is jittery, especially on higher ping. Is there a way to make it smoother? Like a interpolation or something?

local function MovePet(player, pet)
	local lastTime = tick()
	local speed = 1.5 -- Adjust speed as needed

	local function onHeartbeat()
		local currentTime = tick()
		local deltaTime = currentTime - lastTime
		lastTime = currentTime

		if player.Character and player.Character:FindFirstChild("HumanoidRootPart") and pet.PrimaryPart then
			local playerPosition = player.Character.HumanoidRootPart.Position
			local offset = Vector3.new(5, 3, 5)
			local newPosition = playerPosition + offset

			-- Interpolate the pet's position towards the new position with delta time
			local currentPos = pet.PrimaryPart.Position
			local newPos = currentPos:Lerp(newPosition, speed * deltaTime)
			pet:SetPrimaryPartCFrame(CFrame.new(newPos))

			-- Calculate the new rotation for the pet
			local lookRotation = CFrame.new(pet.PrimaryPart.Position, playerPosition)
			local initialRotationOffset = CFrame.Angles(0, math.rad(-90), 0)
			lookRotation = lookRotation * initialRotationOffset

			-- Set the pet's rotation directly
			pet:SetPrimaryPartCFrame(lookRotation)
		end
	end

	-- Connect to Heartbeat event
	local connection
	connection = game:GetService("RunService").Heartbeat:Connect(function()
		onHeartbeat()
	end)
2 Likes

Have you made sure that the server has network ownership over the NPC’s root part? e.g. Humanoid.RootPart:SetNetworkOwner(nil)

If you have done so, there are a few things you could consider but they all have their trade offs.

Depending on the type of game you’re making, and what would be agreeable with its design, you could consider:

  1. Making the pet NPCs client-side only

    • Instantiate only the appropriate pets on each of the clients
    • Make each of the clients update the position of the pets
  2. Custom replication and prediction

    Note: this is a simplified explanation and not completely accurate, recommend you do some research

    See somewhat related example: chickynoid

    • As above, but server-authoritative:
      • In a low ping situation

        • Server controls & determines where the pet should be, or what actions it should take, and sends the data to the client
        • Client attempts to update the pet(s) location
      • In a high ping situation:

        • Take last known packet
        • Predict next location
        • Update the pet location(s)

I haven’t done that yet. What benefit does it give me?

Movement is then being networked on the Server and not on each individual client (nil is Server basically)

1 Like

Check the documentation for more detailed information, available here

Brief summary of network ownership

Roblox attempts to distribute the physics computation between the server and the clients. This means that clients can be assigned physically simulated assemblies, or groups of BasePart instances, to divide the work of the physics step between the server and connected clients.

That’s why, for example, if you’ve ever tried to move an unanchored sphere between more than one player in Roblox:

  • It will be more responsive for whoever owns that instance at that point in time because they’re not awaiting packets from the server to update the transform of the instance, and they don’t have to send their packet off for the action to take effect
  • If you don’t have network ownership of the assembly and experience latency, you’ll note that the instance will move unexpectedly in an attempt to resync the instance on your client

All server-owned, unanchored assemblies are subject to this; but you can toggle the behaviour by using the ::SetNetworkOwner method and setting the assembly’s network owner to nil.

Setting network ownership of the assembly’s root part, or the pet’s HumanoidRootPart in your case, would ensure that the server maintains network ownership of the assembly - this stops it from transferring between players, which can be a source of desync.

The other thing to consider, would be to set the network ownership of the pet to the client that owns it, and then updating the location of the pet on the client. Any changes the network owner would make to its transform, e.g. in the case of your attached script, would be replicated to the server and then sent to the other clients.

Caveat: This does not work on anchored parts, since anchored parts are always owned by the server.