If you are struggling with using the module I recommend downloading the rbxm file which provides a place repro of how to set it up
The rbxm file just gives me the module like the model. I’m pretty sure rbxm is just for models and rblx is for places. Do you mean rblx file?
Oops got confused since some of my modules I give place repros.
Ok so you do need intermediate scripting knowledge to use this module and if you need a basis I would look at the example code provided in the post
This should go in a local script instead StartGui / StarterPlayerScripts
It gives this error when I use it:
Infinite yield possible on ‘Players.BloxianParrot:WaitForChild(“PetFolder”)’ - Studio
Great module, very easy to use, and saved me quite a bit of time! However, I’d like to point out a few bugs that need to be fixed:
When a player dies, the output is flooded with errors:
To fix this, change:
if Character then -- line 52
to:
if Character and Character.PrimaryPart then -- line 52
When a player dies, the HumanoidRootPart is destroyed, but the character still exists, therefore if Character
would still return true.
Also, another thing that needs to be fixed is that you forgot to loop through and add players that already exist in the server. To fix this, add this code somewhere at the bottom of the script:
for _,player in pairs(game.Players:GetPlayers()) do
AddPlayer(player)
end
Full Code
local PetFollowModule = {}
local ModuleFunctions = {}
local ActivePets = {}
local Settings = require(script.Settings)
local LocalPlayer = game.Players.LocalPlayer
local YPoint = 0
local Addition = Settings.YDriftSpeed
local FullCircle = 2 * math.pi
local RunService = game:GetService("RunService")
function SetPetCollisions(Model)
for _, Part in pairs(Model:GetDescendants()) do
if Part:IsA("BasePart") then
Part.CanCollide = false
Part.Anchored = true
end
end
end
function getXAndZPositions(Angle, Radius)
local x = math.cos(Angle) * Radius
local z = math.sin(Angle) * Radius
return x, z
end
function ModuleFunctions:AddPet(Player, PetName)
if not PetName then PetName = Player Player = LocalPlayer end
local PetModel = Settings.PetFolder[PetName]:Clone()
PetModel.Parent = ActivePets[Player.UserId]
if Player.Character then PetModel:SetPrimaryPartCFrame(Player.Character.PrimaryPart.CFrame) end
SetPetCollisions(PetModel)
return PetModel
end
function UpdatePetPositions()
YPoint = YPoint + Addition
if YPoint > Settings.YDrift or YPoint < -1 * Settings.YDrift then Addition = -1 * Addition end
for Id, Folder in pairs(ActivePets) do
if Folder.Parent == game.Workspace then
local Player = game.Players:GetPlayerByUserId(Id)
local Character = Player.Character
if Character and Character.PrimaryPart then
local PetTable = Folder:GetChildren()
local PetTableLength = #PetTable
local Count = 0
for _, Pet in pairs(PetTable) do
Count += 1
local Angle = Count * (FullCircle / PetTableLength)
local X, Z = getXAndZPositions(Angle, Settings.Radius + (PetTableLength / 2))
local Position = (Character.PrimaryPart.CFrame * CFrame.new(X, YPoint, Z)).p
local LookAt = Character.PrimaryPart.Position
local TargetCFrame = CFrame.new(Position, LookAt)
local NewCFrame = Pet.PrimaryPart.CFrame:Lerp(TargetCFrame, Settings.Responsivness)
Pet:SetPrimaryPartCFrame(NewCFrame)
end
end
end
end
end
function ModuleFunctions:RemovePet(Player, PetId)
if not PetId then PetId = Player Player = LocalPlayer end
ActivePets[Player.UserId][PetId]:Destroy()
end
function ModuleFunctions:HidePets(Player)
ActivePets[Player.UserId].Parent = game.ReplicatedStorage
end
function ModuleFunctions:HideOthersPets()
Settings.OtherPetsVisible = false
for Id, Folder in pairs(ActivePets) do
if Id ~= LocalPlayer.UserId then
Folder.Parent = game.ReplicatedStorage
end
end
end
function ModuleFunctions:HideAllPets()
Settings.AllPetsVisible = false
for Id, Folder in pairs(ActivePets) do
Folder.Parent = game.ReplicatedStorage
end
end
function ModuleFunctions:ShowPets(Player)
ActivePets[Player.UserId].Parent = game.Workspace
end
function ModuleFunctions:ShowOthersPets()
Settings.OtherPetsVisible = true
for Id, Folder in pairs(ActivePets) do
if Id ~= LocalPlayer.UserId then
Folder.Parent = game.Workspace
end
end
end
function ModuleFunctions:ShowAllPets()
Settings.AllPetsVisible = true
for Id, Folder in pairs(ActivePets) do
Folder.Parent = game.Workspace
end
end
function AddPlayer(Player)
ActivePets[Player.UserId] = Instance.new("Folder")
if Settings.OtherPetsVisible and Settings.AllPetsVisible then
ActivePets[Player.UserId].Parent = game.Workspace
else
ActivePets[Player.UserId].Parent = game.ReplicatedStorage
end
end
for _, player in pairs(game.Players:GetPlayers()) do
AddPlayer(player)
end
game.Players.PlayerAdded:Connect(AddPlayer)
AddPlayer(game.Players.LocalPlayer)
game.Players.PlayerRemoving:Connect(function(Player)
ActivePets[Player.UserId]:Destroy()
end)
RunService:BindToRenderStep('Update', 1, UpdatePetPositions)
setmetatable(PetFollowModule, {__index = ModuleFunctions})
return PetFollowModule
Can you help me summon the pet?
Script in Workspace:
game.Players.PlayerAdded:connect(function(p)
local petFolder = Instance.new(“Folder”)
petFolder.Name = “PetFolder”
petFolder.Parent = p
local pet = script.Pet
local petClone = pet:Clone()
petClone.Parent = p.PetFolder
end)
Script in StarterPlayerScripts:
local PetFolder = game.Players.LocalPlayer:WaitForChild(‘PetFolder’)
PetFolder.ChildAdded:Connect(function(Child)
PetModule:AddPet(Child.Pet)
end)
PetFollowModule is in serverstorage
local PetFollow
local Player = game.Players.LocalPlayer
function PetController:Start()
PetFollow = require(game.ReplicatedStorage.PetFollowModule)
PetFollow:AddPet(Player, "Dominus Sky")
end
I can only see this. I thought if I give this parameter it will work ??
The client cannot access ServerStorage, therefore you will have to use the pet script through the server.
Keeps on saying it’s an unknown global when I try to use it.
I believe I fixed this in the version of the module I use, not sure if I published it to this post. After school I’ll update the module in the post to the new version
does this PetFolder ScreenGUI exist in the playerGUI’s?
Been aware for a very long time about the fact that this doesn’t support server client relationship very well… I guess thats the result of being tired and not having time to test.
Not really interested in documenting it, but if you want to actually use the infrastructure in a production game here is the fixed version. I would note that the old system’s pet movement code was fine It was just adding / removing pets.
PetFollowModule v3.rbxm (2.6 KB)
Thank you soo much for doing this! It’s amazing!
Does it work like this (this is a local script in starterGui)?
wait(10)
local module = require(workspace.PetFollowModule)
local Player = game.Players.LocalPlayer
module:AddPet(Player, 'Spikey Balloon')
It still only shows the pets for the client, Are you sure you sent the right file? Because I think they are the same.
Thanks!
Adding pets should only be done from the server now, adding them from the client was an exploitable mess.
I tried this on a server script but it returns an error:
game.Players.PlayerAdded:Connect(function(plr)
wait(5)
local module =require(workspace.PetFollowModule)
module:AddPet(plr, 'Spikey Balloon')
end)
I’m I doing it right?
Thank you!
You need to parse a model as the second argument
That’s what I did 'Spikey Balloon'
Is a pet inside of a folder in replicatedStorage and I did configure it:
Thank you!
The module does not work anymore, for some reason the UpdatePetPositions
function is not being called from the BindToRenderStep
, I tried printing “called” every time the function is called and it does not print anything:
Edit: It does equip the pet and puts it in the player’s folder but the pet does not follow the player and there are no errors, here is how I call the function:
And here is how the pet is set up:

