Skip to content
ENSign in

How do I make a pet that follows the player in Roblox?

Move the pet on the client, every frame, with CFrame maths — not with a BodyMover and not on the server. The pet is one anchored model that lerps toward an offset behind the character. The server only decides which pet is equipped; the following itself is local and costs nothing.

In short

  • Anchored parts and RenderStepped, never physics: physics pets fight the character.
  • One offset per pet slot, so two pets never sit inside each other.
  • The server owns which pet is equipped; the client owns where it is.

The pets that feel good are not simulated, they are drawn. A pet moved by physics on the server shoves the player on a slope, lags behind on a 200ms ping and costs a network update every frame. The same pet interpolated on the client sits exactly where it should, for free.

Steps

1. Prepare the model

One Model with a PrimaryPart, every part anchored and non-collidable. A pet that collides is a pet that pushes the player off a bridge.

local function prepare(model: Model): Model
	for _, part in model:GetDescendants() do
		if part:IsA("BasePart") then
			part.Anchored = true
			part.CanCollide = false
			part.CanQuery = false
			part.CanTouch = false
			part.Massless = true
		end
	end
	if not model.PrimaryPart then
		model.PrimaryPart = model:FindFirstChildWhichIsA("BasePart")
	end
	return model
end

2. Give each slot its own offset

Three pets in the same place look like one pet. The offsets are a small table, in studs, relative to the character.

local SLOTS = {
	Vector3.new(-2.5, 1.5, 3),
	Vector3.new(2.5, 1.5, 3),
	Vector3.new(0, 1.5, 4.5),
}

3. Follow on RenderStepped

Lerp is the whole trick: move a fraction of the remaining distance each frame and the pet trails naturally without any easing code.

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer

local function follow(model: Model, slot: number)
	local offset = SLOTS[slot] or SLOTS[1]
	local bob = 0
	RunService.RenderStepped:Connect(function(dt: number)
		local char = player.Character
		local root = char and char:FindFirstChild("HumanoidRootPart")
		if not root or not model.PrimaryPart then return end

		bob += dt
		local goal = root.CFrame * CFrame.new(offset + Vector3.new(0, math.sin(bob * 3) * 0.25, 0))
		-- Face the way the player faces, and keep the pet upright.
		goal = CFrame.new(goal.Position) * CFrame.Angles(0, select(2, root.CFrame:ToEulerAnglesYXZ()), 0)
		-- 0.15 per frame at 60fps is a soft trail; 1 would glue it to the offset.
		model:PivotTo(model.PrimaryPart.CFrame:Lerp(goal, math.min(1, dt * 9)))
	end)
end

4. Equip from the server

The server says which pets are out; the client builds them. The list travels, not the models.

-- Server
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local equipped = Instance.new("RemoteEvent")
equipped.Name = "PetsEquipped"
equipped.Parent = ReplicatedStorage

local MAX_OUT = 3

local function setEquipped(player: Player, petIds: { string })
	local out = {}
	for i = 1, math.min(#petIds, MAX_OUT) do
		out[i] = petIds[i]
	end
	-- Everyone sees everyone's pets, so this goes to all clients.
	equipped:FireAllClients(player, out)
end
-- Client
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local models = ReplicatedStorage:WaitForChild("PetModels")
local equipped = ReplicatedStorage:WaitForChild("PetsEquipped")

local live: { [Player]: { Model } } = {}

equipped.OnClientEvent:Connect(function(owner: Player, petIds: { string })
	for _, m in live[owner] or {} do
		m:Destroy()
	end
	local spawned = {}
	for i, id in petIds do
		local source = models:FindFirstChild(id)
		if source then
			local m = prepare(source:Clone())
			m.Parent = workspace
			follow(m, i)
			spawned[#spawned + 1] = m
		end
	end
	live[owner] = spawned
end)

How to move the pet

MethodFeels rightCosts networkPushes the playerVerdict
Client RenderStepped + LerpyesnothingnoWhat to build
AlignPosition on the serversometimesevery frameyes, on slopesAvoid for pets
BodyPosition / BodyGyronoevery frameyesDeprecated; do not
Welding to the characterno — rigidnothingnoOnly for a backpack, not a pet

FAQ

Should the pet be in the character model?

No. Parent it to workspace (or to a folder in it) and move it with PivotTo. Inside the character it inherits the character's physics and the Humanoid starts treating it as part of the body.

Why does my pet lag behind when the player runs?

Because the lerp factor is too small, or because you are moving it on the server. Raise dt * 9 toward dt * 16 for a tighter follow. If it is on the server, no factor will fix it: the position is arriving over the network.

Do other players see the pet?

Only if the equip event goes to every client, as above, and every client builds the model locally. A pet created on the server is replicated to everyone and costs you an update per frame — which is the thing this design exists to avoid.

How many pets can a player have out?

Three is a sensible cap, and the cap belongs on the server (MAX_OUT). The client could draw thirty; the thing that stops it is that the server never names more than three.

Updated: Tested in Studio: Roblox Studio, October 2026Author: GustavoLicense: Code in this guide may be used in any Roblox experience, including a paid one

Related pack

Brainrot Pet Pack

Brainrot Pet Pack

$150See the pack

Get notified

Coming soon

Drop your email and we'll ping you the moment this pack goes live.