Can I move a BasePart or Model on the server without replicating the movement to the client?

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.

1 Like

Hello, I recently helped someone with a similar issue in the past. Hopefully this post can help: How to make sure that enemies will sync in my tds game? - #3 by BuilderDolphin

Using the code provided here, you are able to get the position of a point traveling along a set of points/segments based on elapsed time and speed. For rendering on the client’s end, you can follow a similar method mentioned in the post. On the server’s end, you don’t even have to do any moving. Just get the position of an object from the function! This also reduces the strain of having to render everything on the server end as well. Hope this helps!

1 Like

To achieve this I believe you can place it in workspace.Camera on the server-side (I think this blocks replication entirely though so you’ll need to recreate the object on the client side too)

this actually works perfect tysm!ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