Optimising pet follow script

I have a few problems I’m encountering with this script.

  1. Getting pet to ‘idle’ on the floor properly (not still be floating)
  2. Pet not always going to one side of my character, it’ll sometimes go my left or right leg. I want it to always get to one of my character, or even sometimes goes infront of my character

    Is now in front of me
  3. Is there a possible future problem, with a number of players each having this script running (it is a module, cloned into each pet and required when the pet is equipped / it gets destroyed when the player unequips the pet) As it contains a while true do loop, which I fear might be problematic in the future?
return function()
	local Pet = script.Parent
	local Owner = Pet:WaitForChild('Owner')
	
	-- Get Owner properties
	local OwnerCharacter = Pet.Parent
	local OwnerHumanoid = OwnerCharacter:WaitForChild('Humanoid')
	local OwnerRootPart = OwnerCharacter:WaitForChild('HumanoidRootPart')
		
	local Head = OwnerCharacter:WaitForChild('Head')
	local LeftUpperLeg = OwnerCharacter:WaitForChild('LeftUpperLeg')

	local BodyGyro = PetHumanoidRootPart:WaitForChild('BodyGyro')

	-- Get pet properties
	local PetHumanoid = Pet:WaitForChild('Humanoid')
	local PetHumanoidRootPart = Pet:WaitForChild('HumanoidRootPart')
	
	local function Move(target)
		local Direction = (target.Position - PetHumanoidRootPart.Position).unit
		local SpawnPos = PetHumanoidRootPart.Position
		local Pos = SpawnPos + (Direction * 1)
		
		BodyGyro.CFrame = CFrame.new(Pos, Pos + Direction)
		BodyGyro.MaxTorque = Vector3.new(9000, 9000, 9000)
		
		PetHumanoid.PlatformStand = false
	end
	
	local function MoveTo(target)
		PetHumanoidRootPart.BodyPosition.Position = target.Position
		PetHumanoidRootPart.BodyPosition.MaxForce = Vector3.new(10000, 10000, 10000) * PetHumanoidRootPart.Speed.Value
	end
	
	local function MoveLeft()
		PetHumanoidRootPart.BodyPosition.Position = RightUpperLeg.Position + Vector3.new(5, 0, 0)
		PetHumanoidRootPart.BodyPosition.MaxForce = Vector3.new(10000, 10000, 10000) * PetHumanoidRootPart.Speed.Value
		PetHumanoid.PlatformStand = true
	end
	
	OwnerHumanoid.Running:Connect(function(speed)
		if speed ~= 0 then return end
		
		MoveLeft()
	end)
	
	spawn(function()
		while true do
			local Speed = OwnerRootPart.Velocity.Magnitude
			
			if Speed == 0 then				
				MoveLeft()
			elseif Speed > 0 then				
				Move(OwnerRootPart)
				MoveTo(OwnerRootPart)
				PetHumanoidRootPart.Anchored = false
			end
			
			OwnerHumanoid:GetPropertyChangedSignal('Jump'):Connect(function()
				if Speed ~= 0 then return end
				
				MoveLeft()
			end)
		
			wait()
		end
	end)
end
2 Likes

A.
Please do NOT use :Connect inside an infinite loop, as your code does here:

	while true do
		-- ...

		OwnerHumanoid:GetPropertyChangedSignal('Jump'):Connect(function()
			if Speed ~= 0 then return end
			MoveLeft()
		end)

		wait()
	end

Move that GetPropertyChangedSignal():Connect outside, before entering the infinite loop, so you can avoid consuming cpu & memory resources.

B.
Your code does not seem to take into account, the orientation (rotation) of the player’s character, when doing this calculation:

local function MoveLeft()
	PetHumanoidRootPart.BodyPosition.Position = RightUpperLeg.Position + Vector3.new(5, 0, 0)
	-- ...

BTW where is that RightUpperLeg even declared and assigned? - I see LeftUpperLeg is assigned, so I guess the code you posted isn’t the actual code.

You might want to look into using CFrame’s * operator, so the rotation-components of the CFrame is also used, when calculating that 5-stud (left-)offset away from the RightUpperLeg.

C.
Your spawn’ed anonymous-function with that infinite loop, is missing a stop-condition:

	spawn(function()
		while true do
			-- ...
		end
	end)

As you write yourself; “when the pet … gets destroyed”. Yet that infinite loop have no statements, that verify when the pet is ‘not there anymore’. So in best case the anonymous-function will crash (but you probably don’t notice it, due to other problems), and in worst case it will continue running, until server (or client) stops the game.

A simple stop-condition, without knowing how you “destroy” the pet, could be to change the while expression to:

while nil ~= PetHumanoid.Parent do
  -- ...
end

This will ensure, that as long as the PetHumanoid has a non-nil parent, the loop will continue. And then stop, when the parent becomes nil, thereby nicely leaving and ending the anonymous-function (without crashing).

