How it works

  • The server clones each equipped pet into a folder named after its owner, anchored and without collisions.
  • Every client moves all the pets every frame, behind their owners. Movement done on the client is smooth at any ping, and since every client runs the same loop, everyone sees everyone’s pets in the right place.

1. Equip pets (server)

A Script in ServerScriptService. Pet models live in ReplicatedStorage.Pets. Here every player gets a Chick and a Bunny; call equip from your inventory code instead.

-- ServerScriptService > PetService (Script)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local container = Instance.new("Folder")
container.Name = "PlayerPets"
container.Parent = workspace

local function equip(player, petName)
	local folder = container:FindFirstChild(player.Name)
	if not folder then
		folder = Instance.new("Folder")
		folder.Name = player.Name
		folder.Parent = container
	end
	local pet = ReplicatedStorage.Pets[petName]:Clone()
	for _, part in pet:GetDescendants() do
		if part:IsA("BasePart") then
			part.Anchored = true
			part.CanCollide = false
			part.CanQuery = false
		end
	end
	pet.Parent = folder
	return pet
end

Players.PlayerAdded:Connect(function(player)
	equip(player, "Chick")
	equip(player, "Bunny")
end)

Players.PlayerRemoving:Connect(function(player)
	local folder = container:FindFirstChild(player.Name)
	if folder then
		folder:Destroy()
	end
end)

2. Follow the owner (client)

A LocalScript in StarterPlayerScripts. Pets line up behind the player, ease towards their spot and bob a little; tweak the four numbers at the top.

-- StarterPlayerScripts > PetFollow (LocalScript)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local container = workspace:WaitForChild("PlayerPets")

local SPACING = 3.5 -- studs between pets
local BEHIND = 5 -- studs behind the owner
local HEIGHT = -1.5 -- up or down from the owner's root
local SMOOTH = 8 -- higher = snappier

RunService.RenderStepped:Connect(function(dt)
	local alpha = 1 - math.exp(-SMOOTH * dt)
	for _, folder in container:GetChildren() do
		local owner = Players:FindFirstChild(folder.Name)
		local character = owner and owner.Character
		local root = character and character:FindFirstChild("HumanoidRootPart")
		if root then
			local pets = folder:GetChildren()
			for i, pet in pets do
				local x = (i - (#pets + 1) / 2) * SPACING
				local bob = math.sin(os.clock() * 4 + i) * 0.15
				local goal = root.CFrame * CFrame.new(x, HEIGHT + bob, BEHIND)
				pet:PivotTo(pet:GetPivot():Lerp(goal, alpha))
			end
		end
	end
end)

1 - math.exp(-SMOOTH * dt) keeps the easing the same at 30 and at 240 frames per second, so pets don’t lag on slow phones or jitter on fast PCs.

Make it feel like a pet game

  • Walk animation: compare a pet’s position with the last frame; if it moved, play Walk, otherwise Idle. Using Cube Pets? Their README explains how to keep the built-in animator running on pets your own script moves.
  • Many pets: past five or six, place them on a circle around the player instead of a line: CFrame.Angles(0, i / #pets * math.pi * 2, 0) * CFrame.new(0, HEIGHT, BEHIND).
  • Flying pets: raise HEIGHT for birds and dragons only, with an attribute on the model.

Pets to follow you

Our Roblox packs are drag-and-drop .rbxm files with animations in code, rarity effects, studs and smooth looks. Try three of them free under CC BY: Cube Pets Free. For eggs and odds, see the egg hatching guide.

Sources