Tower defence enemies lagging behind

yeah i know about that. So should i send the tween through remote event?

2 Likes

Send the tween details through the remote event, like how long the tween takes and what enemy to tween. Then, on the client, tween the character.

If you ever need help, feel free to ask.

2 Likes

so the client moves the enemies. So does the client send a remote event back to delete the enemy when it reaches the end?

2 Likes

No, just add a wait on the server and make sure to set the position on the server every time the tween finishes (just wait until the tween should end, no need for further client communication).

3 Likes

so… the remote function cant send the model?

heres the code:

ReplicatedStorage.moveenemies.OnClientInvoke = function(model,distance,goal)
	print("clientmodel: ".. tostring(model:GetFullName()))
	print("clientdistance: "..tostring(distance))
	print("clientgoal: "..tostring(goal))
	local tweeninfo = TweenInfo.new(distance,Enum.EasingStyle.Linear)
	local tween = TweenService:Create(model.HumanoidRootPart,tweeninfo,goal)
	tween:Play()
	tween.Completed:Wait()
end
for i,v in pairs(Players:GetPlayers()) do
			moveremote:InvokeClient(v,model, distance / enemySpeed ,movementgoal)
		end
1 Like

Use a RemoteEvent instead and use :FireAllClients.

2 Likes

Okay, I got the remote function working… turns out the model wasn’t loading fast enough. It’s still a bit laggy tho.

1 Like

Actually I’ll switch to remote events. I just realized that remote functions are extremely insecure. My one concern is that the client could lag badly, and the server might just teleport the enemy to the next node, before the client finishes the tween. Also, could you explain your time scaling code, because the main reason I skipped tweens is that I thought scaling the time would take too long.

1 Like

It’s basic physics. Distance / Speed = Time

So you can just wait the amount to time you send over.

2 Likes

How would I go about changing the enemy speeds while the enemy’s are moving? Because that’s one of the last things the humanoid system had that the tweeting system didn’t I thought about sending another remote to cancel the tween, but I couldn’t reliably find the tween that the client was using for an specific enemy. A little help here? @Katrist

1 Like

Tweens automatically are cancelled when another tween (of the same properties) is played onto the object. You don’t have to manually do :Cancel.

2 Likes

wait but how would i cancel the task.wait() teleport on the server? should i try putting everything in a coroutine and cancel that?

1 Like

Smart idea. You can also use task.spawn and task.cancel because it may be easier to work with.

2 Likes

i tried doing the coroutine method, and the client cant tween the enemy properly. It tries to go to the first node, and the server teleports it back to the node its supposed to be. And than it works fine.

--!native
local module = {}
local ServerStorage = game:GetService("ServerStorage")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local PhysicsService = game:GetService("PhysicsService")
local moveremote = ReplicatedStorage:WaitForChild("tweenenemy")
local Players = game:GetService("Players")

