Is lerp() here intensive?

I’m trying to have the character gently face the same direction as the camera while the player is standing still. My script works, however it seems to lag sometimes. Is lerp() intensive here sometimes or something?

local cam = workspace.CurrentCamera
local plr = game:GetService("Players").LocalPlayer

game:GetService("RunService").RenderStepped:connect(function()
	if plr.Character then
		local root = plr.Character.HumanoidRootPart
		if root and root.Velocity.magnitude==0 and root.CFrame ~= root.CFrame:lerp(CFrame.new(root.CFrame.p,root.CFrame.p+Vector3.new(cam.CFrame.lookVector.X,0,cam.CFrame.lookVector.Z)),0.1) then
			root.CFrame = root.CFrame:lerp(CFrame.new(root.CFrame.p,root.CFrame.p+Vector3.new(cam.CFrame.lookVector.X,0,cam.CFrame.lookVector.Z)),0.1)
		end
	end
end)

There are a few issues in this code, some of which could be the cause for it to perform poorly.

  • Directly compares sets of numbers
  • Indexing with . then checking for nil
  • Lacks variables for intermediate operations
  • CFrame.new instead of CFrame.lookAt

Fixing this step by step, we first remove the comparison. Noting that you should never directly compare Vector3s or CFrames unless they are sentinel values, because number arithmetic can cause seemingly equal numbers to not come up as such. This also removes our unnecessary allocation, because CFrame.new creates a new value every time.

Also, it’s unlikely that character velocity will always equal exactly 0 even when apparently standing still, so I’d replace that check with comparison against a small number of your choice. In this case, I used 0.1.

local cam = workspace.CurrentCamera
local plr = game:GetService("Players").LocalPlayer

game:GetService("RunService").RenderStepped:connect(function()
    local character = plr.Character

	if character then
		local root = plr.Character.HumanoidRootPart

        if root and root.Velocity.magnitude <= 0.1 then
			root.CFrame = root.CFrame:lerp(CFrame.new(root.CFrame.p,root.CFrame.p+Vector3.new(cam.CFrame.lookVector.X,0,cam.CFrame.lookVector.Z)),0.1)
		end
	end
end)

Next we address that Character.HumanoidRootPart can fail as an error. Since Player.Character is a property, it returns nil if it doesn’t exist, so that check is fine. However, Character.HumanoidRootPart is an instance look up, which hard errors when it fails. In this case, FindFirstChild is what you’re looking for.

local cam = workspace.CurrentCamera
local plr = game:GetService("Players").LocalPlayer

game:GetService("RunService").RenderStepped:connect(function()
    local character = plr.Character

	if character then
		local root = plr.Character:FindFirstChild("HumanoidRootPart")

        if root and root.Velocity.magnitude <= 0.1 then
			root.CFrame = root.CFrame:lerp(CFrame.new(root.CFrame.p,root.CFrame.p+Vector3.new(cam.CFrame.lookVector.X,0,cam.CFrame.lookVector.Z)),0.1)
		end
	end
end)

Now to break up the intermediate operations and use CFrame.lookAt. We also use vector multiplication to zero out the Y axis.

local cam = workspace.CurrentCamera
local plr = game:GetService("Players").LocalPlayer

game:GetService("RunService").RenderStepped:connect(function()
    local character = plr.Character

	if character then
		local root = plr.Character:FindFirstChild("HumanoidRootPart")

        if root and root.Velocity.magnitude <= 0.1 then
            local direction = cam.CFrame.lookVector * Vector3.new(1, 0, 1)
            local desired = CFrame.lookAt(root.CFrame.p, root.CFrame.p + direction)

			root.CFrame = root.CFrame:lerp(desired, 0.1)
		end
	end
end)

As a final step, let’s also update the functions and fields used to be more up to date. .p and :lerp, for example, are deprecated.

local cam = workspace.CurrentCamera
local plr = game:GetService("Players").LocalPlayer

game:GetService("RunService").RenderStepped:Connect(function()
    local character = plr.Character

	if character then
		local root = plr.Character:FindFirstChild("HumanoidRootPart")

        if root and root.Velocity.Magnitude <= 0.1 then
            local direction = cam.CFrame.LookVector * Vector3.new(1, 0, 1)
            local desired = CFrame.lookAt(root.Position, root.Position + direction)

			root.CFrame = root.CFrame:Lerp(desired, 0.1)
		end
	end
end)

This should perform better as you’ve avoided a lot of unneeded operations, but your bottleneck could be in an entirely different piece of code too. You’ll have to test and see.

5 Likes

This will still error, since you might index nil with Velocity. Adding a guard clause on top of the if statement to check for root should fix it. See 2 comments below. I would delete but it’s not working.

Actually fun fact: if root was nil, the if statement will stop at the root check, and it wont even reach the root.Velocity part.

This is why you see people doing:
if hit and hit.Parent and hit.Parent:FindFirstChild("Humanoid") then
all on one line, because if any of the first is nil, it will stop right away and not error at the Humanoid check.

1 Like

They were helping improve it step by step. The final product doesn’t have this issue:

And yes, because of short-circuiting, a and b will not even reach b if a is falsey. If you test =nil and error("siuuu") in the command bar you will see this never calls error

3 Likes