If it’s possible to avoid loops, it’s best to do so, both AlignPosition and AlignOrientation allow you to minimize changes and avoid loops and client side controlling via local script (assuming it’s just a basic follow pet system, with more advanced ones you will need to do client side scripting too, but it’s still minimized)
here is an example of this in action, I tried to explain everything in comments, but if you have any questions let me know
Module named PetService in ServerScriptService
local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local petTemplate = Instance.new("Model")
petTemplate.Name = "Pet"
local petPrimary = Instance.new("Part")
petPrimary.Material = Enum.Material.SmoothPlastic
petPrimary.CanCollide = false
petPrimary.Anchored = false
petPrimary.Size = Vector3.new(3, 3, 3)
petPrimary.Shape = Enum.PartType.Ball
petPrimary.BrickColor = BrickColor.Green()
petPrimary.Parent = petTemplate
petTemplate.PrimaryPart = petPrimary
local petInfos = {
Pet1 = {
Model = petTemplate,
PosOffset = Vector3.new(3, 3, 3), -- the attachment offset
AlignPosMaxForce = 20000,
AlignPosResponsiveness = 15,
AlignOriResponsiveness = 20
}
}
local PetService = {Pets = {}, PetInfos = petInfos}
-- returns a CollectionService tag for a player, for tagging and deleting pets
local function getPetTag(player)
return player.Name .. "Pet"
end
-- creates a new pet model, and sets up the constraints
local function createPet(player, character, petInfo)
local petTag = getPetTag(player)
local pet = petInfo.Model:Clone()
local petPrimary = pet.PrimaryPart
local characterPrimary = character.PrimaryPart
local alignPosAttachment0 = Instance.new("Attachment", petPrimary)
local alignPosAttachment1 = Instance.new("Attachment", characterPrimary)
alignPosAttachment1.Position = petInfo.PosOffset
local alignOriAttachment0 = Instance.new("Attachment", petPrimary)
local alignOriAttachment1 = Instance.new("Attachment", characterPrimary)
local alignPosition = Instance.new("AlignPosition")
alignPosition.MaxForce = petInfo.AlignPosMaxForce
alignPosition.Responsiveness = petInfo.AlignPosResponsiveness
alignPosition.Attachment0 = alignPosAttachment0
alignPosition.Attachment1 = alignPosAttachment1
alignPosition.Parent = petPrimary
local alignOrientation = Instance.new("AlignOrientation")
alignOrientation.Responsiveness = petInfo.AlignOriResponsiveness
alignOrientation.Attachment0 = alignOriAttachment0
alignOrientation.Attachment1 = alignOriAttachment1
alignOrientation.Parent = petPrimary
CollectionService:AddTag(pet, petTag) -- to delete the pet when needed using :GetTagged()
petPrimary.CFrame = characterPrimary.CFrame -- moves the pet to the player initially so it doesnt have to fly across the map
pet.Parent = workspace
petPrimary:SetNetworkOwner(player) -- gives client control
end
-- deletes a player's pet model, using CollectionService to retrieve any existing tagged pet
local function deletePet(player)
local petTag = getPetTag(player)
for _, pet in ipairs(CollectionService:GetTagged(petTag)) do
pet:Destroy()
end
end
-- sets a player's pet, calls createPet() if they have a character
function PetService:SetPet(player, petInfo)
if self.Pets[player] then
PetService:UnsetPet(player)
end
self.Pets[player] = petInfo
local character = player.Character
if character then
local humanoid = character:FindFirstChild("Humanoid")
if humanoid and humanoid.Health > 0 then -- creates pet model if player has an alive character
createPet(player, character, petInfo)
end
end
end
function PetService:UnsetPet(player)
if self.Pets[player] then
deletePet(player)
self.Pets[player] = nil
end
end
local function playerAdded(player)
local petTag = getPetTag(player)
PetService:SetPet(player, PetService.PetInfos.Pet1) -- set their pet, can be used outside of module, this is just here for testing
local function characterAdded(character)
local pet = PetService.Pets[player]
if pet and #CollectionService:GetTagged(petTag) == 0 then -- if a pet is set and no existing pet model exists, create one
createPet(player, character, pet)
end
character.Humanoid.Died:Connect(function() -- player died, delete their pet
deletePet(player)
end)
end
characterAdded(player.Character or player.CharacterAdded:Wait()) -- gets the first character, either by getting it directly if it exists, or waiting for it
player.CharacterAdded:Connect(characterAdded) -- connects the CharacterAdded event for any future characters
end
local function playerRemoving(player)
PetService:UnsetPet(player)
end
-- calls the playerAdded() function for all player's that exist before we set up the PlayerAdded event
for _, player in ipairs(Players:GetPlayers()) do
playerAdded(player)
end
Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)
return PetService
you will need to require() this module for the code to run, you can do this by having a script and writing
local PetService = require(game:GetService("ServerScriptService").PetService)