Question about variables

I have 105 (and counting) variables in only ONE script, which is a camera handling script. Most of the variables are calculations that I didn’t wanted to do directly at the place of usage because of code neatness. Most of them are used only once so I can use them at the place of usage but yet again due to code neatness I am not.

For example:

local Delta = Input.Delta
local X = cameraRotation.X - math.clamp(Delta.X/6,-10,10)
local Y = math.clamp(cameraRotation.Y - Delta.Y/4,-75,75)
cameraRotation = Vector2.new(X % 360,Y) * (UserInputService.MouseDeltaSensitivity)

The variables “Delta”, “X” and “Y” are only used once in the entire code.

What my question is; “Is using so many variables in one singular script normal? Or will it cause me issues?”.

I know this is a stupid question, especially for a veteran like me.

1 Like

It kinda depends. While the Roblox VM is pretty good at keeping memory safe, memory leaks still happen. I am talking about the garbage collector, the thing that is supposed to remove unused variables from memory. When programming in low level languages like C, the compiler will try to optimize your code. For example, it will try to merge variables together. Let’s say we have 3 variables: a = 5, b = 5 and c = a + b. We end up only printing c: print(c). Instead of using all 3 variables for this calculation, it should end up discarding a and b, defining c as 5 + 5. It might even just remove all of these variables because they are not used anywhere else. It will just output print(5+5) without the use of variables.
btw this is on machine code level, I am just giving an example.

It really depends, Roblox also had an update that introduced native machine code generation which is supposed to optimize these stuff even better. Having a lot of variables is completely normal. That is why they exist. For example you have a HTTP request handler. You can fetch the status code with some function. You need like 10 if statements, you gonna put a new fetch for each statement or you gonna save the code in a single variable then check it? While having too many variables can make your code look cluttered with garbage, it doesn’t matter because it ends up going through a lot of rules for optimization.

This is actually an interesting topic, because Roblox’s variables act dumb. If you wanted to write the number 255, it will save it with 3 whole bytes of data. At least people claim that this is the case. I haven’t ever checked or confirmed it myself, but since a lot of community made libraries exist, it is probably true. They are supposed to convert that integer into an actual single byte. This is called bit-packing. You can read more about it on the internet. It might be very useful when data compression and speed is critical.

I am currently doing research and a lot of testing for a project I started some time ago. Basically a “video player”. Just like in that post for bit packing I linked, it’s shown how small mistakes can lead to disasters in performance. I hope you find this topic interesting and useful. Again, it’s completely normal to have lots of variables. It’s not the amount of variables that is critical, but how you use them in your code. This is a complex topic, so no your question is not stupid. It is very important to understand it. Also remember that google is your friend.

1 Like

Wow! Well that is an entire essay! Jokes aside! I read it all and my knowledge has increased significantly! Thank you for telling me about data compression and how roblox manages variables. Now I can safely add 1000 variables in my code!

I have noted that you said:

Can you tell me if my code has useless variables or not?

Here it is:

Code for a camera system.

It is actually for a camera system I am currently making. It is still not complete btw.

local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

local Player = game.Players.LocalPlayer
local Character = script.Parent
local HumanoidRootPart:BasePart = Character:WaitForChild("HumanoidRootPart")
local Head:BasePart = Character:WaitForChild("Head")
local Torso:BasePart = Character:WaitForChild("Torso")
local Humanoid = Character:WaitForChild("Humanoid")
local Camera = workspace.CurrentCamera

local Shake = script.ShakeIntensity
local FOV = script.FOV
local Offset = script.CameraOffset
local Move = Character.Move
local canMove = Move.Movement
local currentAction = Move.CurrentAction

local RootJoint:Motor6D = Character:FindFirstChild("RootJoint",true)
local Neck:Motor6D = Character:FindFirstChild("Neck",true)
local LeftHip:Motor6D = Character:FindFirstChild("Left Hip",true)
local RightHip:Motor6D = Character:FindFirstChild("Right Hip",true)
local LeftShoulder:Motor6D = Character:FindFirstChild("Left Shoulder",true)
local RightShoulder:Motor6D = Character:FindFirstChild("Right Shoulder",true)
local RootJointC0 = RootJoint.C0
local NeckC0 = Neck.C0
local LeftHipC0 = LeftHip.C0
local RightHipC0 = RightHip.C0
local LeftShoulderC0 = LeftShoulder.C0
local RightShoulderC0 = RightShoulder.C0

