Player clipping through fast-moving platform

I am using TweenService in order to move my platform upwards.
However, on high speeds, the player clips through it and falls.
I have no idea how to avoid it or detect it. Any help is appreciated.

For the engine to include the platform in physics calculations, you’ll have to use something physics based.

  • AlignPosition, VectorForce, and other mover constraints
  • PrismaticConstraints and similar
  • BodyMovers (deprecated)

Here’s Okeanskiy’s example where he made an elevator using prismatic constraints (ROBLOX Easy Elevator with Prismatic Constraint - 2019 Scripting Tutorial (Floor/Lift System) - YouTube). It’s from 2019 but still very relevant.

My platform is also constantly moving downwards.
And I am not too sure if I can tween the constraints.

The constraints are not tweened, they’re physics based and tweens/direct CFraming are not.

If you set the appropriate actuator type (Motor, Servo), some additional settings - like Velocity, MotorMaxAcceleration, MotorMaxForce → motor; LinearResponsiveness, Speed, ServoMaxForce, TargetPosition → servo - are going to open.

The video above has a working example.

I didn’t like the idea of constraints, so I decided to make a script of my own.
Took a couple of hours but I think it is worth it and others might find this helpful.

In StarterPlayerScriptsStarterCharacterScripts create a LocalScript. Name it however you want.
Paste this code in:

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 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 relativePosition 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
		end
	else
		oldPosition = nil
	end
end)

Ta-da.
The script isn’t perfected, but it is enough for me.

How does it work?

i have zero clue. i was in trans while writing this :sob:

Basically, it makes a Raycast to check what part the player is standing on.

If the player doesn’t stand on anything, it checks if the player was previously standing on anything.
If yes, then we will check if the part that the player was standing on moved.
If yes, then we will snap the player back to it.

Elseif the player is standing on something, it checks if the part moved or not.
If yes, then snap back to the part.

Why LocalScript instead of ServerScript?

I used ServerScript but it turned out to be jittery and didn’t allow for the player to leave the platform. LocalScript does this just fine.

Thank you for your help @waves5217.

EDITED: Reworked the script.

1 Like