Align Position Stuttering On Client But Smooth On Server

I am making a tower defense game and I’m using align position and align orientation to move and rotate the enemies.

All goes well while running the game, but when playing I noticed a stutter when they reached the waypoints on the client.

  • Basically it stutters for about a second once reaching the waypoint and then continues to the next, but when switching to the server it is a smooth as butter.

  • I’ve tried setting the network ownership to the server but it didn’t seem to do anything.

  • searched a good bit but I can’t seem to find the same issue online.

Can anyone help me resolve the issue and also give me suggestions on making my code more performance-friendly?

– << Services >> –
local RS = game:GetService(“ReplicatedStorage”)
local RunS = game:GetService(“RunService”)

– << Variables >> –
local baseHealth = RS[“Game Data”].BaseHealth
local events = RS.Events
local enemyFolder = workspace.Enemies

– << Module >> –
local enemy = {}

– << Configs >> –
local enemyCongifuration = {
– health, speed, rotation, damage
Ducky = {10, 3, 20, 2}
}

– << Functions >> –
function enemy.Move(duck, root, map, folder, speed, rotation, damage, isReturningObj)
local waypoints = map.Waypoints

local alignPos = root:FindFirstChildOfClass(“AlignPosition”)
local rotPos = root:FindFirstChildOfClass(“AlignOrientation”)

local node = root.Node