module.spawn = function(name:string, waypointfolder:Folder, exit, spawns:CFrame, boss:BoolValue)
	local offset = math.random(-1,1)
	
	local model: Model = ServerStorage.Enemies:FindFirstChild(name):Clone()
	model:PivotTo(spawns + Vector3.new(offset,0,0))

	if boss then
		ReplicatedStorage.AddHealthBar:FireAllClients(name,model.Health.Value,model.Health.Value)
	end
	--for _,parts:BasePart in pairs(model) do
	--	if parts:IsA("BasePart") then
	--		parts.CollisionGroup = "Enemies"
	--	end
	--end
	model.Parent = Workspace:FindFirstChild(ReplicatedStorage.Map.Value).enemies
	--local health = model.health
	--health:GetPropertyChangedSignal("Value"):Connect(function()
	--	if health.Value == 0 then
	--		model:Destroy()
	--	end
	--end)

	model.PrimaryPart:SetNetworkOwner(nil)

	local enemySpeed = model.speed.value
	--model.speed:GetPropertyChangedSignl("Value"):Connect(function()
	--	enemySpeed = model.speed.Value
	--end)
	local orientation, size = model:GetBoundingBox()

	for index = 1, #waypointfolder:GetChildren() do
		local node = waypointfolder[index]
		local finished = false
		local distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
		local movethread = coroutine.create(function()
			local movementgoal = {
				CFrame = CFrame.lookAlong(node.Position + Vector3.new(offset, size.Y / 2, 0), model.PrimaryPart.CFrame.LookVector)
			}
			ReplicatedStorage.tweenenemy:FireAllClients(model,distance / enemySpeed,movementgoal)
			task.wait(distance/enemySpeed)
			model.PrimaryPart.CFrame = CFrame.lookAlong(node.Position + Vector3.new(offset, size.Y / 2, 0), model.PrimaryPart.CFrame.LookVector)
			finished = true
		end)
		coroutine.resume(movethread)
		local speed:IntValue = model:WaitForChild("speed")

		speed:GetPropertyChangedSignal("Value"):Connect(function()
			local changedthread = nil
			print("recalculating tween")
			coroutine.close(movethread)
			enemySpeed = model.speed.value
			distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
			print("newspeed: "..tostring(enemySpeed).." ".."newdistance: "..tostring(distance).." ".."newtime: ".." "..tostring(distance / enemySpeed))
			changedthread = coroutine.create(function()
				print("updating tweens")
				local movementgoal = {
					CFrame = CFrame.lookAlong(node.Position + Vector3.new(offset, size.Y / 2, 0), model.PrimaryPart.CFrame.LookVector)
				}
				ReplicatedStorage.tweenenemy:FireAllClients(model,distance / enemySpeed,movementgoal)
				task.wait(distance/enemySpeed)
				model.PrimaryPart.CFrame = CFrame.lookAlong(node.Position + Vector3.new(offset, size.Y / 2, 0), model.PrimaryPart.CFrame.LookVector)
				finished = true
			end)
			print(coroutine.status(changedthread))
			coroutine.resume(changedthread)
		end)
		repeat task.wait() until finished == true
		
		local rotationgoal = {
			CFrame = CFrame.lookAlong(model.PrimaryPart.Position, node.CFrame.LookVector)
		}
		ReplicatedStorage.tweenenemy:FireAllClients(model,0.05,rotationgoal)
		task.wait(0.06)
		if not model then
			break
		end
		model.PrimaryPart.CFrame = CFrame.lookAlong(model.PrimaryPart.Position, node.CFrame.LookVector)
	end
	
	if model then
		local node = exit

		local distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude

		local movmentgoal = {
			CFrame = CFrame.lookAlong(node.Position + Vector3.new(offset, size.Y / 2, 0), model.PrimaryPart.CFrame.LookVector)
		}
		ReplicatedStorage.tweenenemy:FireAllClients(model,distance/ enemySpeed,movmentgoal)
		task.wait(distance/enemySpeed)
		ReplicatedStorage.BaseHealth.Value = math.clamp(ReplicatedStorage.BaseHealth.Value - model.Health.Value,0,ReplicatedStorage.MaxBaseHealth.Value)
		model:Destroy()
	end
end

return module

type or paste code here

hers a video:

you can see the enemy glitching around and then teleporting back.

1 Like

You forgot to recalculate the distance. You also forget to disconnect the :GetPropertyChangedSignal event.

Code:

--!native
local module = {}
local ServerStorage = game:GetService("ServerStorage")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local PhysicsService = game:GetService("PhysicsService")
local moveremote = ReplicatedStorage:WaitForChild("tweenenemy")
local Players = game:GetService("Players")

