Tower defence enemies lagging behind

yes, i anchored only the primary part.

Is it because it would be hard on the remotes? Because i could try to do something with unreliable remote events.

No, it’s because the server wouldn’t know the position of the units properly.

OH, okay. So how do i fix the enemys falling apart? also the enemys are floating, even after adding size.y / 2.

Edit: i also noticed that the enemys also start spawning at the first node after a bit of spawning.

Edit: Oops, didnt divide it by 2 But the enemys still spawn at the first node when its laggy. And the enemys still fall apart a lot.

Can you show the code where you’re calling .Spawn? Also, the :PivotTo at the start of your function is useless.

I don’t have access to the code rn, but it’s just a for loop that calls the module, than has a random wait between 0.2 to 0.4.

Dose bezier path automatically pivot the part?

Edit: Heres the code:

local function spawnenemies(name, amount, exit, enemyspawn, nodes)
	for _ = 1, amount, 1 do
		local spawnthread = coroutine.create(function()
			enemy.spawn(name, nodes, exit, enemyspawn.CFrame)
		end)
		coroutine.resume(spawnthread)
		task.wait(math.random(0.2, 0.3))
	end
end
coroutine.resume(coroutine.create(function()
					for _, folder in pairs(wave.enemies) do
						if lost == true then
							break
						end
						for name, amount in pairs(folder) do
							local wrap = coroutine.wrap(spawnenemies)
							if lost == true then
								break
							else
								wrap(name, amount, nodefolder.exit, nodefolder.enemyspawn, nodefolder)
							end
						end
						task.wait(10)
					end
				end))

@Katrist

No, there’s a reason I said “at the start of your function”.

You can try to do it on the client though, remember that you will need to keep track of the position on the server too.

okay, so do i send the value T to the client, and than have it compute on the Client?
And how do i fix the enemys falling apart?
@Katrist

Edit: Also, the first longer path enemies are much faster than the shorter path enemies. It seems that the longer path enemies speed up to make sure they reach the exit the same time as the second path

Edit2: okay i fixed edit one with a different equation for T.

I’m honestly not very sure. Did this happen when the enemies were unanchored too?

no, the enemies didnt fall apart when it was unanchored

i recommend you don’t make a new bezierpath object for every enemy, the precomputation in bezierpath is quite hefty. just make 1 bezierpath object when the game runs, and store it in a table and reuse it by just indexing said table instead of making a new object per enemy.
Also the way you are calculating the T value is not the best way.
You can get smooth movement by just doing:

local PathLength = NewPath:GetPathLength()
local T = (tick() - EnemySpawnTime) / (PathLength / EnemySpeed)

and put this in a run service loop.
It should help you get smoother movement.

They can’t make a BezerPath object and cache it because they have an offset variable that changes for every mob, which means that the positions will be different. If that were removed though, this task would be significantly easier.

I did that(make one bezierpath for every path), and then had the enemy add the offset its self.

1 Like

Also, I found out that the individual body parts aren’t separated, but the body parts just aren’t loaded in.

1 Like

Is the performance better?

I would say its better. But the enemys still fall apart

Could you send a video?

just do UniformCFrame * OffsetCFrame
its what i do in my game lol

Sorry for late reply, didnt see that. Heres a video:

Also, its really laggy at the start. How can i fix it?

@Katrist

You can try to offload the work onto the client. Could I see your current code? You’ve said you have implemented it on the client but it doesn’t look like it is.

yeah i havent done the client thing. I dont reallly know how i should do that. Heres my 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")
local BezierPath = require(ReplicatedStorage.Packages.BezierPath)
local janitor = require(ReplicatedStorage.Packages.janitor)
local paths = {}


module.createnavpath = function(nodefolder, index)
	local nodePositions = {}
	table.insert(nodePositions,nodefolder.enemyspawn.Position)
		for i = 1, #nodefolder:GetChildren() - 2 do
			table.insert(nodePositions, nodefolder[i].Position)
		end

	table.insert(nodePositions, nodefolder.exit.Position)
	paths[index] = 	BezierPath.new(nodePositions, 3)
end



module.spawn = function(name:string, nodefolderindex:number, exit, spawns:CFrame, boss:BoolValue)
	local newPath = paths[nodefolderindex]
	local StartTime = tick()
	ReplicatedStorage.DebugValues.EnemyCount.Value += 1
	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:GetChildren()) do
		if parts:IsA("BasePart") then
			parts.CollisionGroup = "Enemies"
		end
	end
	model.Parent = Workspace:FindFirstChild(ReplicatedStorage.Map.Value).enemies
	local health:IntValue = model:WaitForChild("Health")
	local healthdestroyconnection = 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 debugconnection = model.Destroying:Connect(function()
		ReplicatedStorage.DebugValues.EnemyCount.Value -= 1
	end)
	local nodetouchindex = 0
	local movethread
	movethread = task.spawn(function()
		local touchbox:BasePart = model:FindFirstChild("RightFoot")
		local noderemoveconnection
		noderemoveconnection = touchbox.Touched:Connect(function(hit)
			--[[ if hit:FindFirstAncestor("nodes") then
				nodetouchindex += 1
				table.remove(nodePositions,nodetouchindex)
			end ]]
		end)
		local enemyjanitor = janitor.new()
		enemyjanitor:Add(noderemoveconnection)
		enemyjanitor:Add(healthdestroyconnection)
		enemyjanitor:Add(debugconnection)
		local t = 0
		local TotalTime = newPath:GetPathLength() / speed.Value
		while t < 1 do
			t = (tick() - StartTime) / (TotalTime)
			model.PrimaryPart.CFrame = newPath:CalculateUniformCFrame(t) + Vector3.new(offset,size.Y / 2, 0)
			task.wait()
			if not model.PrimaryPart or not model.Parent then
				break
			end
		end
		ReplicatedStorage.BaseHealth.Value = math.clamp(ReplicatedStorage.BaseHealth.Value - model.Health.Value,0,ReplicatedStorage.MaxBaseHealth.Value)
		model:Destroy()
	end)
	local speedchanged = nil
	speedchanged = speed.Changed:Connect(function(value)
		
	end)
end

return module