§ How to · with Picoo

How to make a Roblox pet system (with AI)

By Sametcan Tasgiran, Founder & Developer·Published ·Updated

A Roblox pet system rests on four server-side parts: a list of pets, a record of who owns which, an equip step the client cannot cheat, and a pet that follows without shoving anyone. Ask Picoo for a pet system and it applies its pet_follower recipe, which builds all four in one server script and a small pet module.

6 files · 1,165 lines · one recipe call. Eggs, rarity, trading and an inventory UI are follow-up prompts that call PetServer.grant and the EquipPet remote.

Pet definitions

Three pets by default in ReplicatedStorage.Pets: Pup (×1.1), Shadow Cat (×1.25) and Dragonling (×2), each with a name, colour and multiplier. Describe your own list and the recipe takes up to 30.

Hover-follow

The equipped pet floats behind and beside its owner (5 studs back, 3 up), bobs gently and turns with them. The server moves it every Heartbeat with a frame-rate-independent lerp.

Physics-safe pet part

The pet is anchored with CanCollide and CanQuery off and Massless on, so it never shoves the player or jitters. It is parked out of the way while the character respawns and comes back with it.

Server-held ownership

Owned pets live in a server table. The EquipPet remote only equips a pet the player already owns (an empty string unequips), so a client cannot hand itself a pet.

Saved between sessions

Owned pets and the equipped pet are stored in the Pets_v1 DataStore: loaded on join, saved on leave and when the server shuts down. If the load fails, nothing is saved that session, so a DataStore outage cannot wipe a player's pets, and on a live server no new pet is granted until the player rejoins; in a Studio play test pets work but are not saved.

Name tag and feedback

Each pet carries a BillboardGui name tag. Granting a pet plays a reveal effect, and equipping one plays a pickup cue.

Files Picoo ships for this prompt

6 files · 1165 lines · one recipe call

ServerScriptService/PetServer.lua

Ownership, DataStore save/load, equip remote, spawn, hover-follow, grant() and multiplier()

282 lines

ReplicatedStorage/Pets.lua

Pet definitions (name, colour, multiplier), follow distance and hover height

20 lines

ReplicatedStorage/Remotes/EquipPet

RemoteEvent the client fires with a pet id to equip (empty string unequips)

instance

Workspace/Pets

Folder the spawned pet parts live in

instance

ReplicatedStorage/PicooFeedback

Shared game-feel module for the reveal and pickup effects

851 lines

StarterPlayer/StarterPlayerScripts/PicooFeedbackClient

Loads the client half of PicooFeedback for every player

12 lines

Sample output: ServerScriptService/PetServer.lua

--!strict
-- Generated by Picoo recipe: pet_follower

-- (excerpt: equip and grant)

-- The client may only ask to equip something it ALREADY OWNS. Without this
-- check the remote hands out every pet in the game to anyone who calls it.
equipRemote.OnServerEvent:Connect(function(player, id)
	if typeof(id) ~= "string" then return end
	if id == "" then
		equipped[player.UserId] = nil
		despawn(player.UserId)
		return
	end
	if not Pets.get(id) then return end
	if not (owned[player.UserId] or {})[id] then return end
	equipped[player.UserId] = id
	spawnPet(player, id)
	-- Only here, not inside spawnPet: spawnPet also runs on load and on every
	-- respawn, and a chime each time the player dies is noise, not feel.
	local spawned = models[player.UserId]
	if spawned then
		Feedback.pickup(spawned, { player = player, keep = true, color = spawned.Color })
	end
end)

--------------------------------------------------------------------
local PetServer = {}

