Tower defence enemies lagging behind

how could i make the rotations more like a curve? srry if im being annoying, but i just want to finish off my enemy system and be done with it.

1 Like

You can try using bezier tweens on the NPC when nearing the end:

Also, don’t worry about asking questions. Sorry for not solving your problem earlier, I feel bad for taking this long.

You could also try using a Catmull Rom Spline for your entire track:

thank you! il try using the modules today.

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?