Hi,
Last time, I was trying to figure out how to use typechecking oop. After learning stuff, I feel that my code is actually very clean and maintainable, and I haven’t felt this in a long time. Is there anyway you can clean this code even more, even though I think it is already clean. Even organizing my methods would be helpful. Thanks!
--!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._Index["sleitnick_trove@1.1.0"]["trove"])
--/ class
local Camera = {}
Camera.__index = Camera
export type ClassType = typeof( setmetatable({} :: {
CamPart: Part,
Player: Player,
CameraAngle: CFrame,
XOFFSET: number,
YOFFSET: number,
ZOFFSET: number,
Damper: number,
Speed: number,
Spring: any,
Tracking: any,
Connections: Trove.ClassType,
Camera: any,
Character: any
},
Camera)
)
function Camera.new(Player: Player): ClassType
local self = {
Player = Player,
Tracking = {Player},
Connections = Trove.new(),
Spring = Spring.new(Vector3.new()),
Camera = workspace.CurrentCamera,
CamPart = Instance.new("Part"),
Character = nil,
-- settings
CameraAngle = CFrame.Angles(math.rad(0), math.rad(0),math.rad(0)),
XOFFSET = 0,
YOFFSET = 3,
ZOFFSET = 10,
Damper = 100,
Speed = 10,
};
setmetatable(self, Camera)
self:_init()
return self
end
--/ private methods
function Camera._characterAdded(self: ClassType, character: Model)
self.Character = self.Character or character
end
function Camera._setupCharacterAddedFunction(self: ClassType)
self.Connections:Connect(self.Player.CharacterAdded, function(character: Model)
self:_characterAdded(character)
end)
if self.Player then
self:_characterAdded(self.Player.Character:: Model)
end
end
function Camera._init(self: ClassType): () -- initilize
self:_setupCharacterAddedFunction()
-- properties
self.CamPart.Anchored = true
self.CamPart.Name = "CamPart"
self.CamPart.Parent = workspace
self.CamPart.CanCollide = false
self.CamPart.Transparency = 1
self.Camera.CameraType = Enum.CameraType.Scriptable
-- set up the damper and speed settings
self.Spring.Damper = self.Damper
self.Spring.Speed = self.Speed
end
function Camera._calculateAveragePosition(self: ClassType)
local Total = Vector3.zero()
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
end
end
return Total / #self.Tracking
end
function Camera._calculateAverageMagnitude(self: ClassType)
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
--/ public
--/ removes player from self.Tracking -> stops tracking the player
function Camera.RemovePlayerFromTracking(self: ClassType, player: Player): ()
local index = table.find(self.Tracking, player)
if index then
table.remove(self.Tracking, index)
end
end
--/ adds player -> self.Tracking -> tracks the player
function Camera.AddPlayerToTracking(self: ClassType, player: Player): ()
if table.find(self.Tracking, player) then
warn(player.Name.." is already added")
else
table.insert(self.Tracking, player)
end
end
--/ gets the current tracking table
function Camera.GetTrackingTable(self: ClassType): ()
return self.Tracking
end
--/ update the camera on BindToRenderStep
function Camera.Update(self: ClassType): ()
while self.Tracking ~= 0 do
warn("No player is seen to track.")
task.wait(1)
end
local Root = self.Character:FindFirstChild("HumanoidRootPart")
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
--/ enables or disables the camera
function Camera.Enable(self: ClassType, enable: Boolean):
if enable then -- on enabled
local function _update()
self.Connections:BindToRenderStep("Camera",
Enum.RenderPriority.Camera.Value + 1, _update)
end
self:Update()
end
--/ seperated instead of an else statement to make it readable
if not enable then -- on not enabled
self.Disconnections:Clean()
end
end
return Camera
PS: any advice that I think is useful will be updated inside the code
this camera is also for a street fighter inspo game