TweenService Performance Issues / Alternative

GOAL: Moving NPCs

CURRENT METHOD: Using the TweenService to move the NPCs from place to place at their specified speeds

PROBLEM: According to the microprofiler, updating etc. the tweens is very resource “inefficient”

ADDITIONAL INFO: The Tweens are performed on the server. The NPCs absolutly MUST walk the route fully, but not at 0.001 stud-steps. Only tweening them on the client will not be a sufficient solution.

WHAT I NEED: An alternative way to move the NPCs or a way to tween them cleanly on the client and sloppily on the server

Thanks in advance :slight_smile:

2 Likes

So long the points you set aren’t more than 3000 studs away from each other, you should be fine.

2 Likes

Would this not use a similar method as Humanoid:MoveTo()?

Hum:MoveTo() was actually my first attempt of moving NPCs and it lagged a LOT

1 Like

I haven’t experienced lag while using MoveTo() myself. Could you write down how you used MoveTo() in that first attempt you made?

Hmm, might be tough as it was long ago, Ill send you my current script. Think the tweenservice usage as hum:Moveto

Script in each individual NPC

do
	local player = script.Parent:WaitForChild("Player").Value
	local hum: Humanoid = script.Parent:WaitForChild("Humanoid")
	local points = game:GetService("Workspace"):WaitForChild("BrickSpawn1"):WaitForChild("Builders"):WaitForChild("Points")
	local animator: Animator = hum:WaitForChild("Animator")
	local run = script.Parent:WaitForChild("Run", 5)
	local idle = script.Parent:WaitForChild("Idle", 5)
	local sprint = script.Parent:WaitForChild("Sprint", 5)
	local hrp: BasePart = hum:FindFirstAncestorOfClass("Model"):FindFirstChild("HumanoidRootPart")
	local TweenService = game:GetService("TweenService")
	local ar = game:GetService("ReplicatedStorage"):WaitForChild("ClientFXEvents"):WaitForChild("AnimateBuilders")
	local upg = require(game:GetService("ServerScriptService").Upgrades)
	local rs = game:GetService("RunService")
	local bsT = upg.getUpgrade("BuilderSpeed")
	local BuilderHandler = require(game:GetService("ServerScriptService").BuilderHandler)
	local getLook = BuilderHandler.getLook
	local calculateDistanceWalked = BuilderHandler.calculateDistanceWalked
	local getRandPoint = BuilderHandler.getRandPoint
	-- Math Imports --

	local rd = math.round
	local sqrt = math.sqrt
	local random = math.random
	-- -- 		


	local WalkToPart = points.Position1

	while player and hum do task.wait(0.5)

		local bs = bsT[player.Upgrades:WaitForChild("BuilderSpeed").Value][1]
		
		local prv = WalkToPart
		
		local dest = getRandPoint()
		
		if dest == prv then repeat task.wait() dest = getRandPoint() until dest ~= prv end
		
		WalkToPart = dest
		

		local location = Vector3.new(dest.Position.X, hrp.Position.Y, dest.Position.Z)
		local xDirection = rd(dest.Position.X - prv.Position.X)
		local zDirection = rd(dest.Position.Z - prv.Position.Z)
		local look = getLook(xDirection, zDirection, dest)
		

		--
		
		hrp.CFrame = CFrame.new(hrp.Position, look)
		
		if player.Upgrades:WaitForChild("BuilderSpeed").Value < 6 then
			ar:FireClient(player, script.Parent, "Run")
		else	
			ar:FireClient(player, script.Parent, "Sprint")
		end
		
		local MoveTinfo = TweenInfo.new(calculateDistanceWalked(xDirection, zDirection)/(10+bs*2), Enum.EasingStyle.Linear, Enum.EasingDirection.InOut)
		local MoveTween = TweenService:Create(hrp, MoveTinfo, {CFrame = CFrame.new(location, look)}) 
		MoveTween:Play()

		MoveTween.Completed:Wait()
		MoveTween:Destroy()
			
		
		ar:FireClient(player, script.Parent, "Idle")
		
		task.wait((6+random(-3, 3))/(1+bs))

		
	end

	
end

BuilderHanlder Module in ServerScriptService:

local BH = {}

local points = game:GetService("Workspace"):WaitForChild("BrickSpawn1"):WaitForChild("Builders"):WaitForChild("Points")
local rd = math.round
local sqrt = math.sqrt
local random = math.random

-- Formulas --

function BH.calculateDistanceWalked(xDirection: Vector3, zDirection: Vector3) 

	local result = sqrt((xDirection ^ 2) + (zDirection ^ 2))

	return result

end

-- --