--- Grant a pet. Server-side only — this is how pets enter the game.
--- Returns false when nothing was granted. Callers MUST check it before they
--- take coins or finish a Robux purchase for the pet.
function PetServer.grant(player: Player, id: string): boolean
	local def = Pets.get(id)
	if not def then return false end
	local set = owned[player.UserId]
	if not set then return false end
	-- After a failed load nothing this session is saved, so on a live server a
	-- pet granted now would vanish when the player leaves. Refuse it, with no
	-- reveal: an egg, shop or receipt that checks the result keeps the purchase
	-- unpaid. A Studio play test plays on (nothing is saved there).
	if not loaded[player.UserId] and not playTest[player.UserId] then return false end
	set[id] = true
	-- A new pet is the rarest thing that happens to this player. The equip
	-- below is a confirmation; THIS is the moment.
	Feedback.reveal(nil, "epic", {
		player = player,
		label = def.name,
		position = originOf(player),
		color = def.color,
	})
	return true
end

Building a Roblox pet system

Pets are a staple mechanic on Roblox, and hand-written pets tend to fail physically rather than logically. A pet part with CanCollide on shoves its owner around. An unanchored pet pushed by CFrame every frame fights the physics solver and jitters. A pet parented to Workspace outlives the character that died. And an equip remote that does not check ownership hands out every pet in the game. Picoo's pet_follower recipe is written around those four.

The pet is a 2×2×2 part that is anchored, with CanCollide and CanQuery off and Massless on, moved by the server every Heartbeat toward a point behind and above its owner. The lerp is frame-rate independent, it bobs a little and turns with the player, and while the character is gone it is parked out of the way instead of hanging where the player died. It carries a BillboardGui name tag that stops drawing past 60 studs.

Ownership lives on the server. PetServer keeps each player's owned pets and equipped pet, the EquipPet remote accepts only a pet the player already owns, and new pets enter the game only through PetServer.grant(player, id), which also plays a reveal effect. Both are saved in the Pets_v1 DataStore when the player leaves and when the server shuts down, and loaded again on join. After a failed load nothing is saved, because an empty pet list written on leave would delete every pet the player owns, and on a live server grant returns false for the rest of that session instead of handing out a pet that would vanish on leave. In a Studio play test of an unpublished place (or with Studio API access off), where every load fails, pets work but are not saved.

Each pet has a multiplier (Pup ×1.1, Shadow Cat ×1.25, Dragonling ×2 by default), and PetServer.multiplier(player) returns the equipped one. The recipe stops at that core on purpose: eggs, rarity odds, trading and an inventory UI are follow-up prompts that call grant, fire EquipPet and read the multiplier.

See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.

Frequently asked

How do players get pets?+

Through PetServer.grant(player, id) on the server; it is also exposed as _G.PetServer. Call it from your egg, shop, quest or code-redeem script. It returns false when nothing was granted, for example when the player's saved pets did not load on a live server (in a Studio play test it grants, but nothing is saved), so check it before you take coins or Robux. The EquipPet remote only accepts a pet the player already owns, so a client cannot give itself one.

Can I add more pets?+

Yes. Add an entry to Pets.Defs in ReplicatedStorage.Pets with a name, colour and multiplier, or describe the pets you want in the prompt: the recipe takes up to 30, each with its own multiplier.

Do pets follow the player visually?+

Yes. The equipped pet is a 2×2×2 part with a name tag that hovers 3 studs up, 5 studs back and a little to the side of the player, bobbing and turning with them. The follow distance and hover height are recipe settings. One pet is equipped at a time.

How are pets stored?+

In a DataStore named Pets_v1: the owned pet ids and the equipped pet, read with GetAsync when the player joins and written with SetAsync when they leave and in BindToClose when the server shuts down. If that GetAsync call fails, nothing is written for the rest of the session, so an outage never overwrites the saved pets with an empty list, and on a live server no pet is granted in that session: PetServer.grant returns false. In a Studio play test of an unpublished place (or with Studio API access off), where every load fails, pets work but are not saved. The equipped pet respawns with the player.

Can I add eggs, rarity, trading or an inventory UI?+

Yes, as follow-up prompts on top of the recipe. An egg or shop script calls PetServer.grant and takes payment only when it returns true, an inventory UI fires the EquipPet remote with a pet id, and PetServer.multiplier(player) returns the equipped pet's multiplier for your coin or damage code.

Related Picoo prompts