I’m trying to make just a simple pet following system and I almost made it but I can’t fix one thing. The animations are really glitchy and not close to smooth even when I’m trying to create them with tweens. Another problem is that I think when I try making the animation smoother it became really inefficient.
Here is a video: https://youtu.be/8t30OoPlAt4
I’m trying to solve the pet system from a long time but this part made everything much harder so I would really appreciate every help.
Here is the code: (server script)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local EquipPetEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("EquipPetEvent")
local UnequipPetEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("UnequipPetEvent")
local D = 5 -- dis behind player for first row
local R = 3 -- dis between rows
local S = 3 -- horizontal spacing between pets
local maxPerRow = 5
-- tween settings
local tweenDuration = 0.3
local bobAmplitude = 0.5
local equippedPets = {}
local petCoroutines = {}
local function calculateTargetCFrame(hrp, formationIndex, totalPets, phaseOffset)
local row = math.ceil(formationIndex / maxPerRow)
local posInRow = (formationIndex - 1) % maxPerRow
local petsInRow = math.min(maxPerRow, totalPets - (row - 1) * maxPerRow)
local behindOffset = -(D + (row - 1) * R) * hrp.CFrame.LookVector
local lateralOffset = (-(petsInRow - 1) / 2 + posInRow) * S * hrp.CFrame.RightVector
local basePos = hrp.Position + behindOffset + lateralOffset + Vector3.new(0, 2, 0)
local yOffset = bobAmplitude * math.sin(2 * math.pi * os.clock() + phaseOffset)
return CFrame.new(basePos + Vector3.new(0, yOffset, 0)) * hrp.CFrame.Rotation * CFrame.Angles(0, math.rad(180), 0)
end
local function startPetTweenLoop(player, pet)
local thread = coroutine.create(function()
local phaseOffset = pet:GetAttribute("PhaseOffset") or 0
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
pet:SetPrimaryPartCFrame(hrp.CFrame)
while pet.Parent and player.Character and player.Character:FindFirstChild("HumanoidRootPart") do
local pets = equippedPets[player]
local formationIndex
for i, p in ipairs(pets) do
if p == pet then
formationIndex = i
break
end
end
if not formationIndex then
break
end
local totalPets = #pets
local targetCFrame = calculateTargetCFrame(hrp, formationIndex, totalPets, phaseOffset)
local tween = TweenService:Create(pet.PrimaryPart, TweenInfo.new(tweenDuration, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), {CFrame = targetCFrame})
tween:Play()
task.wait(0.1)
end
end)
petCoroutines[player] = petCoroutines[player] or {}
petCoroutines[player][pet] = thread
coroutine.resume(thread)
end
EquipPetEvent.OnServerEvent:Connect(function(player, petId)
equippedPets[player] = equippedPets[player] or {}
local petModel = ReplicatedStorage.Pets:FindFirstChild(petId, true)
if petModel then
local clonedPet = petModel:Clone()
clonedPet:SetAttribute("PhaseOffset", math.random() * 2 * math.pi)
if not clonedPet.PrimaryPart then
clonedPet.PrimaryPart = clonedPet:FindFirstChildWhichIsA("BasePart")
end
for _, part in ipairs(clonedPet:GetDescendants()) do
if part:IsA("BasePart") then
part.CanCollide = false
end
end
clonedPet.Parent = workspace
table.insert(equippedPets[player], clonedPet)
startPetTweenLoop(player, clonedPet)
else
warn("Pet model not found for petId:", petId)
end
end)
UnequipPetEvent.OnServerEvent:Connect(function(player, petId)
if equippedPets[player] then
for i, pet in ipairs(equippedPets[player]) do
if pet.Name == petId then
if petCoroutines[player] and petCoroutines[player][pet] then
petCoroutines[player][pet] = nil
end
pet:Destroy()
table.remove(equippedPets[player], i)
break
end
end
end
end)
Players.PlayerRemoving:Connect(function(player)
if equippedPets[player] then
for _, pet in ipairs(equippedPets[player]) do
if pet and pet.Parent then
pet:Destroy()
end
end
equippedPets[player] = nil
petCoroutines[player] = nil
end
end)
Again thanks a lot