Hello developers! I’ve been trying to figure out how I can make an optimized Follower like Pet Sim 99 for a couple of days now.
Each player will be able to equip a maximum of 100 pets. The limit of players on the server will be 10 == 1k pets
I have tried so many different ways.
- local script with one loop that handles all pets.
- local script, but with a separate loop for each player and his pets.
- a local script inside Actor, which means Parallel.
- Pet Class (metatables)
Almost every method makes the same fps
But I’ll take the parallel script as an example:
1 player = 100 pets, 200+ fps when not moving, and 160-170fps when moving.
2 players = 100 pets for each, and a total of 200 pets. 140± FPS when not moving and 100± FPS when moving.
But this is on PC, it’s scary to imagine what will happen on phones and weak devices.
And so I really want to know what are the ways to make an optimized Follower
Below I will provide one of the last attempts to write a good Follower
Code :
local Players = game:GetService('Players')
local RS = game:GetService('ReplicatedStorage')
local RunService = game:GetService('RunService')
local Actor : Actor = script.Parent
local player = Players:GetPlayerByUserId(Actor:GetAttribute('OwnerId'))
local PetFolder = workspace:WaitForChild(player.Name..' pets')
local Rendering = require(RS.PetRender)
local Positions = {}
local List = {}
function setPetPos(Pet, Pos)
Positions[Pet] = Pos
end
function updatePositions()
table.clear(List)
List = PetFolder:GetChildren()
local Rows = Rendering.getRows(#List)
local SplitData = Rendering.splitPets(Rows, List)
for rowIndex, RowData in Rows do
local Data = SplitData[rowIndex]
for _petIdex, petModel : Model in Data do
local formula = _petIdex * (math.pi/1.2) / RowData.Pets
setPetPos(petModel,CFrame.new(
math.cos(formula) * RowData.Radius,
0,
2 + math.sin(formula) * RowData.Radius
))
end
RunService.Heartbeat:Wait()
end
end
function getResult(Origin : Vector3)
local dir = Vector3.new(0,-100, 0)
local RayParams = RaycastParams.new()
RayParams.FilterType = Enum.RaycastFilterType.Exclude
RayParams:AddToFilter({
PetFolder,
player.Character
})
return workspace:Raycast(Origin + Vector3.new(0,50, 0), dir, RayParams)
end
function updatePets(dt)
local Character = player.Character
if not Character then
return;
end
local RootPart = Character.PrimaryPart
local InitCharacterCF = RootPart:GetPivot()
local RootCF = CFrame.new(
InitCharacterCF.X,
0,
InitCharacterCF.Z
) * InitCharacterCF.Rotation
for petModel : Model, TargetPosition : CFrame in Positions do
local Primary = petModel.PrimaryPart
local FinalPosition
if not Primary then
continue;
end
FinalPosition = RootCF * TargetPosition
local result = getResult(FinalPosition.Position)
if not result then
continue;
end
task.synchronize()
Primary.CFrame = FinalPosition * CFrame.new(0, result.Position.Y + Primary.Size.Y/2, 0)
end
end
function render()
updatePositions()
RunService.RenderStepped:ConnectParallel(updatePets)
PetFolder.ChildAdded:Connect(function(child: Instance)
Actor:SendMessage('petAdded')
end)
end
Actor:BindToMessage('petAdded', updatePositions)
Actor:BindToMessage('petRemoved', updatePositions)
Actor:BindToMessage('OnStart', render)