§ 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
endBuilding 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
Roblox shop system
8 files · 1,259 lines, 852 of them the shared PicooFeedback module · one recipe call. Three starter items by default; name your own items and prices in the prompt.
Roblox daily rewards
Example output: 5 files · 170 lines. A 7-day cycle in this example; the model writes the code for your request, so the files and cycle can differ.
How to use Roblox DataStore the right way
1 file · 155 lines · one recipe call. Saves one number per player; name the stat, its starting value and the autosave interval in the prompt.