Help With Pet Following System

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I need the pets following the player to be behind them and in rows.
  2. What is the issue? Include screenshots / videos if possible!
    I was able to get the pets behind the player and in rows, but the positioning is off
  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I did look for solutions on the Developer Hub, however none were related to my issue.

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

This is me trying to get the pets lined up in rows of 3
https://gyazo.com/12042f62e9291993327bbc1f3ae9eebf

Code:

Pet System Module (yes this is kind of similar to LuaBearyGood’s Pet Follow Module, I took inspiration)

local m = {}
local active = {}
local players = game:GetService("Players")
local runService = game:GetService("RunService")
local tweenService = game:GetService("TweenService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local settings = require(script:WaitForChild("Settings"))
local globalFloat = 0
local minGlobalFloat = settings.MinFloat
local maxGlobalFloat = settings.MaxFloat
local floatInc = settings.FloatIncrement
local switch = false

function UpdatePetPos()
	if not switch then
		globalFloat += floatInc
		if globalFloat >= maxGlobalFloat then
			switch = true
		end
	else
		globalFloat -= floatInc
		if globalFloat <= minGlobalFloat then
			switch = false
		end
	end
	for playerId, playerPets in pairs(active) do
		local player = players:GetPlayerByUserId(playerId)
		if player then
			local character = player.Character
			if character and character.PrimaryPart then
				playerPets = playerPets:GetChildren()
				for i, pet in pairs(playerPets) do 
					local cf
					local rayOrigin = pet.PrimaryPart.Position + Vector3.new(0, 5, 0)
					local rayDirection = Vector3.new(0, -10000, 0)
					local raycastParams = RaycastParams.new()
					raycastParams.FilterDescendantsInstances = playerPets
					raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
					local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
					local chCfPos = character:GetPrimaryPartCFrame().Position
					local maxOnRow = settings.MaxOnRow
					local round = math.floor(i-(maxOnRow * math.floor((i - 1)/maxOnRow)))
					local negative = 1
					if round / maxOnRow <= 0.5 then
						negative = -negative
					end
					if pet:GetAttribute("FloatType") == "ground" then
						cf = character:GetPrimaryPartCFrame():ToWorldSpace(CFrame.new((3 * ((round * negative) * (settings.SpaceBetweenPets / (math.floor(maxOnRow/round))))), -(chCfPos.Y - (raycastResult or {Position = Vector3.new()}).Position.Y) + (pet.PrimaryPart.Size.Y / 2), settings.SpaceBehindCharacter + (3 * math.floor(((i / 2) + 0.5) - 1))) * CFrame.Angles(0, math.rad(180), 0)) -- i literally gave up here so if anyones reading this and ur like "what is this?!" thats why
					else
						cf = character:GetPrimaryPartCFrame():ToWorldSpace(CFrame.new((3 * ((-(i % 2) + 2) - ((math.clamp(#playerPets - ((math.floor(i / (#playerPets)) * i) * (i % 2)), 1, 2) + 1) / 2))), -(chCfPos.Y - (raycastResult or {Position = Vector3.new()}).Position.Y) + (pet.PrimaryPart.Size.Y / 2) + 2 + globalFloat, 5 + (3 * math.floor(((i / 2) + 0.5) - 1))) * CFrame.Angles(0, math.rad(180), 0))
					end
					tweenService:Create(pet.PrimaryPart, TweenInfo.new(settings.PetSpeed, Enum.EasingStyle.Sine, Enum.EasingDirection.Out, 0, false, 0), {CFrame = cf}):Play()
				end
			end
		else
			active[playerId]:Destroy()
		end
	end
end

function CreateHiddenPets()
	local folder = replicatedStorage:FindFirstChild("HiddenPets")
	if not folder then 
		folder = Instance.new("Folder", replicatedStorage)
		folder.Name = "HiddenPets" 
		return folder
	end
	return folder
end

function CreatePlayerFolder(player)
	local hiddenPets = CreateHiddenPets()
	if not active[player.UserId] then 
		local new = Instance.new("Folder")
		active[player.UserId] = new
		if player.UserId == players.LocalPlayer.UserId then 
			if settings.OwnPetsHidden then 
				new.Parent = hiddenPets
			else
				new.Parent = workspace
			end
		else
			if settings.OtherPetsHidden then 
				new.Parent = hiddenPets
			else
				new.Parent = workspace
			end
		end
	end
end

function m:AddPet(petToClone, floatType, name, player)
	if not player then player = players.LocalPlayer end
	if not petToClone or not player then return end
	CreatePlayerFolder(player)
	local character = player.Character
	local pet = petToClone:Clone()
	pet.Parent = active[player.UserId]
	pet:SetAttribute("FloatType", floatType)
	pet.Name = pet.Name.."_"..(name or #active[player.UserId]:GetChildren())
	local cf = character:GetPrimaryPartCFrame():ToWorldSpace(CFrame.new(0, 0, 5) * CFrame.Angles(0, math.rad(180), 0))
	if character then pet:SetPrimaryPartCFrame(cf) end
	return pet
end

function m:RemovePet(pet, player)
	if not player then player = players.LocalPlayer end
	if not pet or not player then return end
	if not active[player.UserId] then active[player.UserId] = Instance.new("Folder") end
	local found = table.find(active[player.UserId], pet)
	if found then table.remove(active[player.UserId], found); pet:Destroy() end
end

function m:HideOwnPets()
	settings.OwnPetsHidden = true
	local hiddenPets = CreateHiddenPets()
	for id, pets in pairs(active) do
		if id == players.LocalPlayer.UserId then
			pets.Parent = hiddenPets
		end
	end
end


function m:HideOtherPets()
	settings.OtherPetsHidden = true
	local hiddenPets = CreateHiddenPets()
	for id, pets in pairs(active) do
		if id ~= players.LocalPlayer.UserId then
			pets.Parent = hiddenPets
		end
	end
end

function m:ShowOwnPets()
	settings.OwnPetsHidden = false
	local hiddenPets = CreateHiddenPets()
	for id, pets in pairs(active) do
		if id == players.LocalPlayer.UserId then
			pets.Parent = workspace
		end
	end
end

function m:ShowOtherPets()
	settings.OtherPetsHidden = false
	local hiddenPets = CreateHiddenPets()
	for id, pets in pairs(active) do
		if id ~= players.LocalPlayer.UserId then
			pets.Parent = workspace
		end
	end
end

runService:BindToRenderStep('PetPos', 1, UpdatePetPos)

return m

Settings Module

local settings = {
	MinFloat = -0.75,
	MaxFloat = 0.75,
	FloatIncrement = 0.03,
	
	PetSpeed = 0.1,
	
	SpaceBetweenPets = 0.5,
	SpaceBehindCharacter = 5,
	MaxOnRow = 3,
	
	OwnPetsHidden = false,
	OtherPetsHidden = false
}

return settings
1 Like

here is a demo project
Pet Grid.rbxl (34.8 KB)

and this is the script inside the demo project

local runService = game:GetService("RunService")

local character = script.Parent
local rootPart = character.HumanoidRootPart
local pets = {}
local spacing = 4

runService.Heartbeat:Connect(function(deltaTime)
	local columns = math.floor(math.sqrt(#pets))
	local offset = Vector3.new(-columns / 2 * spacing + spacing / 2, 0, 4) 
	for i, pet in pets do
		i -= 1
		local x = i % columns
		local z = math.floor(i / columns)
		pet.CFrame = pet.CFrame:Lerp(rootPart.CFrame * CFrame.new(offset + Vector3.new(x, 0, z) * spacing), 0.1)
	end
end)

while true do
	local part = Instance.new("Part")
	part.Anchored = true
	part.Size = Vector3.new(2, 2, 2)
	part.CanCollide = false
	part.CanQuery = false
	part.CanTouch = false
	part.Parent = workspace
	table.insert(pets, part)
	task.wait(1)
end

this video might help explain how my script works

6 Likes

Wow thank you, this works great!

1 Like