[SOLVED] TDS enemy movement. Tween sync between server and client

This is my first time asking for help here, so I apologize if I haven’t provided enough information.

  1. What do you want to achieve?
    Trying to achieve smooth and synched enemy tween movement between the server and client

  2. What is the issue?
    The visual model sometimes gets behind after some rotations on the path when tweening the cframe, causing sync issues.

  3. What solutions have you tried so far?

  1. Tried welding visual model to an enemy part but the movement is not smooth.
  2. Tried to tween an instance value in the server, and sync both server and client side movements using *:GetPropertySignalChanged, but the result is same as the first one.

I tried looking for solutions in the DevHub but couldn’t find any. The topics mainly covers client-side tween movement rendering, while on the server positions updates with data (TweenService Plus+ or TweenService V2 mainly topics).

The problem is that I can’t use any these methods, because I rely on parts to detect enemies within a range for towers attacks and other stuff (ZonePlus v3.2.0). That’s why I need to tween on both the server and client side.

This is the current code I have:

Server side:

local Nodes = StraightPoints:GetChildren()

table.sort(Nodes, function(a,b)
	return tonumber(a.Name) < tonumber(b.Name)
end)

-- Movement function
local function Movement(Model, MovementTween)
	local WalkSpeed = EnemyData[Model.name]["Stats"]["WalkSpeed"]
	task.spawn(function() task.wait(1) CS:AddTag(Model.Hitbox, "Enemies") end)
	
	Model:PivotTo(Nodes[1].CFrame) -- Sets position to first node (waypoint)	
	
	for Wp = 1, #Nodes, 1 do
		if Wp ~= #Nodes then
			if Model.Parent == nil then break end
			
			local CurrentWp = Nodes[Wp]
			local NextWp = Nodes[Wp + 1]
			Model:SetAttribute("Node", Wp + 1)

			local Distance = (NextWp.Position - CurrentWp.Position).Magnitude
			local Speed = Distance / WalkSpeed
			
			local PrimaryPart = Model.PrimaryPart
			PrimaryPart:PivotTo(CFrame.lookAt(CurrentWp.Position, NextWp.Position))
			
			local Info = TweenInfo.new(Speed, EasingStyle.Linear, EasingDirection.Out, 0, false, 0)
			local Tween = TS:Create(PrimaryPart, Info, {CFrame = CFrame.new(NextWp.Position) * CFrame.Angles(PrimaryPart.CFrame:ToEulerAnglesYXZ())})
			
			Tween:Play()
			Tween.Completed:Wait()
		else
			Base.UpdateHealth(Model:GetAttribute("Health"))
			Model:Destroy()
		end
	end
end

function Enemy.Spawn(Name, PathType)
	-- Enemy creation:
	local Model = workspace.Part
	--...
	
	--...
	
	Model.Parent = EnemyFolder

	Events.MovementSync:FireAllClients(Model, MovementTween) -- Client tween signal
	coroutine.wrap(Movement)(Model, MovementTween)
end

Client side:

local function Movement(Enemy, Visual)
	local WalkSpeed = EnemyData[Enemy.name]["Stats"]["WalkSpeed"]

	for Wp = 1, #Nodes, 1 do
		if Wp ~= #Nodes then
			if Visual.Parent == nil then break end
			local CurrentWp = Nodes[Wp]
			local NextWp = Nodes[Wp + 1]

			local Distance = (NextWp.Position - CurrentWp.Position).Magnitude
			local Speed = (Distance / WalkSpeed)

			local PrimaryPart = Visual.PrimaryPart
			PrimaryPart:PivotTo(CFrame.lookAt(CurrentWp.Position, NextWp.Position))

			local Info = TweenInfo.new(Speed, EasingStyle.Linear, EasingDirection.Out, 0, false, 0)
			local Tween = TS:Create(PrimaryPart, Info, {CFrame = CFrame.new(NextWp.Position) * CFrame.Angles(PrimaryPart.CFrame:ToEulerAnglesYXZ())})

			Tween:Play()
			Tween.Completed:Wait()
		else
			Visual:Destroy()
		end
	end
end

function Mob.Create(Enemy, MovementTween)
	-- Visual creation:
	local Visual = workspace.Part0
	-- ...

	-- ...

	Visual:PivotTo(Enemy:GetPivot())
	Visual.Parent = VisualsFolder

	Movement(Enemy, Visual, MovementTween)
end

Events.MovementSync.OnClientEvent:Connect(Mob.Create)

1 Like

Thanks to @bigtheangry, the solution is to use one of the Tween services modules (TweenServicePlus or TweenService V2) for replicating tweening on the client to ensure smooth and synchronized movement with the server.

Also, for tower attacks it is recommended to check the distance using the magnitude in a loop (preferably with a heartbeat or while loop with task.wait()), as it to be more performant compared to the module I used (ZonePlus v3.2.0).

Heres the formula for getting the mob cframe on the server side since the mob itself is not moving on the server side (only updates the position when the tween reaches the destination) only on the client side:


local Start_Pos = Part0.Position -- you can use either mob current position or waypont position (not sure if the wp will effect the sync but it seemed fine to me when I did testing)
local End_Pos = Part1.Position -- (Final pos or just next waypoint pos)
local Part_Start_Time  = workspace:GetServerTimeNow()
local Time = 2 -- time to reach end pos, use distance/time to calculate constant speed between wps

local MobCFrame = CFrame.new(Mob_Pos) + (End_Pos - Start_Pos) * math.clamp((workspace:GetServerTimeNow() - Mob_Start_Time)) / Time , 0, 1)
1 Like

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