Im working on enemy movement for a tower defense game. I had an issue where because I was handling all the movement on the server with tween service on the client the enemy movement looked choppy so i added this event that will tell the clients to tween the enemies model to make it look smooth for them without actually relying on the client to change its position. I also added some logic on the server for moving the enemies that doesnt use the tween service but instead uses runservice.heartbeat signals to make the results more accurate (because using task.wait() isnt always the same wait time) but for some reason it still looks choppy on the client, i believe the issue is that the server movements are overriding the clients tween. Is there some way for me to make it so the server’s movements dont get replicated to the client, at least until i want them to be? (preferably every waypoint it will be replicated/synced with client)
Server Script (the part that handles movement)
local height : number = model:GetAttribute("Height") or 0
local primaryPart = model.PrimaryPart
if primaryPart == nil then warn("Enemy model didnt have a PrimaryPart"); return end
task.spawn(function()
local function moveToWaypoint(i : number, startingDistance : number?)
local lastPoint = waypointsFolder:FindFirstChild(`{i}`)
local nextPoint = waypointsFolder:FindFirstChild(`{i+1}`)
if lastPoint == nil or not lastPoint:IsA("BasePart") then warn(`Waypoint {i} missing or wrong type`); return end
if nextPoint == nil then -- it reached the end
model:Destroy()
return
end
if not nextPoint:IsA("BasePart") then warn(`Waypoint {i+1} is wrong type`); return end
primaryPart.CFrame = CFrame.new(lastPoint.Position.X, lastPoint.Position.Y + height, lastPoint.Position.Z)
local distanceBetweenPoints = (nextPoint.Position - lastPoint.Position).Magnitude
local targetPos = Vector3.new(nextPoint.CFrame.Position.X, nextPoint.CFrame.Position.Y + height, nextPoint.CFrame.Position.Z)
local directionVector = (targetPos - primaryPart.Position).Unit
local totalDistanceTraveled = 0
local heartbeatConnection = nil
heartbeatConnection = RunService.Heartbeat:Connect(function(deltaTime)
local distanceToTravel = self.WalkSpeed*deltaTime + (startingDistance or 0)
startingDistance = 0
local newPosition = primaryPart.Position + directionVector * distanceToTravel
primaryPart.CFrame = CFrame.new(newPosition)
totalDistanceTraveled += distanceToTravel
if totalDistanceTraveled >= distanceBetweenPoints then
assert(heartbeatConnection, "there wasnt a heartbeat connection")
heartbeatConnection:Disconnect()
moveToWaypoint(i+1, totalDistanceTraveled - distanceBetweenPoints)
end
end)
rev_TweenEnemyModel:FireAllClients(primaryPart, targetPos, distanceBetweenPoints, self.WalkSpeed)
end
moveToWaypoint(1)
end)
Client Script (the function that is called when the event is fired)
local function TweenEnemyModel(primaryPart : BasePart?, targetPos : Vector3, distance : number, walkSpeed : number)
if primaryPart == nil then return end
local estimatedTravelTime = distance/walkSpeed
primaryPart.CFrame = CFrame.lookAt(primaryPart.Position, targetPos)
TweenService:Create(primaryPart, TweenInfo.new(estimatedTravelTime, Enum.EasingStyle.Linear), {
CFrame = CFrame.lookAt(targetPos, targetPos + (targetPos - primaryPart.Position).Unit)
}):Play()
end
And it is essential that the movements do happen on the server as I’m going to use the positions of enemies to calculate tower attacks/targeting on the server. and I want there to be some sort of a voice of reason that keeps track of all the enemy positions or else everybody will be seeing different stuff.