Pet follow system spacing problems

I am having problems with this system I’ve created (Its not the full finished version), there are 2 types of pets normal and huge, for normal pets the system works great but for huge pets the positioning is off and I’ve spent days trying to figure out a solution but I just cant right now so I’m looking for help.

This is what its doing
image

I want the huge pets the be spaced apart the equivalent distance that the normal pets are but for whatever reason this is quite a challenge

local player = _GLOBAL_.Services.Players.LocalPlayer
local petsDisplayed = {}
local petsDisplayedHuge = {}
local petsOrdered = {}
local petsOrderedHuge = {}
local petRows = {}
local petRowsHuge = {}
local order = 1

_GLOBAL_.Services.RunService.Heartbeat:Connect(function()
	--// Render [ petsDisplayed ]
	local char = player.Character

	if char and #petsOrdered >= 1 or #petsOrderedHuge >= 1 then
		local lookVector = char.PrimaryPart.CFrame.LookVector
		local charLocation = char.PrimaryPart.CFrame.Position
		local rightVector = -char.PrimaryPart.CFrame.RightVector
		local leftVector = char.PrimaryPart.CFrame.RightVector

		local petDistance = 2.5
		local petDistanceSide = 2.5
		local petNumber = 1

		local baseLocation = charLocation + Vector3.new(-lookVector.X * petDistance, -lookVector.Y - 0, -lookVector.Z * petDistance)
		local charLook = charLocation + lookVector
		local lookat = Vector3.new(charLook.X, baseLocation.Y + 0, charLook.Z)
		
		local function RenderPetsOfType(rows: any, petSize: number, lastNormalRow: number)	
			local ordered = petsOrdered if petSize == 2 then ordered = petsOrderedHuge end
			petNumber = 1
			
			for rowNumber, petsPerRow in rows do
				local petsToSide = math.floor(petsPerRow / 2)
				local hasMiddlePet if petsPerRow / 2 ~= petsToSide then hasMiddlePet = true end
				baseLocation = charLocation + Vector3.new(-lookVector.X * (petDistance * (rowNumber + lastNormalRow)), -lookVector.Y + 0, -lookVector.Z * (petDistance * (rowNumber + lastNormalRow)))
				
				--// Pets to the left
				for i = 1, petsToSide, 1 do
					if ordered[petNumber] and ordered[petNumber]['pet'] and ordered[petNumber]['pet']:FindFirstChild('PetCFrame') then
						local addVector = Vector3.new()
						local pet = ordered[petNumber]['pet']
						if not hasMiddlePet then addVector = (leftVector * Vector3.new(petDistanceSide, petDistanceSide, petDistanceSide)) / Vector3.new(2, 2, 2) end

						pet.PetCFrame.Value = pet.PetCFrame.Value:Lerp(CFrame.new(baseLocation + ((leftVector) * i) * petDistanceSide - addVector, lookat), 0.01)
						petNumber += 1
					end
				end

				--// Pets in the middle
				if hasMiddlePet then
					if ordered[petNumber] and ordered[petNumber]['pet'] and ordered[petNumber]['pet']:FindFirstChild('PetCFrame') then
						local pet = ordered[petNumber]['pet']

						pet.PetCFrame.Value = pet.PetCFrame.Value:Lerp(CFrame.new(baseLocation, lookat), 0.01)
						petNumber += 1
					end
				end

				--// Pets to the right
				for i = 1, petsToSide, 1 do
					if ordered[petNumber] and ordered[petNumber]['pet'] and ordered[petNumber]['pet']:FindFirstChild('PetCFrame') then
						local addVector = Vector3.new()
						local pet = ordered[petNumber]['pet']
						if not hasMiddlePet then addVector = (rightVector * Vector3.new(petDistanceSide, petDistanceSide, petDistanceSide)) / Vector3.new(2, 2, 2) end

						pet.PetCFrame.Value = pet.PetCFrame.Value:Lerp(CFrame.new(baseLocation + ((rightVector) * i) * petDistanceSide - addVector, lookat), 0.01)
						petNumber += 1
					end
				end
			end
			
			return #rows
		end
		
		local normalRender = RenderPetsOfType(petRows, 1, 0)
		
		petDistanceSide = 5
		petDistance = 7
		local hugeRender = RenderPetsOfType(petRowsHuge, 2, normalRender)
	end
end)

If you have any better ways of doing this please lmk aswell

If your pets use a Mesh there is an Offset in the properties. Looks like your huge pets are the same size. May be as simple and setting the Offset right. You then could use that on all of them without changing your script at all. Or setting the right Offset vs what side they are on.

They are models atm, the offset would be in the code but I’ve been working on this for so long I cant find where

This looks like offset settings. May just have to guess untill you find what works.
Also unrelated … You’re using Heartbeat in a loop. I’ll bet you could use Stepped
and it would work just as well. Saving a lot of cycles being overused.

Heartbeat is around 240 fps … Stepped is around 60 fps. 60 updates a second should be fine.

yes but they don’t work as intended, petDistance works as if the distance between normal and huge pets not the spacing between the pets themselves

Yea I was using stepped and changed it to heartbeat to see if it made any noticeable difference and just never changed it back

