Make Pets always touch the ground

Hello! I am wondering how can I make it so pets will always touch the ground, like in Pet Simulator X, no matter where you go, the pets will always be touching the ground. How can I achieve this? This is the Pet Position script I am currently using, and I am wondering how to make the pet always touch the ground because currently its depending on your avatar size, which could make the pets collide with the floor if their avatar it too tall, or too small.

local runService = game:GetService("RunService")

local playerPets = workspace:WaitForChild("Player_Pets")

local circle = math.pi * 2

local function getPosition(angle, radius)
	local x = math.cos(angle) * radius
	local z = math.sin(angle) * radius
	return x, z
end

local function positionPets(character, folder)
	for i, pet in pairs(folder:GetChildren()) do
--		local radius = 2+#folder:GetChildren()
		local radius = 5
		local angle = i + (circle / #folder:GetChildren())
		local x, z = getPosition(angle, radius)
		local _, characterSize = character:GetBoundingBox()
		local _, petSize = pet:GetBoundingBox()

		local offsetY = - characterSize.Y/2 + petSize.Y/4
		local sin = (math.sin(15 * time() + 1.6)/.5)+1
		local cos = math.cos(7 * time() + 1)/4
		
		if character.Humanoid.MoveDirection.Magnitude > 0 then
			if pet:FindFirstChild("Walks") then
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY+sin, z) * CFrame.fromEulerAnglesXYZ(0,0,cos),0.1))
			else
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY/2+math.sin(time()*3)+1, z),0.1))
			end 
		else
			if pet:FindFirstChild("Walks") then 
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY, z) ,0.1))
			else
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY/2+math.sin(time()*3)+1, z) ,0.1))
			end
		end
	end
end

runService.RenderStepped:Connect(function()
	for _, PlrFolder in pairs(playerPets:GetChildren()) do
		local Player = game.Players:FindFirstChild(PlrFolder.Name) or nil
		if Player ~= nil then
			local character = Player.Character or nil
			if character ~= nil then
				positionPets(character, PlrFolder)
			end
		end
 	end
end)

So how can I achieve this? Thanks!

Any help will be appreciated! :grin:

1 Like

You can use RayCasting to find the ground.

Can you give an example of what you mean? Are there any tutorials or any DevForum post about this that explain it throughly

1 Like

Consider this API reference:
https://developer.roblox.com/en-us/api-reference/function/WorldRoot/Raycast
From there, the position of the ground will be equal to the result of the raycast .Position
https://developer.roblox.com/en-us/api-reference/datatype/RaycastResult

Let me know if you would like a detailed explanation…

I would appreciate it if you could, but would I put a Script inside all the pets? Like how exactly would I do this? Where would I put the script to find the floor beneath the pet model and for it go above the floor beneath it.

You will need to adjust it a bit, because the position is based on the center of the object, and half of it will be going through the ground.

local runService = game:GetService("RunService")

local playerPets = workspace:WaitForChild("Player_Pets")

local circle = math.pi * 2

local function getPosition(angle, radius)
	local x = math.cos(angle) * radius
	local z = math.sin(angle) * radius
	return x, z
end

local function positionPets(character, folder)
	for i, pet in pairs(folder:GetChildren()) do
--		local radius = 2+#folder:GetChildren()
		local radius = 5
		local angle = i + (circle / #folder:GetChildren())
		local x, z = getPosition(angle, radius)
		local _, characterSize = character:GetBoundingBox()
		local _, petSize = pet:GetBoundingBox()

		local offsetY = - characterSize.Y/2 + petSize.Y/4
		local sin = (math.sin(15 * time() + 1.6)/.5)+1
		local cos = math.cos(7 * time() + 1)/4
		
		if character.Humanoid.MoveDirection.Magnitude > 0 then
			if pet:FindFirstChild("Walks") then
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY+sin, z) * CFrame.fromEulerAnglesXYZ(0,0,cos),0.1))
			else
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY/2+math.sin(time()*3)+1, z),0.1))
			end 
		else
			if pet:FindFirstChild("Walks") then 
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY, z) ,0.1))
			else
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY/2+math.sin(time()*3)+1, z) ,0.1))
			end
		end
        pet.PrimaryPart.Position = workspace:Raycast(pet.PrimaryPart.Position, Vector3.new(0,-100,0)).Position
	end
end

runService.RenderStepped:Connect(function()
	for _, PlrFolder in pairs(playerPets:GetChildren()) do
		local Player = game.Players:FindFirstChild(PlrFolder.Name) or nil
		if Player ~= nil then
			local character = Player.Character or nil
			if character ~= nil then
				positionPets(character, PlrFolder)
			end
		end
 	end
end)

To adjust this, you will need to subtract from the Vector3 returned by .Position. Let me know if you need help with that as well.

So when I implemented the script I joined and I equipped a pet and right away it just flung into the sky to never be seen again.

This is because he never did the Instance part of the Raycast.

local runService = game:GetService("RunService")

local playerPets = workspace:WaitForChild("Player_Pets")

local circle = math.pi * 2

local function getPosition(angle, radius)
	local x = math.cos(angle) * radius
	local z = math.sin(angle) * radius
	return x, z
end

local function positionPets(character, folder)
	for i, pet in pairs(folder:GetChildren()) do
