Hello guys, I am working on adding a script to my game that moves pets to follow the player. The problem is that it is all running on the server, and with a “while true do” for up to 20 players. So I am scared that this will lag but I have no way to accurately test if it will. All of this runs on a server script.
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
local pet = game:GetService("ReplicatedStorage").Pets.Bodies.PetModel:Clone()
local torso
local dead = false
local inProg = false
local humanoid = character:WaitForChild("Humanoid")
if humanoid.RigType == Enum.HumanoidRigType.R15 then
torso = player.Character:WaitForChild("UpperTorso")
elseif humanoid.RigType == Enum.HumanoidRigType.R6 then
torso = player.Character:WaitForChild("Torso")
end
pet.Parent = character
humanoid.Died:Connect(function()
dead = true
end)
while true do
if dead then pet:Destroy() break end
local p = torso.CFrame * Vector3.new(2,2,2)
pet.BodyGyro.CFrame = torso.CFrame * CFrame.Angles(0,(3*math.pi)/2,0)
pet.BodyPosition.Position = Vector3.new(p.x,p.y,p.z)
wait()
end
end)
It’s always worth commenting your code so that when you go back to it a month later, you can quickly look through to know exactly what it is doing.
Never put a while loop inside CharacterAdded because if another character is loaded without the humanoid dying, say the character falls off the map for example, then the loop will never end, and you’ll end up with two infinite loops, whilst since the Character would be destroyed by debris service the pet wouldn’t exist, you’d get errors from attempting to index it.
Instead, I’d connect to Heartbeat for any pet movement code.
I’d also store a table of pets by player, and at the start of character added check the table for any pets already owned by that player and destroy them there.
local PetDictionary = {}
game.Players.PlayerAdded:Connect(function(Player)
Player.CharacterAdded:Connect(function(Character)
local OldPet = PetDictionary[Player]
if OldPet then OldPet:Destroy() end
--When a character spawns, if there is a pet from their previous life, it is removed
local Humanoid = Character:WaitForChild("Humanoid")
local Pet = game:GetService("ReplicatedStorage").Pets.Bodies.PotModel:Clone()
Pet.Parent = Character
--Load the pet Instance, and parent it to the Character.
Humanoid.Died:Connect(function()
Pet:Destroy()
PetDictionary[Player] = nil
end)
--If the humanoid dies, Destroy the pet and remove the dictionary entry
end)
end)
game:GetService("RunService").Heartbeat:Connect(function(dT)
for Player,Pet in pairs(PetDictionary) do
if Player and Player.Character then
local Root = Player.Character.PrimaryPart
--Will get the HumanoidRootPart of the Character
Pet.BodyPosition.Position = (Root.CFrame * CFrame.new(2,2,2)).p
--Move to Root position + (2,2,2) in relative space
Pet.BodyGyro.CFrame = (Root.CFrame-Root.Position) * CFrame.Angles(0, 3*math.pi/2, 0)
end
end
end)
Using the Player as the key of the table is fine even if a player leaves, the Player object is destroyed regardless of the reference and becomes nil. The pet is a descendant of the Character and will be destroyed too, but it just leaves us with a nice clean slate with no memory risks whilst giving us an easy tool to get a Pet from a Player object.
Actually, I ended up coming up with a different solution and I am curious how do you think it compares to yours. I ended up using SetNetworkOwner of the pet brick and just used the same script as before but in a local script.
--SERVER SCRIPT
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
local rootPart = game:GetService("ReplicatedStorage").Pets.Bodies.PetModel:Clone()
rootPart.Parent = character
print(rootPart.Parent.Name)
while not rootPart:IsDescendantOf(workspace) do
wait()
end
rootPart:SetNetworkOwner(game.Players:GetPlayerFromCharacter(character))
end)
end)
--LOCAL SCRIPT
local plr = game.Players.LocalPlayer
repeat wait() until plr.Character
local chr = plr.Character
plr.CharacterAdded:connect(function(char)
chr = char
local pet = chr:WaitForChild("PetModel")
local torso
local dead = false
local inProg = false
local humanoid = chr:WaitForChild("Humanoid")
if humanoid.RigType == Enum.HumanoidRigType.R15 then
torso = plr.Character:WaitForChild("UpperTorso")
elseif humanoid.RigType == Enum.HumanoidRigType.R6 then
torso = plr.Character:WaitForChild("Torso")
end
humanoid.Died:Connect(function()
dead = true
end)
while wait() do
if dead then pet:Destroy() break end
local p = torso.CFrame * Vector3.new(2,2.4,2)
pet.BodyGyro.CFrame = torso.CFrame * CFrame.Angles(0,(3*math.pi)/2,0)
pet.BodyPosition.Position = Vector3.new(p.x,p.y,p.z)
end
end)
pet = chr:WaitForChild("PetModel")
local torso
local dead = false
local inProg = false
local humanoid = chr:WaitForChild("Humanoid")
if humanoid.RigType == Enum.HumanoidRigType.R15 then
torso = plr.Character:WaitForChild("UpperTorso")
elseif humanoid.RigType == Enum.HumanoidRigType.R6 then
torso = plr.Character:WaitForChild("Torso")
end
humanoid.Died:Connect(function()
dead = true
end)
while wait() do
if dead then pet:Destroy() break end
local p = torso.CFrame * Vector3.new(2,2.4,2)
pet.BodyGyro.CFrame = torso.CFrame * CFrame.Angles(0,(3*math.pi)/2,0)
pet.BodyPosition.Position = Vector3.new(p.x,p.y,p.z)
end
If the local script was a CharacterScript then you could just do LocalPlayer.Character or script.Parent rather than connecting to the CharacterAdded event, and it also means the script would be destroyed when a new Character loads so the while loop wouldn’t be a problem in that case.