local cameraRotation = Vector2.new(0,0)
local cameraDistance = 0
local deltaTime = 1/60
local old_finalRotation = CFrame.Angles(0,0,0)
local old_cameraOffset = CFrame.new(0,0,0)
local old_shakeCFrame = CFrame.new(0,0,0)
local oldX = 0
local currentShoulder = 1
local leaning = false
local old_bobbleX,old_bobbleY = 0,0
local whitelistedParts = {"Left Arm","Right Arm","Left Leg","Right Leg"}
local holdStart = 0
local tiltX,tiltY = 0,0

local rayCast_Params = RaycastParams.new()
rayCast_Params.FilterDescendantsInstances = {Character}

Camera.CameraType = Enum.CameraType.Scriptable

local function InFirstPerson()
	return cameraDistance <= 0
end

local function GetMovementVector()
	local HRPCFrame = HumanoidRootPart.CFrame
	local HRPVelocity = HumanoidRootPart.AssemblyLinearVelocity
	local movementVector = Vector3.zero
	if HRPVelocity.Magnitude > 1 then
		movementVector = HRPCFrame:VectorToObjectSpace(Vector3.new(HRPVelocity.X,0,HRPVelocity.Z)).Unit
	end
	movementVector = Vector3.new(math.round(movementVector.X),0,math.round(movementVector.Z))
	return movementVector
end

local function Lerp(a,b,t)
	return a+(b-a)*t
end

local function MakeInvisible(Status)
	for _,part in pairs(Character:GetChildren()) do
		if part:IsA("BasePart") and not table.find(whitelistedParts,part.Name) then
			part.LocalTransparencyModifier = Status and 1 or 0
		elseif part:IsA("Accessory") then
			part.Handle.LocalTransparencyModifier = Status and 1 or 0
		elseif part:IsA("Decal") then
			part.LocalTransparencyModifier = Status and 1 or 0
		end
	end
end

local function ToolEquipped()
	return Character:FindFirstChildOfClass("Tool")
end