function BH.getRandPoint()

	local wtp = random(1, 29)

	local dest

	if wtp == 1 then dest = points.Position1 end
	if wtp == 2 then dest = points.Position2 end
	if wtp == 3 then dest = points.Position3 end
	if wtp == 4 then dest = points.Position4 end
	if wtp == 5 then dest = points.Position5 end
	if wtp == 6 then dest = points.Position6 end
	if wtp == 7 or wtp == 8 then dest = points.Position7 end
	if wtp == 9 then dest = points.Position8 end
	if wtp == 10 or wtp == 11 then dest = points.Position9 end
	if wtp == 12 then dest = points.Position10 end
	if wtp == 13 then dest = points.Position11 end
	if wtp == 14 then dest = points.Position12 end
	if wtp == 15 then dest = points.Position13 end
	if wtp == 16 then dest = points.Position14 end
	if wtp == 17 then dest = points.Position15 end
	if wtp == 18 then dest = points.Position16 end
	if wtp == 19 or wtp == 20 then dest = points.Position17 end
	if wtp == 21 then dest = points.Position18 end
	if wtp == 22 or wtp == 23 then dest = points.Position19 end
	if wtp == 24 then dest = points.Position20 end
	if wtp == 25 then dest = points.Position21 end
	if wtp == 26 then dest = points.Position22 end
	if wtp == 27 then dest = points.Position23 end
	if wtp == 28 then dest = points.Position24 end
	if wtp == 29 then dest = points.Position25 end

	return dest

end

function BH.getLook(xDirection, zDirection, dest)

	local look = dest.Position

	if xDirection == 13 or xDirection == -13 then
		if xDirection < 0 then look += Vector3.new(-30, 0, 0) end if xDirection > 0 then look += Vector3.new(30, 0, 0) end if xDirection == 0 then look = Vector3.new(0, look.Y, look.Z) end
	end

	if zDirection == 13 or zDirection == -13 then
		if zDirection < 0 then look += Vector3.new(0, 0, -30) end if zDirection > 0 then look += Vector3.new(0, 0, 30) end if zDirection == 0 then look = Vector3.new(look.X, look.Y, 0) end
	end

	if xDirection == 26 or xDirection == -26 then
		if xDirection < 0 then look += Vector3.new(-60, 0, 0) end if xDirection > 0 then look += Vector3.new(60, 0, 0) end if xDirection == 0 then look = Vector3.new(0, look.Y, look.Z) end
	end

	if zDirection == 26 or zDirection == -26 then
		if zDirection < 0 then look += Vector3.new(0, 0, -60) end if zDirection > 0 then look += Vector3.new(0, 0, 60) end if zDirection == 0 then look = Vector3.new(look.X, look.Y, 0) end
	end
	if xDirection == 39 or xDirection == -39 then
		if xDirection < 0 then look += Vector3.new(-90, 0, 0) end if xDirection > 0 then look += Vector3.new(90, 0, 0) end if xDirection == 0 then look = Vector3.new(0, look.Y, look.Z) end
	end

	if zDirection == 39 or zDirection == -39 then
		if zDirection < 0 then look += Vector3.new(0, 0, -90) end if zDirection > 0 then look += Vector3.new(0, 0, 90) end if zDirection == 0 then look = Vector3.new(look.X, look.Y, 0) end
	end
	if xDirection == 52 or xDirection == -52 then
		if xDirection < 0 then look += Vector3.new(-120, 0, 0) end if xDirection > 0 then look += Vector3.new(120, 0, 0) end if xDirection == 0 then look = Vector3.new(0, look.Y, look.Z) end
	end

	if zDirection == 52 or zDirection == -52 then
		if zDirection < 0 then look += Vector3.new(0, 0, -120) end if zDirection > 0 then look += Vector3.new(0, 0, 120) end if zDirection == 0 then look = Vector3.new(look.X, look.Y, 0) end
	end

	return look

end

return BH

Ignore any comments, I used them to explain what some things do to a frend

Also: Do note that this is run around 20/s on a somewhat busy server

I suspect the lag has less to do with the tweens and more to do with a culmination of all the inefficiencies in the code. On their own, they wouldn’t be too much of a problem, but since they’re running 20 times a second, it adds up to some visible lapses.

WaitForChild() is Used Too Often

According to this comment, you’re using WaitForChild() way too often in the NPCs script. If you can be certain that something exists, don’t use WaitForChild(), because that will introduce a lot of bloat. If the NPCs script is a server-side script, you don’t need to use WaitForChild() at all, since everything on server-side is loaded in before executing anything. Especially egregious are the WaitForChild()s in the loop. If you don’t end up removing the WaitForChild()s, you can at least move the ones in the loop to somewhere out of it.

If Statements Could be Else-If Statements

The if statements you have going in BuilderHanlder are really inefficient. A better solution for getRandPoint would’ve been to make a table of all the positions and just using the random number to index one of them (ie. like points[wtp]) instead of checking every single one.

For getLook(), the entire thing can be rewritten as an else-if statement.

Other Inefficiencies

This small list on its own probably still wouldn’t amount to much in the face of the servers’ ability to make billions of computations a second (based on the assumption that they’re more powerful than cheap laptops, which can do so). So either tweens are even more inefficient than I remember, or there are even more inefficiencies scattered about. I’m especially worried about how you’re using run service, because operations that run every frame absolutely need to be as efficient as possible, so check that out as well.

Conclusion

I can’t be sure that these inefficiencies are actually the problem for lag. So I still advise that you try to use character pathfinding instead. There’s no need to reinvent the wheel, and whatever those Roblox engineers have come up with will likely be better than whatever you or I make, especially when they know the intricacies of Luau better.

1 Like

Thanks alot :heart:
I had no idea that WaitForChild is a bad choice for server scripts! This’ll be helpful in the future as well. Thanks a lot, really!