Thank you!
Hello! I have experienced the same issue as I was testing out the module but quickly came to fix this. If your still interested this is the fixed script:
local PetFollowModule = {}
local ModuleFunctions = {}
local Folders = {}
local Settings = require(script.Settings)
local LocalPlayer = game.Players.LocalPlayer
local YPoint = 0
local Addition = Settings.YDriftSpeed
local FullCircle = 2 * math.pi
local RunService = game:GetService("RunService")
local IsServer = LocalPlayer
local MasterFolder
local ReplicatedStorageFolder
MasterFolder = game.Workspace:WaitForChild('PetFolder')
ReplicatedStorageFolder = game.ReplicatedStorage:WaitForChild('ReplicatedStorageFolder')
function SetPetCollisions(Model)
for _, Part in pairs(Model:GetDescendants()) do
if Part:IsA("BasePart") then
Part.CanCollide = false
Part.Anchored = true
end
end
end
function getXAndZPositions(Angle, Radius)
local x = math.cos(Angle) * Radius
local z = math.sin(Angle) * Radius
return x, z
end
function UpdatePetPositions()
YPoint = YPoint + Addition
if YPoint > Settings.YDrift or YPoint < -1 * Settings.YDrift then Addition = -1 * Addition end
for _, Folder in pairs(MasterFolder:GetChildren()) do
local Player = game.Players[Folder.Name]
local Character = Player.Character
if Character then
local PetTable = Folder:GetChildren()
local PetTableLength = #PetTable
local Count = 0
for _, Pet in pairs(PetTable) do
Count += 1
local Angle = Count * (FullCircle / PetTableLength)
local X, Z = getXAndZPositions(Angle, Settings.Radius + (PetTableLength / 2))
local Position = (Character.PrimaryPart.CFrame * CFrame.new(X, YPoint, Z)).p
local LookAt = Character.PrimaryPart.Position
local TargetCFrame = CFrame.new(Position, LookAt)
local NewCFrame = Pet.PrimaryPart.CFrame:Lerp(TargetCFrame, Settings.Responsivness)
Pet:SetPrimaryPartCFrame(NewCFrame)
end
end
end
end
function ModuleFunctions:HideOthersPets()
assert(IsServer, 'This function can only be called from the client')
Settings.OtherPetsVisible = false
for Id, Folder in pairs(MasterFolder:GetChildren()) do
if Id ~= LocalPlayer.UserId then
Folder.Parent = ReplicatedStorageFolder
end
end
end
function ModuleFunctions:HideAllPets()
assert(IsServer, 'This function can only be called from the client')
Settings.AllPetsVisible = false
for Id, Folder in pairs(MasterFolder:GetChildren()) do
Folder.Parent = ReplicatedStorageFolder
end
end
function ModuleFunctions:ShowPets(Player)
assert(IsServer, 'This function can only be called from the client')
if ReplicatedStorageFolder:FindFirstChild(Player.Name) then
ReplicatedStorageFolder[Player.Name].Parent = game.Workspace
end
end
function ModuleFunctions:ShowOthersPets()
assert(IsServer, 'This function can only be called from the client')
Settings.OtherPetsVisible = true
for Id, Folder in pairs(ReplicatedStorageFolder:GetChildren()) do
if Id ~= LocalPlayer.UserId then
Folder.Parent = MasterFolder
end
end
end
function ModuleFunctions:ShowAllPets()
assert(IsServer, 'This function can only be called from the client')
Settings.AllPetsVisible = true
for Id, Folder in pairs(ReplicatedStorageFolder:GetChildren()) do
Folder.Parent = MasterFolder
end
end
function ModuleFunctions:AddPet(Player, Pet, Key)
assert(not IsServer, 'This function can only be called from the server')
Key = Key and Key or Pet.Name
local ClonedPet = Pet:Clone()
ClonedPet.Name = Key
SetPetCollisions(ClonedPet)
ClonedPet.Parent = MasterFolder[Player.Name]
end
function ModuleFunctions:RemovePet(Player, Key)
assert(not IsServer, 'This function can only be called from the server')
local Pet = MasterFolder[Player.Name]:FindFirstChild(Key)
if Pet then
Pet:Destroy()
end
end
function AddPlayer(Player)
Folders[Player.UserId] = Instance.new("Folder")
Folders[Player.UserId].Name = Player.Name
Folders[Player.UserId].Parent = MasterFolder
end
function RemovePlayer(Player)
MasterFolder[Player.Name]:Destroy()
end
game.Players.PlayerAdded:Connect(AddPlayer)
game.Players.PlayerRemoving:Connect(RemovePlayer)
for _, Player in pairs(game.Players:GetChildren()) do
AddPlayer(Player)
end
RunService:BindToRenderStep('Update', 1, UpdatePetPositions)
setmetatable(PetFollowModule, {__index = ModuleFunctions})
return PetFollowModule