This is my first time asking for help here, so I apologize if I haven’t provided enough information.
-
What do you want to achieve?
Trying to achieve smooth and synched enemy tween movement between the server and client -
What is the issue?
The visual model sometimes gets behind after some rotations on the path when tweening the cframe, causing sync issues. -
What solutions have you tried so far?
- Tried welding visual model to an enemy part but the movement is not smooth.
- 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)