Ok moved the :Connect() function out of the while loop.

The naming was a blunder on my part. RightUpperLeg was supposed to say LeftUpperLeg. I’m not entirely sure what you mean by the rotation stuff though?

As for the loop, is I used

while nil ~= PetHumanoid.Parent do
  print(1)
  -- ...
end

and it would continue to print(1) after the model and script were deleted :confused:

Then try changing it, so it is the Pet.Parent that is tested against, ìn case Pet is the model you do the delete / destroy on.

As you discovered, even if you delete the Script, that does not automatically stop any spawn’ed functions containing loops (unless they error/crash). So that is why I wrote about a ‘stop condition’ for such loops.

For a Position it is just that, a position in the world-space coordinate system, consisting of the three values; X, Y, Z.

So when you only use a position, and then add another “position” (that is; offset or vector) to it, the calculation will follow the coordinate system of the position.

Example: A character’s position is { x=10, y=0, z=5 }. You then want to add an offset of { x=5, y=0, z=0 } to it. The resulting position will be { x=10+5, y=0+0, z=5+0 } => { x=15, y=0, z=5 }.

Now if you wanted that offset to be on “the left-side of the character’s position”, you need to figure out; “where is this left-side anyway?” - In case the character has no possibility to rotate, and always looks towards the coordinate system’s negative Z axis (direction), then you would know, because there is no rotation, that the “left-side” is always in the negative X direction of the position.

But having a character that cannot rotate would be a pretty boring 3D-world game. So you need to consider the rotation (orientation) of the block, to correctly calculate an offset that is “5-studs to the left-side of the block’s position.”

Ok

while Pet.Parent do

end

Seemed to fix the issue of the loop running after being destroyed.

However, there’s still a ton of problems I am facing

  1. The pet still goes to random positions when I stop moving, whether that be in front, behind, left or right of me. It should always revert to my right side. Basically when I move, it follows, when I stop it should go to my right and stop.
  2. Its movement isn’t smooth, but really jagged
  3. It doesn’t stay on the ground (I want it so it’s always touching the ground, like it’s walking)

Updated script

return function()
	local Pet = script.Parent
	local Owner = Pet:WaitForChild('Owner')
	
	-- Get Owner properties
	local OwnerCharacter = Pet.Parent
	local OwnerHumanoid = OwnerCharacter:WaitForChild('Humanoid')
	local OwnerRootPart = OwnerCharacter:WaitForChild('HumanoidRootPart')
		
	local Head = OwnerCharacter:WaitForChild('Head')
	local RightUpperLeg = OwnerCharacter:WaitForChild('RightUpperLeg')
	
	-- Get pet properties
	local PetHumanoid = Pet:WaitForChild('Humanoid')
	local PetHumanoidRootPart = Pet:WaitForChild('HumanoidRootPart')
	
	local BodyGyro = PetHumanoidRootPart:WaitForChild('BodyGyro')
	
	-- Default Values
	local BeingCarried = false
	
	local function Move(target)
		local Direction = (target.Position - PetHumanoidRootPart.Position).unit
		local SpawnPos = PetHumanoidRootPart.Position
		local Pos = SpawnPos + (Direction * 1)
		
		BodyGyro.CFrame = CFrame.new(Pos, Pos + Direction)
		BodyGyro.MaxTorque = Vector3.new(9000, 9000, 9000)
		
		PetHumanoid.PlatformStand = false
	end
	
	local function MoveTo(target)
		PetHumanoidRootPart.BodyPosition.Position = target.Position
		PetHumanoidRootPart.BodyPosition.MaxForce = Vector3.new(10000, 10000, 10000) * PetHumanoidRootPart.Speed.Value
	end
	
	local function MoveRight()
		PetHumanoidRootPart.BodyPosition.Position = RightUpperLeg.Position + Vector3.new(-5, 0, 0)
		PetHumanoidRootPart.BodyPosition.MaxForce = Vector3.new(10000, 10000, 10000) * PetHumanoidRootPart.Speed.Value
		PetHumanoid.PlatformStand = true
	end
	
	OwnerHumanoid.Running:Connect(function(speed)
		if speed ~= 0 then return end
		
		MoveRight()
	end)
	
	OwnerHumanoid:GetPropertyChangedSignal('Jump'):Connect(function()
		local Speed = OwnerRootPart.Velocity.Magnitude
		
		if Speed ~= 0 then return end
				
		MoveRight()
		wait()
	end)
	
	spawn(function()
		while Pet.Parent do
			local Speed = OwnerRootPart.Velocity.Magnitude
			
			if Speed == 0 then
				MoveRight()
			elseif Speed > 0 then
				Move(OwnerRootPart)
				MoveTo(OwnerRootPart)
				PetHumanoidRootPart.Anchored = false
			end
			
			wait()
		end
	end)
end
1 Like