Hey everybod.
Recently, I stumped upon a problem - the player didn’t move with the part.
And I absolutely HATE working with constraints…
So, I tried to make a system which would prevent that. it took just 2 hours it is easy tbh
And I want to share it with you!
1. How is it supposed to work?
Well, first things first, what are we even supposed to write in our script? How the heck is it going to work? Let's break it down into smaller steps.-
Get the part that the player is standing on.
We can simply use raycast in order to do that. -
Is the part that we are standing on moving?
By creating a variable for old position of the part, we can easily check that. -
Snap the player back on the part.
:PivotTo()
will do the thing.
Well, now we know what we are supposed to do. Let’s actually make this.
2. Scripting
Planning this was the easiest thing - now, let's script it. First of all, we will raycast to check what part we are standing on. (We will also make an indicator of the ray origin and destination) In ```StarterPlayer``` -> ```StarterCharacterScripts``` create a ```LocalScript``` and write this code:local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local char:Model = script.Parent
local player = Players.LocalPlayer
local rootPart:BasePart = char:WaitForChild("HumanoidRootPart")
if char:WaitForChild("Humanoid").RigType == Enum.HumanoidRigType.R15 then
legHeight = char:WaitForChild("LeftUpperLeg").Size.Y
legHeight += char:WaitForChild("LeftLowerLeg").Size.Y
legHeight += char:WaitForChild("LeftFoot").Size.Y
else
legHeight = char:WaitForChild("LeftLeg").Size.Y
end
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {char}
params.IgnoreWater = true
--// these are for testing where the ray goes
local originPart = Instance.new("Part")
originPart.Transparency = 0.5
originPart.CanCollide = false
originPart.Anchored = true
originPart.Color = Color3.new(1, 0, 0)
originPart.Size = Vector3.new(1.5, 1.5, 1.5)
originPart.Parent = char
local directionPart = Instance.new("Part")
directionPart.Transparency = 0.5
directionPart.CanCollide = false
directionPart.Anchored = true
directionPart.Color = Color3.new(0, 1, 0)
directionPart.Size = Vector3.new(1.5, 1.5, 1.5)
directionPart.Parent = char
RunService.Heartbeat:Connect(function(dt)
local origin = rootPart.Position
originPart.Position = origin
local destination = origin - (Vector3.new(0, legHeight + (rootPart.Size.Y / 2), 0))
directionPart.Position = destination
local result = workspace:Raycast(origin, destination-origin, params)
if result then
print(result)
end
end)
This is what we should get!
Two parts representing the
origin
and destination
and some stuff in console. We can see the instance, where did the raycast collide and blah blah blah…
Next, let’s try to detect if the part is moving or not. For that, we will make a variable oldPosition
to represent the old position of the previousResult
. (We will also check if the part CanCollide
or not)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local char:Model = script.Parent
local player = Players.LocalPlayer
local rootPart:BasePart = char:WaitForChild("HumanoidRootPart")
if char:WaitForChild("Humanoid").RigType == Enum.HumanoidRigType.R15 then
legHeight = char:WaitForChild("LeftUpperLeg").Size.Y
legHeight += char:WaitForChild("LeftLowerLeg").Size.Y
legHeight += char:WaitForChild("LeftFoot").Size.Y
else
legHeight = char:WaitForChild("LeftLeg").Size.Y
end
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {char}
params.IgnoreWater = true
--// these are for testing where the ray goes
local originPart = Instance.new("Part")
originPart.Transparency = 0.5
originPart.CanCollide = false
originPart.Anchored = true
originPart.Color = Color3.new(1, 0, 0)
originPart.Size = Vector3.new(1.5, 1.5, 1.5)
originPart.Parent = char
local directionPart = Instance.new("Part")
directionPart.Transparency = 0.5
directionPart.CanCollide = false
directionPart.Anchored = true
directionPart.Color = Color3.new(0, 1, 0)
directionPart.Size = Vector3.new(1.5, 1.5, 1.5)
directionPart.Parent = char
local lastResult:RaycastResult = nil
local oldPosition = nil
RunService.Heartbeat:Connect(function(dt)
local origin = rootPart.Position
originPart.Position = origin
local destination = origin - (Vector3.new(0, legHeight + (rootPart.Size.Y / 2), 0))
directionPart.Position = destination
local result = workspace:Raycast(origin, destination-origin, params)
if lastResult ~= nil and result == nil then
if lastResult.Instance:IsA("BasePart") then
if lastResult.Instance.Position ~= oldPosition and lastResult.Instance.CanCollide then
print("player slipped of the platform")
end
end
elseif lastResult and result then
if lastResult.Instance:IsA("BasePart") and result.Instance:IsA("BasePart") then
if oldPosition ~= lastResult.Instance.Position and lastResult.Instance == result.Instance and result.Instance.CanCollide then
print("platform is moving")
end
end
end
lastResult = result
if result then
if result.Instance:IsA("BasePart") then
oldPosition = result.Instance.Position
else
oldPosition = nil
end
else
oldPosition = nil
end
end)
Cool.
But, here comes the hardest part - we have to snap the player to the platform.
No worries. I already did this for you.
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local char:Model = script.Parent
local player = Players.LocalPlayer
local rootPart:BasePart = char:WaitForChild("HumanoidRootPart")
if char:WaitForChild("Humanoid").RigType == Enum.HumanoidRigType.R15 then
legHeight = char:WaitForChild("LeftUpperLeg").Size.Y
legHeight += char:WaitForChild("LeftLowerLeg").Size.Y
legHeight += char:WaitForChild("LeftFoot").Size.Y
else
legHeight = char:WaitForChild("LeftLeg").Size.Y
end
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {char}
params.IgnoreWater = true
--// these are for testing where the ray goes
local originPart = Instance.new("Part")
originPart.Transparency = 0.5
originPart.CanCollide = false
originPart.Anchored = true
originPart.Color = Color3.new(1, 0, 0)
originPart.Size = Vector3.new(1.5, 1.5, 1.5)
originPart.Parent = char
local directionPart = Instance.new("Part")
directionPart.Transparency = 0.5
directionPart.CanCollide = false
directionPart.Anchored = true
directionPart.Color = Color3.new(0, 1, 0)
directionPart.Size = Vector3.new(1.5, 1.5, 1.5)
directionPart.Parent = char
local lastResult:RaycastResult = nil
local oldPosition = nil
local relativePosition = nil
RunService.Heartbeat:Connect(function(dt)
local origin = rootPart.Position
originPart.Position = origin
local destination = origin - (Vector3.new(0, legHeight + (rootPart.Size.Y / 2), 0))
directionPart.Position = destination
local result = workspace:Raycast(origin, destination-origin, params)
if lastResult ~= nil and result == nil then
if lastResult.Instance:IsA("BasePart") then
if lastResult.Instance.Position ~= oldPosition and lastResult.Instance.CanCollide then
char:PivotTo(
CFrame.new(
lastResult.Instance.Position + relativePosition
) *
CFrame.Angles(
math.rad(rootPart.Rotation.X),
math.rad(rootPart.Rotation.Y),
math.rad(rootPart.Rotation.Z)
)
)
end
end
elseif lastResult and result then
if lastResult.Instance:IsA("BasePart") and result.Instance:IsA("BasePart") then
if oldPosition ~= lastResult.Instance.Position and lastResult.Instance == result.Instance and result.Instance.CanCollide then
char:PivotTo(
CFrame.new(
lastResult.Instance.Position + relativePosition
) *
CFrame.Angles(
math.rad(rootPart.Rotation.X),
math.rad(rootPart.Rotation.Y),
math.rad(rootPart.Rotation.Z)
)
)
end
end
end
if result then
if result.Instance:IsA("BasePart") then
relativePosition = rootPart.Position - result.Instance.Position
end
end
lastResult = result
if result then
if result.Instance:IsA("BasePart") then
oldPosition = result.Instance.Position
else
oldPosition = nil
end
else
oldPosition = nil
end
end)
I will try to explain what it does:
When we are standing on a platform, it will write the relativePosition
- which is player’s position relative to the platform. Then, when we detect that the player slipped off or the platform is moving and snap the player back.
Just comment the parts out and…
3. Some clarification
Finished script
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local char:Model = script.Parent
local player = Players.LocalPlayer
local rootPart:BasePart = char:WaitForChild("HumanoidRootPart")
if char:WaitForChild("Humanoid").RigType == Enum.HumanoidRigType.R15 then
legHeight = char:WaitForChild("LeftUpperLeg").Size.Y
legHeight += char:WaitForChild("LeftLowerLeg").Size.Y
legHeight += char:WaitForChild("LeftFoot").Size.Y
else
legHeight = char:WaitForChild("LeftLeg").Size.Y
end
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {char}
params.IgnoreWater = true
--// these are for testing where the ray goes
--local originPart = Instance.new("Part")
--originPart.Transparency = 0.5
--originPart.CanCollide = false
--originPart.Anchored = true
--originPart.Color = Color3.new(1, 0, 0)
--originPart.Size = Vector3.new(1.5, 1.5, 1.5)
--originPart.Parent = char
--local directionPart = Instance.new("Part")
--directionPart.Transparency = 0.5
--directionPart.CanCollide = false
--directionPart.Anchored = true
--directionPart.Color = Color3.new(0, 1, 0)
--directionPart.Size = Vector3.new(1.5, 1.5, 1.5)
--directionPart.Parent = char
local lastResult:RaycastResult = nil
local oldPosition = nil
local relativePosition = nil
RunService.Heartbeat:Connect(function(dt)
local origin = rootPart.Position
--originPart.Position = origin
local destination = origin - (Vector3.new(0, legHeight + (rootPart.Size.Y / 2), 0))
--directionPart.Position = destination
local result = workspace:Raycast(origin, destination-origin, params)
if lastResult ~= nil and result == nil then
if lastResult.Instance:IsA("BasePart") then
if lastResult.Instance.Position ~= oldPosition and lastResult.Instance.CanCollide then
char:PivotTo(
CFrame.new(
lastResult.Instance.Position + relativePosition
) *
CFrame.Angles(
math.rad(rootPart.Rotation.X),
math.rad(rootPart.Rotation.Y),
math.rad(rootPart.Rotation.Z)
)
)
end
end
elseif lastResult and result then
if lastResult.Instance:IsA("BasePart") and result.Instance:IsA("BasePart") then
if oldPosition ~= lastResult.Instance.Position and lastResult.Instance == result.Instance and result.Instance.CanCollide then
char:PivotTo(
CFrame.new(
lastResult.Instance.Position + relativePosition
) *
CFrame.Angles(
math.rad(rootPart.Rotation.X),
math.rad(rootPart.Rotation.Y),
math.rad(rootPart.Rotation.Z)
)
)
end
end
end
if result then
if result.Instance:IsA("BasePart") then
relativePosition = rootPart.Position - result.Instance.Position
end
end
lastResult = result
if result then
if result.Instance:IsA("BasePart") then
oldPosition = result.Instance.Position
else
oldPosition = nil
end
else
oldPosition = nil
end
end)
Why not use constraints?
You can. But, I am not too familiar with them.
Plus there is sometimes no way to implement some things with constraints rather than just changing the position of a part.
Why local script?
- Not jittery
- Less chance of player falling from the platform
- High ping will not interrupt the script
Author notes
If I made any grammar mistakes, I’d be happy if you tell me.
If you can improve da script, I’d also be happy :)
Edits
Jul. 30 2023 - Fixed some spelling
Jul. 30 2023 (2) - Reworked script for parts that cannot collide