In this tutorial, you will learn how to make an object align to a surface while also pointing in some direction like this:
- An understanding of vectors and CFrame is required.
The scenario:
In this example, a raycast is projected downwards to get the surface normal of the ground to align onto. We also have the lookvector of the HumanoidRootPart, or whatever the object should face towards.
Here is that info visualized:
Before we figure out how to align the object, we must first know some concepts.
What is CFrame.fromMatrix()?
CFrame.fromMatrix()
creates a CFrame given the position, lookvector, right vector, and upvector as shown:
CFrame.fromMatrix(Position, LookVector, RightVector, UpVector)
We will use CFrame.fromMatrix()
because we need to align the object with a certain UpVector (the ground UpVector) while also pointing the object in a direction (the LookVector of the HumanoidRootPart)
What are Cross Products?
Vector3:Cross()
returns a vector that is perpendicular to the two vectors inputted. For example:
All three of these vectors are perpendicular (90 degrees apart) from each other.
The Problem
If we look back at our scenario, you may notice that the two vectors we have are NOT perpendicular to each other
How can we make all the vectors perpendicular? We also have the RightVector of the HumanoidRootPart!
The RightVector of the HumanoidRootPart is already perpendicular to the Surface Normal! This means that we can take the cross product of it to get the new LookVector!
With these 3 perpendicular vectors, we can now use CFrame.fromMatrix()!!
CFrame.fromMatrix(
Position, -- POSITION OF THE OBJECT
-newRay.Normal:Cross(HumanoidRootPart.CFrame.RightVector), -- LOOKVECTOR (negative because it was facing backwards)
HumanoidRootPart.CFrame.RightVector, --RIGHTVECTOR
newRay.Normal --UPVECTOR
Now if we set the CFrame of an object to this new CFrame.fromMatrix()
every frame, we will get this!
The script I used:
local RunService = game:GetService("RunService")
local LocalPlayer = Players.LocalPlayer
local rParams = RaycastParams.new()
function newPet(character)
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
local testPet = Instance.new("Part") -- Creating the new pet
testPet.Parent = workspace
testPet.Anchored = true
testPet.Size = Vector3.new(2,2,2)
testPet.CanCollide = true
testPet.Material = Enum.Material.Neon
testPet.BrickColor = BrickColor.White()
rParams.FilterDescendantsInstances = {character,testPet}
local connection
connection = RunService.Stepped:Connect(function()
if character == nil then --Stops the connection and kills pet when character is gone
connection:Disconnect()
testPet:Destroy()
return
end
local characterRightCF = humanoidRootPart.CFrame * CFrame.new(4,0,0) -- The origin for the raycast
local newRay = workspace:Raycast(characterRightCF.Position, Vector3.new(0,-1,0) * 25, rParams) -- Raycast to get floor position and normal
if newRay then
testPet.CFrame = CFrame.fromMatrix(
newRay.Position, -- POSITION OF THE OBJECT
-newRay.Normal:Cross(humanoidRootPart.CFrame.RightVector), -- LOOKVECTOR
humanoidRootPart.CFrame.RightVector, --RIGHTVECTOR
newRay.Normal --UPVECTOR
)
end
end)
end
LocalPlayer.CharacterAdded:Connect(newPet)
if LocalPlayer.Character then
newPet(LocalPlayer.Character)
end
This is the first tutorial I have made, please comment feedback!
Edit: @anumiri pointed out a flaw in the way this works. For more accurate rotations, you can follow his instructions here!