How I could make pets file into a semi circle formation?

I am working in one pet system. Everything works fine but the position for each pet is giving some problems. Lest say max pets I can have is 3.

image
And it works

the script is something like this:

local LERP_ALPHA = 0.05;
local pet = script.Parent
local character = script.Parent.Parent

local function Update()

      local petCframe = pet.PrimaryPart.CFrame
      local characterCframe = character.PrimaryPart.CFrame

      local targetCframe = characterCframe:ToWorldSpace(CFrame.new(5,-2.95,0)) --this is difrent for each pet..
      local newCframe = petCframe:Lerp(targetCframe, LERP_ALPHA)

      pet:SetPrimaryPartCFrame(newCframe)
end

game:GetService("RunService").Heartbeat:Connect(Update)

image

Now if lest say I have 2 rabbit and I equip 2 rabbit and other random pet.

image
this is what happen.(the same pet stays in the same position)

I am trying to make something like this…
image

I already tried to look into other posts to see how I could fix this but I dont find any solution

3 Likes

btw I forgot to say one thing.

image
each pet looks like this. (NOTE: model PrimaryPart is “HumanoidRootPart”)

You could have a table/module with different positions and get a random one from it. However make sure u implement a system where u can’t use the same pos another pet is using.

I mean I have to save everything…

I’m sry but I am having trouble understanding ur problem.

Aligning each pet can be simplified using CFrame math. Since each pet will in theory be evenly spaced from each other angle-wise, we can use that to our advantage and find the angle between each pet first.

  • It looks like we can achieve that intended behavior by pretending there is one extra pet on the end of the 180-degree semi-circle. For example, with three pets:

  • This will let us calculate the angle per pet as 180 degrees divided by the number of pets + 1, with the +1 being the extra pet.