RunService.RenderStepped:Connect(function()
	UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
	
	local HeadPosition = Head.Position
	local HeadCFrame = CFrame.new(HeadPosition)
	local HeadXRotation,_,HeadZRotation = Head.CFrame:ToOrientation()
	
	local HRPCFrame = HumanoidRootPart.CFrame
	local HRPPosition = HumanoidRootPart.Position
	local HRPVelocity = HumanoidRootPart.AssemblyLinearVelocity
	local HRPVMagnitude = HRPVelocity.Magnitude
	
	local movementVector = GetMovementVector()
	local movingBackwards = movementVector.Z > 0 and -1 or 1
	
	local noiseX = math.noise(tick()/10,os.clock()/10)*5
	local noiseY = math.noise(os.clock()/10,tick()/10)*5
	local bX = math.sin(time() * HRPVMagnitude/2)*((HRPVMagnitude/16)^(1/3))*2
	local bY = math.abs(math.sin(time() * HRPVMagnitude/2))*((HRPVMagnitude/16)^(1/3))*2
	local bobbleX = Lerp(old_bobbleX,bX,deltaTime*HRPVMagnitude)
	local bobbleY = Lerp(old_bobbleY,bY,deltaTime*HRPVMagnitude)
	
	local xRotation = cameraRotation.X+bobbleX+noiseX+math.deg(HeadXRotation/5)
	local yRotation = cameraRotation.Y+bobbleY+noiseY
	local zRotation = (oldX-cameraRotation.X)+(GetMovementVector().X*HRPVelocity.Magnitude/8)+bobbleX/2+math.deg(HeadZRotation/5)
	
	local cameraAngle = CFrame.Angles(0,math.rad(xRotation),0)*CFrame.Angles(math.rad(yRotation),0,math.rad(zRotation))
	
	local finalRotation = old_finalRotation:Lerp(cameraAngle,deltaTime*20)

	local rayOrigin = HeadPosition
	local rayDirection = (HeadCFrame*CFrame.Angles(Camera.CFrame:ToOrientation())).RightVector*currentShoulder*3
	local ray = workspace:Raycast(rayOrigin,rayDirection,rayCast_Params)
	local sideDistance = ray ~= nil and 0 or math.huge
	
	local offset = CFrame.new(InFirstPerson() and 0 or math.min(2,sideDistance)*currentShoulder,InFirstPerson() and 2/3 or 0,0)
	
	local rayOrigin2 = (HeadCFrame*offset).Position-Camera.CFrame.LookVector
	local rayDirection2 = Camera.CFrame.LookVector*-cameraDistance
	local ray2 = workspace:Raycast(rayOrigin2,rayDirection2,rayCast_Params)
	local final_cameraDistance = ray2 ~= nil and ray2.Distance-0.5 or math.huge
		
	offset *= CFrame.new(currentShoulder*(leaning and InFirstPerson() and 1 or 0),0,math.min(cameraDistance,final_cameraDistance))*CFrame.Angles(0,0,math.rad((leaning and -30 or 0)*currentShoulder))
	
	local cameraOffset = old_cameraOffset:Lerp(offset,deltaTime*10)
	
	local shake_pX,shake_pY,shake_pZ = math.random()-0.5,math.random()-0.5,math.random()-0.5
	local shake_oX,shake_oY,shake_oZ = math.rad(math.random()-0.5)*10,math.rad(math.random()-0.5)*10,math.rad(math.random()-0.5)*10
	local shakePositional = CFrame.new(Vector3.new(shake_pX,shake_pY,shake_pZ)*Shake.Value)
	local shakeOrientational = CFrame.Angles(shake_oX*Shake.Value,shake_oY*Shake.Value,shake_oZ*Shake.Value)
	local shakeCFrame = old_shakeCFrame:Lerp(shakePositional*shakeOrientational,deltaTime*10)
	
	Camera.CFrame = HeadCFrame*finalRotation*cameraOffset*Offset.Value*shakeCFrame
	Camera.FieldOfView = Lerp(Camera.FieldOfView,FOV.Value+Vector2.new(HRPVelocity.X,HRPVelocity.Z).Magnitude/1.25,deltaTime*20)
	
	local HRProtX,HRProtY,HRProtZ = HRPCFrame:ToOrientation()
	local CamerarotX,CamerarotY,_ = Camera.CFrame:ToOrientation()
	local TorsorotX,TorsorotY,_ = Torso.CFrame:ToOrientation()
	if canMove.Value then
		tiltY = Lerp(tiltY, movementVector.X*movingBackwards,deltaTime*5)
		tiltX = Lerp(tiltX,movementVector.Z*HRPVMagnitude/16,deltaTime*5)
		local torsoTilt_Y = math.clamp(-tiltY*40*(movementVector.Z ~= 0 and 1/(2^(1/2)) or 1.25),-30,30)
		RootJoint.C0 = RootJoint.C0:Lerp(RootJointC0*CFrame.Angles(math.rad(-tiltX*10),0,math.rad(torsoTilt_Y)),deltaTime*10)
		local legTilt_Y = math.clamp(-tiltY*20*(movementVector.Z ~= 0 and 1/(2^(1/2)) or 1.25),-60,60)
		LeftHip.C0 = CFrame.Angles(0,math.rad(legTilt_Y),0)*LeftHipC0
		RightHip.C0 = CFrame.Angles(0,math.rad(legTilt_Y),0)*RightHipC0
		HumanoidRootPart.CFrame = CFrame.new(HRPPosition)*CFrame.Angles(HRProtX,CamerarotY,HRProtZ)
	else
		RootJoint.C0 = RootJointC0
		LeftHip.C0 = LeftHipC0
		RightHip.C0 = RightHipC0
	end
	Neck.C0 = NeckC0*CFrame.Angles(-CamerarotX-(HRProtX-TorsorotX),0,0)
	
	local Tool = ToolEquipped()
	
	local rightShoulder_C0 = RightShoulderC0
	local leftShoulder_C0 = LeftShoulderC0
	
	if Tool then
		local armBobble = CFrame.Angles(math.rad(zRotation)*2,math.rad(bobbleX)*5,math.rad(bobbleY)*5)
		local rightShoulder_Offset = CFrame.new((InFirstPerson() and 1.5 or 0), -1, 0)*armBobble
		local leftShoulder_Offset = CFrame.new((InFirstPerson() and -1.5 or 0), -1, 0)*armBobble:Inverse()

		local HRParmOffset = (RootJoint.C0*RootJointC0:Inverse()):Inverse()

		rightShoulder_C0 = HRParmOffset*(RightShoulderC0+Vector3.yAxis)*CFrame.Angles(0,0,CamerarotX)*rightShoulder_Offset
		leftShoulder_C0 = HRParmOffset*(LeftShoulderC0+Vector3.yAxis)*CFrame.Angles(0,0,-CamerarotX)*leftShoulder_Offset
	end
	
	RightShoulder.C0 = RightShoulder.C0:Lerp(rightShoulder_C0,deltaTime*10)
	LeftShoulder.C0 = LeftShoulder.C0:Lerp(leftShoulder_C0,deltaTime*10)
	
	if UserInputService:IsKeyDown(Enum.KeyCode.E) then
		currentShoulder = 1
		if holdStart == 0 then
			holdStart = tick()
		end
		if tick()-holdStart > 0.25 then
			leaning = true
		end
	elseif UserInputService:IsKeyDown(Enum.KeyCode.Q) then
		currentShoulder = -1
		if holdStart == 0 then
			holdStart = tick()
		end
		if tick()-holdStart > 0.25 then
			leaning = true
		end
	else
		holdStart = 0
		leaning = false
	end
	
	MakeInvisible(InFirstPerson())
	
	old_finalRotation = finalRotation
	old_cameraOffset = cameraOffset
	old_shakeCFrame = shakeCFrame
	oldX = cameraRotation.X
	old_bobbleX = bobbleX
	old_bobbleY = bobbleY
end)

