Pet follow module [Simulator style]

Since releasing my 100 free simulator pet models one of my most persistent requests was to make a script which makes pets follow the player, so here we are.


System in action (Ignore bad gif quality)


API:

:AddPet(Player, PetName) Searches the pet folder for a pet by that name.  :AddPet(PetName) Also works and automatically assigns the pet to the local player.
:RemovePet(Player, PetName) Searches the players existing pets and removes a pet by that name.

:HidePets(Player) Hides that players pets from view and stops performing movement calculations on them
:HideOtherPets() Hides all pets except the local players from view and stops performing movement calculations on them
:HideAllPets() Hides all pets from view and stops performing movement calculations on them

:ShowPets(Player) Shows that players pets and continues movement calculations on them
:ShowOtherPets() Shows every players pets and continues movement calculations on them excluding the local player
:ShowAllPets() Shows all pets and continues movement calculations on them


Settings (Located in settings module)

PetFolder - Folder where your pet models should be located

OtherPetsVisible - If you can see the pets of players who join
AllPetsVisible - Mostly for internal stuff, but if false it overrides 'OtherPetsVisible'.

YDrift - The max change on the Y axis that the pet will drift up and down,
YDriftSpeed - The speed at which the pet drifts on its Y axis,
	
Responsivness - How quickly the pets respond to movement by the player,
Radius = How far away [In studs] the pets orbit around the player,

Example Code:

local PetFolder = game.Players.LocalPlayer:WaitForChild('PetFolder')
local HideOthersButton

PetFolder.ChildAdded:Connect(function(Child)
	PetModule:AddPet(Child.Name)
end)

PetFolder.ChildRemoved:Connect(function(Child)
	PetModule:RemovePet(Child.Name)
end)

HideOthersButton.MouseButton1Click:Connect(function()
	PetModule:HideOthersPets()
end)

And just like that in 13 lines of code you have set up a pet following system!


How do I get it?

Here is a model link for the module
Here is an rbxm file of the module (2.3 KB)


Side notes:

Going to be honest I’ve barely done any testing on the API excluding the AddPet function so it is possible they will not work, if so let me know and ill update it when I get the chance.

The pet model must have a primary part else the module will break, in addition to that my module performs no validation checks on the functions you are calling so if you try to add an invalid pet it will error.

I strongly recommend disabling cast shadow for your pets, having hundreds of moving objects is bad enough without having to worry about the lighting engine.

I strongly recommend not having an excess of maybe 300 pets loaded at a time, while I believe my module is pretty performant I would not recommend doing anything stupid with it.

I don’t think I mentioned this in the post since I thought it was obvious but evidently it has caused confusion this module must be run on the client, it may work on the server but it is designed to be used on the client.

130 Likes

He’s done it again folks! Unbelievable!

But this is really helpful, I expect to see tons of simulators with pets pop up on the market.

10 Likes

Wow this is super cool!. Might use in the future!

2 Likes

This is definitely gonna be interesting and an easier way for developers to script pet following. Absolutely stunning.

1 Like

Any particular reason you chose a system like this instead of using Body Movers (BodyPosition/AlignPosition)?

A system using Body Movers and the player given NetworkOwnership means that they are responsible for their own position calculation only and they are replicated to the other clients, which is rather useful especially if people could have hundreds of pets at a single time so that other player’s don’t need to iterate through hundreds of pets, being less taxing for low-end devices. The only downside I suppose is that because they are given network ownership they can make those pets do literally anything.

1 Like

These is severely less performant and will cause numerous problems, looping through pets is significantly more performant.

All pets are not rendered and moved locally therefor they will run depending on the specs of the clients computer this also means the parts don’t have to exist on the server meaning you don’t need to worry about network ownership.

3 Likes

I tested it with 2 players and I couldn’t see the other player’s pets.

2 Likes

May I ask how you are adding the other player’s pets?

3 Likes

PetModule:AddPet(petName) on the client. am I doing it wrong?

2 Likes

That will only add the pet to the local player, I recommend re-reading my API documentation, if you want to add a pet for another player you need to do :AddPet(Player,PetName)

2 Likes

How do I use this? I am not good at scripting and I don’t know how module scripts work. Where do I put the module script and example code script?

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 :slight_smile:

3 Likes

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 :grimacing: 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

2 Likes

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
3 Likes

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.