module.spawn = function(name:string, waypointfolder:Folder, exit, spawns:CFrame, boss:BoolValue)
	local offset = math.random(-1,1)

	local model: Model = ServerStorage.Enemies[name]:Clone()
	model:PivotTo(spawns + Vector3.new(offset,0,0))

	if boss then
		ReplicatedStorage.AddHealthBar:FireAllClients(name,model.Health.Value,model.Health.Value)
	end
	--for _,parts:BasePart in pairs(model) do
	--	if parts:IsA("BasePart") then
	--		parts.CollisionGroup = "Enemies"
	--	end
	--end
	model.Parent = Workspace:FindFirstChild(ReplicatedStorage.Map.Value).enemies
	--local health = model.health
	--health:GetPropertyChangedSignal("Value"):Connect(function()
	--	if health.Value == 0 then
	--		model:Destroy()
	--	end
	--end)

	model.PrimaryPart:SetNetworkOwner(nil)
	
	local speed:IntValue = model:WaitForChild("speed")
	--model.speed:GetPropertyChangedSignl("Value"):Connect(function()
	--	enemySpeed = model.speed.Value
	--end)
	local orientation, size = model:GetBoundingBox()

	for index = 1, #waypointfolder:GetChildren() do
		local node = waypointfolder[index]
		local finished = false
		local distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
		local enemySpeed = speed.Value

		local movethread = task.spawn(function()
			local movementgoal = {
				CFrame = CFrame.lookAlong(node.Position + Vector3.new(offset, size.Y / 2, 0), model.PrimaryPart.CFrame.LookVector)
			}
			
			ReplicatedStorage.tweenenemy:FireAllClients(model,distance / enemySpeed,movementgoal)
			task.wait(distance/enemySpeed)
			model.PrimaryPart.CFrame = movementgoal.CFrame
			finished = true
		end)

		local speedChanged = nil

		speedChanged = speed.Changed:Connect(function(enemySpeed)
			if movethread then
				task.cancel(movethread)
				movethread = nil
			end

			distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
			print("newspeed: "..tostring(enemySpeed).." ".."newdistance: "..tostring(distance).." ".."newtime: ".." "..tostring(distance / enemySpeed))

			movethread = task.spawn(function()
				print("updating tweens")
				local movementgoal = {
					CFrame = CFrame.lookAlong(node.Position + Vector3.new(offset, size.Y / 2, 0), model.PrimaryPart.CFrame.LookVector)
				}
				
				ReplicatedStorage.tweenenemy:FireAllClients(model,distance / enemySpeed,movementgoal)
				task.wait(distance/enemySpeed)
				model.PrimaryPart.CFrame = movementgoal.CFrame
				finished = true
			end)
		end)

		repeat task.wait() until finished == true

		speedChanged:Disconnect()

		local rotationgoal = {
			CFrame = CFrame.lookAlong(model.PrimaryPart.Position, node.CFrame.LookVector)
		}
		
		ReplicatedStorage.tweenenemy:FireAllClients(model,0.05,rotationgoal)
		task.wait(0.06)
		if not model then
			break
		end

		model.PrimaryPart.CFrame = rotationgoal.CFrame
	end

	if model then
		local node = exit

		local distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude

		local movmentgoal = {
			CFrame = CFrame.lookAlong(node.Position + Vector3.new(offset, size.Y / 2, 0), model.PrimaryPart.CFrame.LookVector)
		}
		ReplicatedStorage.tweenenemy:FireAllClients(model,distance/ enemySpeed,movmentgoal)
		task.wait(distance/enemySpeed)
		ReplicatedStorage.BaseHealth.Value = math.clamp(ReplicatedStorage.BaseHealth.Value - model.Health.Value,0,ReplicatedStorage.MaxBaseHealth.Value)
		model:Destroy()
	end
end

return module
2 Likes

erm… the enemy that has it’s speed changed still glitches around

1 Like

Could you try my edited script? If it doesn’t work, another video would help in debugging the problem.

2 Likes

Yeah I used the edited script. The exact same thing happened, where you can see the enemy’s glitching around.

1 Like

You can also see the enemy’s glitching around and acting up if u watch the video I sent u

at 0:26 in the video, if you look at the path near the entrance, you can see the enemy being glitchy

1 Like

Does it print the newSpeed correctly?

1 Like