UserInputService.InputChanged:Connect(function(Input,gameProcessedEvent)
	if not gameProcessedEvent then
		if Input.UserInputType == Enum.UserInputType.MouseMovement then
			local Delta = Input.Delta
			local X = cameraRotation.X - math.clamp(Delta.X/6,-10,10)
			local Y = math.clamp(cameraRotation.Y - Delta.Y/4,-75,75)
			cameraRotation = Vector2.new(X % 360,Y) * (UserInputService.MouseDeltaSensitivity)
		elseif Input.UserInputType == Enum.UserInputType.MouseWheel then
			cameraDistance = math.clamp(cameraDistance - Input.Position.Z,0,12)
		end
	end
end)
1 Like

Yeah, you got some. They are most likely automatically removed from memory by the garbage collector anyway, but it’s nice to remove them unless you really need them.


You don’t even need to guess. Linters exist. A linter is basically a tool to check your code for such stuff. BTW, that’s not an actual error. I have configured it to be very strict and detect any issue or warning as an error. You can safely ignore it.

It takes a little bit of time to set up such a workflow, but in the long run, it is very useful and increases productivity. The tool I am using here is called Luau-LSP. It’s a CLI tool, but it also has an extension for VSCode. The Roblox editor actually uses a similar thing internally. I think you can configure the Roblox one as well, but I don’t know how. Even if it’s easy, I would still consider a workflow that doesn’t depend on the studio. You can also use a tool called Stylua; it makes your code look better and applies general rules for “styling.” It is also a CLI tool and offers a VSCode extension.

Also, once again, you shouldn’t worry about variables, even if you use them a single time in your code. For example:

The garbage collector and the internal optimizations should do their jobs. Sometimes they don’t. That’s the whole issue and probably the reason you asked this question. Whether you want to avoid it or not, it is up to you. Everyone has their own style of writing code.

Also about the “type errors,” that’s just an error whenever the compiler is confused about the data type the variable holds. There are many types of data, such as integers, strings, bools, decimals, etc. In the function above, you can see I used type annotations. a: number specifies that the a argument is only a number. : () at the end of the function specifies that the function doesn’t return anything. It is a complex topic, but it can also increase productivity and code quality. It will also automatically highlight and show suggestions if you specify the type. Sometimes you might need to use stuff like :WaitForChild() for objects that are generated while the game runs. You know the object will eventually be there, but your code editor doesn’t know what type of object it is. That’s why you don’t get auto completion. Specifying the type is as easy as writing a colon and the type. That’s all. I hope you find this useful. More information about it here.

1 Like

If you’re worried about unused variables cluttering up your environment, you could define them using a block:

local tauInDegrees do
    local tau = math.pi * 2;

    tauInDegrees = math.deg(tau)
end
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.