Am I doing this correctly?

Hi,

I am trying to get into making classes because I feel like it is easier to create and manage efficiently.
I don’t know if I am doing this correctly so can someone tell me if this is how I make classes?

--!strict
--/ services
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--/ directories
local Modules = ReplicatedStorage.Source.Modules
local Classes = ReplicatedStorage.Source.Classes
local Packages = ReplicatedStorage.Packages
--/ requires
local Spring = require(Modules.Spring)
local Trove = require(Packages.Trove)
local CharacterLoadedWrapper = require(Classes.CharacterLoadedWrapper)
--/ class
local Camera = {}
Camera.__index = Camera

type self = {
	CamPart: Part,
	Player: Player,
	CameraAngle: CFrame,
	XOFFSET: number,
	YOFFSET: number,
	ZOFFSET: number,
	Damper: number,
	Speed: number,
	Spring: any,
	Tracking: any,
	Connections: any,
	Camera: any,
	CharacterLoadedWrapper: any,
	Character: any
};

export type Camera = typeof( setmetatable({} :: self, Camera) )

function Camera.new(Player: Player): Camera
	local self = setmetatable({} :: self, Camera)
	
	self.Player = Player
	self.Tracking = {Player} -- stores the players that the camera is tracking
	self.Connections = Trove.new() -- stores connections such as runservice
	self.Camera = workspace.CurrentCamera
	-- customizable settings
	self.CameraAngle = CFrame.Angles(math.rad(0), math.rad(0),math.rad(0))
	self.XOFFSET = 0
	self.YOFFSET = 3
	self.ZOFFSET = 10
	self.Damper = 100
	self.Speed = 10
	
	self.Spring = Spring.new(Vector3.new())
	self.CamPart = Instance.new("Part")
	
	self.CamPart.Anchored = true
	self.CamPart.Name = "CamPart"
	self.CamPart.Parent = workspace
	self.CamPart.CanCollide = false
	self.CamPart.Transparency = 1
	
	self.Character = self.Player.CharacterAdded:Wait() or self.Player.Character
	self.CharacterLoadedWrapper = CharacterLoadedWrapper.new(self.Player)
	
	self.Camera.CameraType = Enum.CameraType.Scriptable
	
	self.Connections:Connect(Players.PlayerRemoving, function(player: Player)
		self.PlayerRemoving(self)
	end)
	
	return self
end

function Camera.calculateAveragePosition(self: Camera)
	local Total = Vector3.new()

	for _, Track in pairs(self.Tracking) do    
		if Track:IsA("Player") and Track.Character then
			local Root = Track.Character:FindFirstChild("HumanoidRootPart").Position
			Total += Root
		end

		if Track:IsA("Part") then
			Total += Track.Position
		end
	end

	return Total / #self.Tracking
end


function Camera.calculateAverageMagnitude(self: Camera)
	local Total = 0
	
	for _, Track in pairs(self.Tracking) do    
		if Track:IsA("Player") then
			local Root = Track.Character:FindFirstChild("HumanoidRootPart")
			Total += (Root.Position - self.CamPart.Position).Magnitude
		end

		if Track:IsA("Part") then
			Total += (Track.Position - self.CamPart.Position).Magnitude
		end
	end

	return Total / #self.Tracking
end

function Camera.Update(self: Camera): ()
	if not self.CharacterLoadedWrapper:isLoaded() then
		self.CharacterLoadedWrapper.loaded:Wait()
	end
	
	for _, player in Players:GetPlayers() do
		if not table.find(self.Tracking, player) then
			table.insert(self.Tracking, player)
		end
	end
	
	local Root = self.Character["HumnaoidRootPart"]
	local AveragePos = self:calculateAveragePosition()
	local AverageMagnitude = self:calculateAverageMagnitude() + self.ZOFFSET or self.ZOFFSET
	
	local StartCF = 
		CFrame.new((Root.CFrame.Position)) 
		* CFrame.Angles(0,math.rad(self.XOFFSET),0)
		* CFrame.Angles(math.rad(self.YOFFSET),0,0)
	
	local GoalCF = StartCF:ToWorldSpace(
		CFrame.new(self.XOFFSET,self.YOFFSET , AverageMagnitude))
	
	local CameraDirection = StartCF:ToWorldSpace(
		CFrame.new(self.XOFFSET, self.YOFFSET, -10000)) 
		* CFrame.new(self.CamPart.Position)
	
	local CameraCF = CFrame.new(self.Spring.Position, CameraDirection.Position) * self.CameraAngle
	
	self.Spring.Target = GoalCF.Position
	self.CamPart.Position = AveragePos
	self.Camera.CFrame = CameraCF
end

function Camera.PlayerRemoving(self: Camera)
	Players.PlayerRemoving:Connect(function(player: Player) 
		if table.find(self.Tracking, player) then
			for i = 1, #self.Tracking do
				if self.Tracking[i] == player then
					table.remove(self.Tracking, i)
				end
			end
		end
	end)
end


return Camera

Yes, check out this post: Type Annotations! A guide to writing Luau code that is actually good

Its long but the worth the time

1 Like

Your code is a good start in creating a class for managing a camera in a Roblox game. However, there are a few improvements and corrections that can be made:

  • Your type declarations are mostly correct, but you’ve misspelled HumanoidRootPart as “HumnaoidRootPart” when accessing it from the character. Fix this typo for accuracy.
  • When creating the CamPart , it’s better to specify its properties directly instead of setting them afterward. This makes the code cleaner and more efficient.
  • In the calculateAveragePosition and calculateAverageMagnitude methods, you’re using self correctly. However, in Update and PlayerRemoving , you should access self using colon syntax (self:methodName() ) rather than dot syntax (self.methodName()).
  • In the PlayerRemoving method, you’re re-connecting to the PlayerRemoving event every time it’s called. This is unnecessary as you’ve already connected it outside the method when the camera is initialized. Remove the reconnection inside this method.
  • In the Update method, Root should be Root.Position when calculating StartCF .
  • Ensure consistency in naming conventions. For example, you have both Track and player variables; stick to one naming convention for clarity.

Here’s the updated version of your code (things will be wrong, im not the best scripter):

-- Update the 'Instance.new' call to specify properties directly
self.CamPart = Instance.new("Part", workspace)
self.CamPart.Anchored = true
self.CamPart.Name = "CamPart"
self.CamPart.CanCollide = false
self.CamPart.Transparency = 1

-- Fix spelling mistake in 'HumanoidRootPart'
local Root = self.Character:FindFirstChild("HumanoidRootPart")

-- Access 'self' using colon syntax
self:calculateAveragePosition()
self:calculateAverageMagnitude()

-- Remove redundant reconnection in 'PlayerRemoving' method

-- Ensure consistency in naming conventions
for _, track in pairs(self.Tracking) do

With these adjustments, your code should work more efficiently and be easier to maintain. Keep up the good work! If you have further questions or need clarification on any point, feel free to ask!

2 Likes

Thanks for the resource, I’ll definetly try to update my code with this

Thanks for the indepth check and feedback :pray: I’ll definetly use this advice for future classes

My pleasure! Good luck in what you are doing! There is a solution button if you don’t mind pressing it, I’m doing a competition against my friend for solutions :grin:.

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