Tower defence enemies lagging behind

Wait if i use the module what would i put as my control points?

Not sure, I’ve never used those modules before. They should have some documentation though.

Also, here’s another module for tower defense games:

hm. It looks good, but it still seems… kind of too smooth? okay that just sounds really stupid, but I want to make the rotations realistic, not smoother than soap. can u give me some pointers on how to make a curve rotation from scratch? Thanks

also, some weird bug where the client dosent finish the tween beforethe server teleports it. Heres a video:

hers the 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: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 _, size = model:GetBoundingBox()


	
	for index = 1, #waypointfolder:GetChildren() - 2 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 = 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 = CFrame.lookAlong(
				node.Position + Vector3.new(offset, size.Y / 2, 0),
				model.PrimaryPart.CFrame.LookVector
			)
			finished = true
		end)

		local speed: IntValue = model:WaitForChild("speed")
		local changedthread = nil

		local speedChanged = nil

		speedChanged = speed:GetPropertyChangedSignal("Value"):Connect(function()
			if movethread then
				task.cancel(movethread)
				movethread = nil
			end

			if changedthread then
				task.cancel(changedthread)
				changedthread = nil
			end

			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 = 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 = CFrame.lookAlong(
					node.Position + Vector3.new(offset, size.Y / 2, 0),
					model.PrimaryPart.CFrame.LookVector
				)
				finished = true
			end)
		end)

		repeat
			task.wait()
		until finished == true

		speedChanged:Disconnect()

		local rotationgoal = {
			CFrame = CFrame.lookAlong(model.PrimaryPart.Position, node.CFrame.LookVector),
		}
		local rotation
		ReplicatedStorage.tweenenemy:FireAllClients(model, 0.05, rotationgoal)
		task.wait(0.05)
		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 finished = false
		local distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
		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 = CFrame.lookAlong(
				node.Position + Vector3.new(offset, size.Y / 2, 0),
				model.PrimaryPart.CFrame.LookVector
			)
			finished = true
		end)

		local speed: IntValue = model:WaitForChild("speed")
		local changedthread = nil

		local speedChanged = nil

		speedChanged = speed:GetPropertyChangedSignal("Value"):Connect(function()
			if movethread then
				task.cancel(movethread)
				movethread = nil
			end

			if changedthread then
				task.cancel(changedthread)
				changedthread = nil
			end

			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 = 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 = CFrame.lookAlong(
					node.Position + Vector3.new(offset, size.Y / 2, 0),
					model.PrimaryPart.CFrame.LookVector
				)
				finished = true
			end)
		end)

		repeat
			task.wait()
		until finished == true

		speedChanged:Disconnect()
		ReplicatedStorage.BaseHealth.Value = math.clamp(
			ReplicatedStorage.BaseHealth.Value - model.Health.Value,
			0,
			ReplicatedStorage.MaxBaseHealth.Value
		)
		model:Destroy()
	end
end

return module

Edit: the only things i changed with the spawning is how the enemys are spawned. I made the wave system work with dual lanes, so calling the module more.

Make the wait longer. It’s most likely looking like this because it takes time for the client to receive the event.

Is this what you want?

You can change the curve size of the path to make it less smooth in bezierpath

I am going to try implementing this today. Thank you!

Also i noticed that now, if the client is lagging badly, the enemy will start teleporting around. Is there any way I can fix this?

Okay, i am in the process of implementing it, i am just trying to figure out where to put the 3rd control point. I am using Bezier Path. Something in noticed about the system is that when the enemy count starts to rack up, the enemys start instantly teleporting/spawning right at the 1st or second node. Heres a vid:

@Katrist

Also, the lag is getting a bit out of hand is there any way i could optimize this more? And again, sorry for bothering you. Thanks!

Edit: this dosent have the curves implemented

You could try to get rid of all the humanoids, because those have a lot of lag.

You can try to put a point at every node for BezierPath.

1 Like