--		local radius = 2+#folder:GetChildren()
		local radius = 5
		local angle = i + (circle / #folder:GetChildren())
		local x, z = getPosition(angle, radius)
		local _, characterSize = character:GetBoundingBox()
		local _, petSize = pet:GetBoundingBox()

		local offsetY = - characterSize.Y/2 + petSize.Y/4
		local sin = (math.sin(15 * time() + 1.6)/.5)+1
		local cos = math.cos(7 * time() + 1)/4
		
		if character.Humanoid.MoveDirection.Magnitude > 0 then
			if pet:FindFirstChild("Walks") then
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY+sin, z) * CFrame.fromEulerAnglesXYZ(0,0,cos),0.1))
			else
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY/2+math.sin(time()*3)+1, z),0.1))
			end 
		else
			if pet:FindFirstChild("Walks") then 
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY, z) ,0.1))
			else
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY/2+math.sin(time()*3)+1, z) ,0.1))
			end
		end
        local ground = workspace:Raycast(pet.PrimaryPart.Position, Vector3.new(0,-100,0)
        if ground and ground.Instance then
            local newC = pet.PrimaryPart.CFrame
            newC.Position.Y = ground.Instance.Position.Y + (ground.Instance.Size.Y/2) + (pet.PrimaryPart.Size.Y/2)
            pet:SetPrimaryPartCFrame(newC)
        end
	end
end

runService.RenderStepped:Connect(function()
	for _, PlrFolder in pairs(playerPets:GetChildren()) do
		local Player = game.Players:FindFirstChild(PlrFolder.Name) or nil
		if Player ~= nil then
			local character = Player.Character or nil
			if character ~= nil then
				positionPets(character, PlrFolder)
			end
		end
 	end
end)

(I believe that is the problem, it might not be, so if it’s wrong sorry)

I got the position of the RaycastResult. Your code is close, but it gets the position of the instance it touches, which would be the position of the ground, not the exact point where it intersects.

It is mostly likely due to the constant change between in the air and on the ground. I don’t think CFrame looping is ethical, and rather the use of BodyGyros.

I’m still really confused on what I am supposed to do to get this to work.

yes, which is why I change the y position and nothing else since everything else should already be changed beforehand.

I understand that.

ground.Instance returns the part that the Raycast intersects with. For example, this could be the entire baseplate. Then, using .Position, you are getting the position of that part, NOT the actual point of intersection by the Raycast. Therefore, the position would be inaccurate.

The best and only way to get the exact position of where the Raycast ends is using RaycastResult.Position.

You really didn’t read the whole line of the code, did you?

right here, I got the Y position of the instance,

and right here made it so the position would land right at the top of the part.

then I added the Pet’s size into account so it would be fully (or almost fully depending on how it’s made) above the ground.

I see, thank you for explaining. Although this will minimize the overlap, the issue now is that the position is going “crazy” because it is looping from the air to the ground simultaneously.

By the way it does work (somewhat), but I get these errors. It seems to only work for one pet, then the others it won’t even show them.

It seems that I cannot change the y position like that :sweat_smile: so, here:

local runService = game:GetService("RunService")

local playerPets = workspace:WaitForChild("Player_Pets")

local circle = math.pi * 2

local function getPosition(angle, radius)
	local x = math.cos(angle) * radius
	local z = math.sin(angle) * radius
	return x, z
end

local function positionPets(character, folder)
	for i, pet in pairs(folder:GetChildren()) do
--		local radius = 2+#folder:GetChildren()
		local radius = 5
		local angle = i + (circle / #folder:GetChildren())
		local x, z = getPosition(angle, radius)
		local _, characterSize = character:GetBoundingBox()
		local _, petSize = pet:GetBoundingBox()

		local offsetY = - characterSize.Y/2 + petSize.Y/4
		local sin = (math.sin(15 * time() + 1.6)/.5)+1
		local cos = math.cos(7 * time() + 1)/4
		
		if character.Humanoid.MoveDirection.Magnitude > 0 then
			if pet:FindFirstChild("Walks") then
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY+sin, z) * CFrame.fromEulerAnglesXYZ(0,0,cos),0.1))
			else
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY/2+math.sin(time()*3)+1, z),0.1))
			end 
		else
			if pet:FindFirstChild("Walks") then 
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY, z) ,0.1))
			else
				pet:SetPrimaryPartCFrame(pet.PrimaryPart.CFrame:Lerp(character.PrimaryPart.CFrame * CFrame.new(x, offsetY/2+math.sin(time()*3)+1, z) ,0.1))
			end
		end
        local ground = workspace:Raycast(pet.PrimaryPart.Position, Vector3.new(0,-100,0)
        if ground and ground.Instance then
            local petC= pet.PrimaryPart.CFrame
            pet:SetPrimaryPartCFrame(CFrame.new(Vector3.new(petC.Position.X, ground.Instance.Position.Y + (ground.Instance.Size.Y/2) + (pet.PrimaryPart.Size.Y/2), petC.Position.Z)))
        end
	end
end

runService.RenderStepped:Connect(function()
	for _, PlrFolder in pairs(playerPets:GetChildren()) do
		local Player = game.Players:FindFirstChild(PlrFolder.Name) or nil
		if Player ~= nil then
			local character = Player.Character or nil
			if character ~= nil then
				positionPets(character, PlrFolder)
			end
		end
 	end
end)

I believe this should fix the problem of that.

I believe you need to embed that in Vector3. May be wrong

Ah, yes, that’s true, thanks for pointing that out.

The only issue is that will negate the CFrame rotation, and will only face towards the origin.