local allPets = -- an array of pets
local anglePerPet = 180 / (#allPets + 1)
  • We can create a new CFrame for a pet by finding where in the ordering the pet is in and using the index to multiply by anglePerPet. Note that the index must be stable, although this can be done through :GetChildren().
local petIndex = -- which index the pet has if it was in an array with all the pets

local function Update()
	-- get pet and character Cframes...

	local targetCframe = characterCframe
		-- turn the CFrame to the beginning of the semi-circle
		* CFrame.Angles(0, math.rad(-90), 0) 

		-- turn the CFrame by anglePerPet
		* CFrame.Angles(0, math.rad(petIndex * anglePerPet), 0) 

		-- move it back X studs
		* CFrame.new(0, 0, DISTANCE_FROM_CHARACTER)

	-- lerp the target Cframe and :SetPrimaryCFrame

This would probably be how I would’ve implemented it.

-- set up pet and character

local allEquippedPets = -- an array of all the player's equipped pets
local anglePerPet = 180 / (#allEquippedPets + 1) -- in degrees
local petIndex = table.find(allEquippedPets, pet)
assert(petIndex, "The player doesn't have this pet equipped!")

local DISTANCE_FROM_CHARACTER = 5

local petCframeOffset = CFrame.Angles(0, math.rad(-90), 0) -- turn the CFrame to the beginning of the semi-circle
	* CFrame.Angles(0, math.rad(petIndex * anglePerPet), 0) -- turn the CFrame by anglePerPet
	* CFrame(0, 0, DISTANCE_FROM_CHARACTER) -- move it back X studs

-- maybe connect an event here that updates the variables above?
-- e.g., .ChildAdded/.ChildRemoved event

local LERP_ALPHA = 0.05

local function Update()
	local petCframe = pet.PrimaryPart.CFrame
	local characterCframe = character.PrimaryPart.CFrame

	local targetCframe = characterCframe * petCframeOffset

	local newCFrame = petCframe:Lerp(targetCframe, LERP_ALPHA)
	pet:SetPrimaryPartCFrame(newCframe)
end

game:GetService("RunService").Heartbeat:Connect(Update)

Edit: It could also be simpler to have one script doing all the Cframing so you can use a for loop iterating through allEquippedPets instead of using a petIndex.

4 Likes

I could add one folder inside the character to keep the pets?

That works, yes. Then you can use PetsFolder:GetChildren() to get your allEquippedPets array.

1 Like

I dont really know how I can do this I am very confused

local allEquippedPets = script.Parent.Parent:GetChildren()
local pet = script.Parent

local anglePerPet = 180 / (#allEquippedPets + 1) -- in degrees
local petIndex = table.find(allEquippedPets, pet)
warn(petIndex, "The player doesn't have this pet equipped!")

local DISTANCE_FROM_CHARACTER = 5


local petCframeOffset = CFrame.Angles(0, math.rad(-90), 0) -- turn the CFrame to the beginning of the semi-circle
	* CFrame.Angles(0, math.rad(petIndex * anglePerPet), 0) -- turn the CFrame by anglePerPet
	* CFrame(0, 0, DISTANCE_FROM_CHARACTER) -- move it back X studs

local LERP_ALPHA = 0.05
local character = script.Parent.Parent

local function Update()
	local petCframe = pet.PrimaryPart.CFrame
	local characterCframe = character.PrimaryPart.CFrame

	local targetCframe = characterCframe * petCframeOffset

	local newCFrame = petCframe:Lerp(targetCframe, LERP_ALPHA)
	pet:SetPrimaryPartCFrame(newCFrame)
end

game:GetService("RunService").Heartbeat:Connect(Update)

when you equip the pet

I dont really know how I can apply this in 1 script. I have tried everything but this is being soo hard. I got the folder I can clone the pets into the folder but Idk how I could make they move (in circle) and change position if they get any new pet in that folder.

I can’t believe I made that typo…

On line 13, it’a supposed to be CFrame.new(...), not CFrame(...).

local allEquippedPets = script.Parent.Parent:GetChildren()
local pet = script.Parent

local anglePerPet = 180 / (#allEquippedPets + 1) -- in degrees
local petIndex = table.find(allEquippedPets, pet)
warn(petIndex, "The player doesn't have this pet equipped!")

local DISTANCE_FROM_CHARACTER = 5


local petCframeOffset = CFrame.Angles(0, math.rad(-90), 0) -- turn the CFrame to the beginning of the semi-circle
	* CFrame.Angles(0, math.rad(petIndex * anglePerPet), 0) -- turn the CFrame by anglePerPet
	* CFrame.new(0, 0, DISTANCE_FROM_CHARACTER) -- move it back X studs

local LERP_ALPHA = 0.05
local character = script.Parent.Parent

local function Update()
	local petCframe = pet.PrimaryPart.CFrame
	local characterCframe = character.PrimaryPart.CFrame

	local targetCframe = characterCframe * petCframeOffset

	local newCFrame = petCframe:Lerp(targetCframe, LERP_ALPHA)
	pet:SetPrimaryPartCFrame(newCFrame)
end

game:GetService("RunService").Heartbeat:Connect(Update)

I’m really sorry about answering late, I’ve been very busy!

1 Like

You wouldn’t have to do it all in one script, it still works in separate scripts.

To update the positions, you can try connecting a .ChildAdded and .ChildRemoved event to the folder and redefine all the variables above LERP_ALPHA except for DISTANCE_FROM_CHARACTER.

e.g.,

-- after petCframeOffset is defined,

local function updatePetCframeOffset()
	allEquippedPets = script.Parent.Parent:GetChildren()
	anglePerPet = 180 / (#allEquippedPets + 1)
	petIndex = table.find(allEquippedPets, pet)
	warn(petIndex, "The player doesn't have this pet equipped!")

	petCframeOffset = CFrame.Angles(0, math.rad(-90), 0)
		* CFrame.Angles(0, math.rad(petIndex * anglePerPet), 0)
		* CFrame.new(0, 0, DISTANCE_FROM_CHARACTER)
end

script.Parent.Parent.ChildAdded:Connect(updatePetCframeOffset)
script.Parent.Parent.ChildRemoved:Connect(updatePetCframeOffset)

-- LERP_ALPHA, character, function Update() ...

Its ok sorry for the delay too looks like we have difrent timezones too, it works fine I was thinking this was very hard but seems not complicated.

one last question. How I could put some pets touching on the ground? I could use raycasting right?

Yep, just raycast down 5 or so studs from newCframe.Position and go back up by half the pet’s size in the Y direction.

-- at the start of the script

local RAY_VECTOR = Vector3.new(0, -5, 0)
local DEFAULT_RAY_OFFSET = Vector3.new(0, -3, 0)

-- ...

    -- after defining newCframe,

    local result = workspace:Raycast(newCframe.Position, RAY_VECTOR)
    if result then
        newCframe += result.Position - newCframe.Position
    else
        newCframe += DEFAULT_RAY_OFFSET
    end

    local _, petSize = pet:GetBoundingBox()
    newCframe += Vector3.new(0, petSize.Y / 2, 0)

    pet:SetPrimaryPartCFrame(newCframe)
end
1 Like

Ok last question. When I rejoin with pets equipped I get a small issue

local characterCframe = character.PrimaryPart.CFrame

any way I could try to make the script wait for character.PrimaryPart.CFrame?

You have to use wait for child. It’s not the PrimaryPart, it’s the character not being loaded yet. Use WaitForChild().

  1. What do you mean by “rejoining”? Do you just mean respawning the character?

  2. Assuming rejoining is the character respawning, what do you do with the pets when the character dies? Do you parent them to nil on dying and reparent them when their character is added back, or do all the pets get destroyed and you create new ones when the character respawns?