if waypoints then
for i = 1, #folder:GetChildren() - 1 do

  	local endNode = waypoints[folder.Name][i].WaypointNode
  	if root == nil then
  		return
  	end
  	
  	rotPos.Attachment1 = endNode
  	alignPos.Attachment1 = endNode
  	
  	rotPos.Responsiveness = rotation
  	alignPos.MaxVelocity = speed
  	
  	while RunS.Heartbeat:Wait() do
  		if (node.WorldCFrame.Position - endNode.WorldCFrame.Position).Magnitude < 0.5 then
  			break
  		end
  	end
  end

  local baseNode = map.Base.BasePart.BaseNode
  
  rotPos.Attachment1 = baseNode
  alignPos.Attachment1 = baseNode
  
  while RunS.Heartbeat:Wait() do
  	if (node.WorldCFrame.Position - baseNode.WorldCFrame.Position).Magnitude < 0.5 then
  		break
  	end
  end

  if root == nil then
  	return
  end

  baseHealth.Value = baseHealth.Value - damage
  
  isReturningObj.Value = true
  
  task.wait(2)
  
  if isReturningObj.Value == true then
  	for i, v in ipairs(duck:GetDescendants()) do
  		if v:IsA("MeshPart") then
  			v.Material = Enum.Material.ForceField
  		end
  	end
  end

  local endPoint = map.EndPoints[math.random(1, #map.EndPoints:GetChildren())]
  
  rotPos.Attachment1 = endPoint.EndNode
  alignPos.Attachment1 = endPoint.EndNode
  
  alignPos.MaxVelocity *= 1.5
  
  while RunS.Heartbeat:Wait() do
  	if (node.WorldCFrame.Position - endPoint.EndNode.WorldCFrame.Position).Magnitude < 0.5 then
  		break
  	end
  end

  if root == nil then
  	return
  end

else
warn(“No waypoints found.”)
end
duck:Destroy()
end

function enemy.Spawn(name, quantity, map)
local enemyExists = RS.Enemies:FindFirstChild(name)

if enemyExists then
–moves into workspace
for x = 1, quantity do
task.wait(1)
print(“spawned a duck”)
local folders = map.Waypoints:GetChildren()
local waypointFolder = folders[math.random(1, #folders)]

  	local newEnemy = enemyExists:Clone()

  	local enemyHealth = enemyCongifuration[newEnemy.Name][1]
  	local speed = enemyCongifuration[newEnemy.Name][2]
  	local rotation = enemyCongifuration[newEnemy.Name][3]
  	local damage = enemyCongifuration[newEnemy.Name][4]
  	
  	local isReturning = false --if the enemy is running away then it will be immune to damage

  	local enemyHealthObj
  	local isReturningObj

  	for i, v in pairs(newEnemy:GetChildren()) do
  		if v:IsA("Configuration") then
  			for i, x in pairs(v:GetChildren()) do
  				if x:IsA("NumberValue") and x.Name == "Health" then
  					x.Value = enemyHealth
  					enemyHealthObj = x
  				elseif x:IsA("BoolValue") and x.Name == "IsReturning" then
  					x.Value = isReturning
  					isReturningObj = x
  				end
  			end
  		end
  	end

  	newEnemy.HumanoidRootPart.Position = waypointFolder:FindFirstChild("EnemySpawn").Position
  	newEnemy.Parent = enemyFolder
  	newEnemy.HumanoidRootPart:SetNetworkOwner(nil)

  	enemyHealthObj.Changed:Connect(function(value)
  		if value <= 0 then
  			newEnemy:Destroy()
  		end
  	end)

  	coroutine.wrap(function()
  		enemy.Move(newEnemy, newEnemy.HumanoidRootPart, map, waypointFolder, speed, rotation, damage, isReturningObj)
  	end)()
  end

else
warn("Enemy does not exist: " … name)
end
end

return enemy

create the alignposition on the client, physics replication is notoriously stuttery

wouldn’t that cause issues for like every other player desyncing their position for each player?
By that I mean wouldn’t the position for the enemies be different for each player even by a little bit?

if they have the same target position it should be fine

alright, but how do you suppose I create it on the client, I spawn the characters using a server side script so I’m not quite sure what to do about creating it on the client.
Maybe I just spawn them on the server and move them for each client?

are they only visual? or are the positions being used for some logic? if its only visual then you could just use remote events to tell the client how to create the alignposition.

it’s not really visual only, it’s used to move the enemies, damage the base and end the game.
So I wouldn’t really say it’s visual only, I use the alignPositions to move them.
Any suggestions?

i just woke up so i might be missing a more obvious solution here, but you could:

  1. use an invisible anchor for the server to track its ACTUAL position
  2. move the actual visual enemy on the clients by creating the alignposition for each client
  3. add a check to make sure that the anchor and enemy dont get too far apart (optional)

the invisible anchor would be the enemy’s position for distance calculations, like checking if the enemy is in range of a tower for example. they should only be about as far apart as the velocity multiplied by the player’s ping for each client, which won’t be much in any case.

1 Like

that’s actually a pretty good idea, though how would you suppose moving the anchor for the enemy?
Since I move the humanoidRootPart on the server for my current enemy movement, can I just do the same but visualize it on the client?
Or for example make a part that will be the anchor (it’s absolute position) and make the enemies humanoidRootPart use alignPosition on that to move it?

i was thinking an invisible part that the server places the alignposition on and treats it as the enemy’s position, and the humanoid root part is only ever moved by the client.

whenever the server alignposition’s attachment is set, you would send a remote event to the clients to set their local attachments as well. that way, any stutter in replicating the anchor would be ignored since the client’s visuals are completely decoupled from it.

1 Like

alright so what I’m getting from this is the invisible part is the part where the alignPosition is set and is the part which the humanoidrootpart follows. Though how would we move that invisible part is my question?

the invisible part has an alignposition on the server moving it, replacing the humanoid root part.
the humanoid root part on the client is doing what the server was doing in the original version

server example

local THRESHOLD = 0.5

local Enemy = workspace.Rig
local Path = workspace.Path
local Waypoints = Path:GetChildren()

local SetWaypoint = game.ReplicatedStorage.SetWaypoint

local Anchor = Instance.new("Part")
Anchor.CanCollide = false
Anchor.Parent = workspace

local AlignPosition = Instance.new("AlignPosition", Anchor)
AlignPosition.Attachment0 = Instance.new("Attachment", Anchor)
AlignPosition.MaxVelocity = 2

game.Players.PlayerAdded:Wait()

while true do
	local attachment = Waypoints[math.random(#Waypoints)]
	AlignPosition.Attachment1 = attachment
	SetWaypoint:FireAllClients(attachment)
	repeat task.wait() until (AlignPosition.Attachment1.WorldPosition - AlignPosition.Attachment0.WorldPosition).Magnitude < THRESHOLD
end

client

local Enemy = workspace.Rig
local AlignPosition = Instance.new("AlignPosition", Enemy:WaitForChild("HumanoidRootPart"))
AlignPosition.Attachment0 = Enemy.HumanoidRootPart:WaitForChild("RootAttachment")
AlignPosition.MaxVelocity =2

game.ReplicatedStorage.SetWaypoint.OnClientEvent:Connect(function(attachment)
	AlignPosition.Attachment1 = attachment
end)
1 Like

Thanks you, I’ll be sure to test it out by later. One last question, how did you visualize the attachment lines?

in the model tab
image

1 Like

Yeah I see it now, thank you so much man, you’ve been a huge help.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.