This is hard to test as I don’t have your pets or everything set up to work with your script. So I’m speculating. First thought that comes to mind is to use the small pets as the base and proportional spacing for huge pets. To try and keep it in line I’ll stick to double. You may need to try other settings.

local player = _GLOBAL_.Services.Players.LocalPlayer
local petsDisplayed = {}
local petsDisplayedHuge = {}
local petsOrdered = {}
local petsOrderedHuge = {}
local petRows = {}
local petRowsHuge = {}
local order = 1

_GLOBAL_.Services.RunService.Heartbeat:Connect(function()
	local char = player.Character

	if char and (#petsOrdered >= 1 or #petsOrderedHuge >= 1) then
		local lookVector = char.PrimaryPart.CFrame.LookVector
		local charLocation = char.PrimaryPart.CFrame.Position
		local rightVector = -char.PrimaryPart.CFrame.RightVector
		local leftVector = char.PrimaryPart.CFrame.RightVector

		local petDistance = 2.5
		local petDistanceSide = 2.5
		local baseLocation = charLocation + Vector3.new(-lookVector.X * petDistance, -lookVector.Y, -lookVector.Z * petDistance)
		local charLook = charLocation + lookVector
		local lookat = Vector3.new(charLook.X, baseLocation.Y, charLook.Z)

		local function RenderPetsOfType(rows, petSize, lastNormalRow)
			local ordered = petsOrdered
			if petSize == 2 then 
				ordered = petsOrderedHuge 
				petDistance = petDistance * 2 -- Double the distance for huge pets
				petDistanceSide = petDistanceSide * 2
			end
			
			local petNumber = 1
			
			for rowNumber, petsPerRow in rows do
				local petsToSide = math.floor(petsPerRow / 2)
				local hasMiddlePet = petsPerRow % 2 ~= 0
				local rowBaseLocation = charLocation + Vector3.new(-lookVector.X * (petDistance * (rowNumber + lastNormalRow)), 0, -lookVector.Z * (petDistance * (rowNumber + lastNormalRow)))

				for i = 1, petsToSide do
					if ordered[petNumber] and ordered[petNumber]['pet'] and ordered[petNumber]['pet']:FindFirstChild('PetCFrame') then
						local pet = ordered[petNumber]['pet']
						local addVector = hasMiddlePet and Vector3.new(0, 0, 0) or (leftVector * petDistanceSide) / 2
						pet.PetCFrame.Value = pet.PetCFrame.Value:Lerp(CFrame.new(rowBaseLocation + ((leftVector * i) * petDistanceSide) - addVector, lookat), 0.01)
						petNumber += 1
					end
				end

				if hasMiddlePet and ordered[petNumber] and ordered[petNumber]['pet'] and ordered[petNumber]['pet']:FindFirstChild('PetCFrame') then
					local pet = ordered[petNumber]['pet']
					pet.PetCFrame.Value = pet.PetCFrame.Value:Lerp(CFrame.new(rowBaseLocation, lookat), 0.01)
					petNumber += 1
				end

				for i = 1, petsToSide do
					if ordered[petNumber] and ordered[petNumber]['pet'] and ordered[petNumber]['pet']:FindFirstChild('PetCFrame') then
						local pet = ordered[petNumber]['pet']
						local addVector = hasMiddlePet and Vector3.new(0, 0, 0) or (rightVector * petDistanceSide) / 2
						pet.PetCFrame.Value = pet.PetCFrame.Value:Lerp(CFrame.new(rowBaseLocation + ((rightVector * i) * petDistanceSide) - addVector, lookat), 0.01)
						petNumber += 1
					end
				end
			end
			
			return #rows
		end
		
		local normalRender = RenderPetsOfType(petRows, 1, 0)
		RenderPetsOfType(petRowsHuge, 2, normalRender)
	end
end)

Not tested. Hopefully this will give you an extra setting to use for this. Right now script is set to * 2.
This is also setting up a spot this would be set… even if the petDistanceSide = petDistanceSide * 2 isn’t quite right. This would still be the area you would need to make the calulation in.
(make sure to back up your original script first)

So there is some math you are going to need to do here, but you need to figure out what your base line size is, and then determine the scale size difference from base is.

So for example iif base is 5, then 10 would be a scale factor of 2. 5/5 = 1 so scale of 1.
7/5 = 1.4 scale factor. You then multiply that into your distance calculations.

So when they are arranging themselves, at whatever position a pet is, you should scale how it positions and other around it. You do not demonstrate exactly what you want here in your explanation, but my assumption is if you had a uniform grid, and each pet resented 1 unit on that grid. And you have a pet that is 2 grids in size, you want them to position around it in a way that makes sense. This is the way to do it.

There are quite a few ways you can achieve this based on the exact following pattern you want, this is functionally what you need to do no matter what overall.

If it was me, a pet would take up an area of space that others could be near it, and while they strive for a uniform positioning they don’t require it to be perfect and will be mildly displaced by a larger pet in the grouping.

If you want to do something more dynamic, consider looking at how boids work, and use a variable arriving and hold variable would also likely achieve what you are looking for.

I ended up rewriting the whole system and now it works exactly as intended
image

The problem was with the way the rows were being calculated because whenever you render a huge its start position is the row number X the new separation distance so you have to account for the first rows being smaller than the huge rows