I already did delete the humanoids.
So do I just put a point at the corner of every node, and use that for the control points?
@Katrist

No, just use every node’s position in your positions table for the BezierPath.new.

Okay? But I thought we needed an extra node to pull the curve up?

If you want the curve to be more exaggerated, you can move the nodes.

Also, could you show some code?

@Katrist
OH, i was using bezier path wrong. Wait lemme inplement it rq

@Katrist I tried implementing this, but it was super choppy, and it wouldn’t wait enough to delete its self. And on the 1st path, the enemies don’t damage the base at all.
Here’s the code:

--!native
--!strict
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 tweenenemy = ReplicatedStorage.tweenenemy
	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 _, size = model:GetBoundingBox()
	local nodepositions = {}
	local enemyspeed = speed.Value
	local nodeindex = 0
	print("spawningenemy")
	for _ = 1, #waypointfolder:GetChildren() - 2 do
		nodeindex += 1
		local node:Part = waypointfolder:FindFirstChild(tostring(nodeindex))
		table.insert(nodepositions,node.CFrame.Position)
	end
	table.insert(nodepositions,waypointfolder.exit.CFrame.Position)
	print("got position table")
	print(unpack(nodepositions))
	local totalwait = 0
	nodeindex = 0
	for _ = 1, #waypointfolder:GetChildren() - 2 do
		local success, err = pcall(function()
			nodeindex += 1
			local node:Part = waypointfolder:FindFirstChild(tostring(nodeindex))
			local distance = (node.CFrame.Position + Vector3.new(offset,size.Y / 2,0) - model.PrimaryPart.CFrame.Position).Magnitude
			totalwait += distance
		end)
		if err then
			warn(err)
		end
	end
	print("generated wait")
	tweenenemy:FireAllClients(model,nodepositions,Vector3.new(offset,size.Y / 2,0))
	task.wait(totalwait / enemyspeed)
	ReplicatedStorage.BaseHealth.Value -= model.Health.Value
	model:Destroy()
end

return module

heres the client code:

ReplicatedStorage.tweenenemy.OnClientEvent:Connect(function(model:Model,Position:table,offset:Vector3)
	print("tweening enemy")
	local modelpath = BezierPath.new(Position,3)
	for i = 0,1,1/100 do
		model.PrimaryPart.CFrame = modelpath:CalculateUniformCFrame(i) + offset
		task.wait(0.1)
	end
end)

here’s a vid:

@Katrist
thank you and have a happy new year!

Edit: wrong code, replaced code

I would do this all on the server, since BezierPath is fairly performant.

Code:

--!native
--!strict
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 tweenenemy = ReplicatedStorage.tweenenemy
	local model: Model = ServerStorage.Enemies[name]:Clone()
	model:PivotTo(spawns + Vector3.new(offset,0,0))
	print("spawningenemy")

	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 _, size = model:GetBoundingBox()
	
	local nodePositions = {}
	
	for i = 1, #waypointfolder:GetChildren() - 2 do
		table.insert(nodePositions, waypointfolder[i].Position + Vector3.new(offset, size.Y / 2, 0))
	end
	
	table.insert(nodePositions, waypointfolder.exit.Position)
	
	local newPath = BezierPath.new(nodePositions)
	local t = 0
	
	while t < 1 do
		model:PivotTo(newPath:CalculateUniformCFrame(t))
		t += speed.Value / 100

		task.wait()
	end
	
	ReplicatedStorage.BaseHealth.Value -= model.Health.Value
	model:Destroy()
end

return module

Make sure to add a variable for BezierPath, I didn’t add one.

If it’s too fast, make sure to change this value:

t += speed.Value / 100

Make the 100 a higher number for the speed to be lower, or make the 100 a lower number for the speed to be higher.

Have you anchored the PrimaryPart? You also won’t need :SetNetworkOwner(nil).

Happy new year to you too!

1 Like

I tried anchoring the primary part before, but that didn’t really work. I don’t know if calling set network ownership hanged the script, but il try it today. Thank you!