Solid From Above, Empty From Below
A one-way platform compares two heights and flips one property, and that version falls apart the moment a second player joins. Drag one player through it, add another, and watch a single shared property fail them both.
Worth reading first: A Part, and the Four Properties That Matter
A one-way platform compares two heights and flips one property, and that version falls apart the moment a second player joins. Drag one player through it, add another, and watch a single shared property fail them both.
The idea is a comparison of two heights
A one-way platform is solid when you are above it and empty when you are below, so you can jump up through the floor and then stand on it. Every implementation is a variation on one comparison: is the player higher than the platform.
local platform = script.Parent local function isAbove(root) return root.Position.Y > platform.Position.YendY is up, which you established in Part 1, and both positions are the centre of the object rather than its surface. For a platform one stud thick the difference is half a stud and does not matter; for a thick one it does, and the honest comparison adds half the height.
local topSurface = platform.Position.Y + (platform.Size.Y / 2)return root.Position.Y > topSurfaceRead the player's root, not their limbs
The obvious mistake is to compare the height of otherPart — the thing Touched handed you. That is a foot, and a foot is exactly the part of a player that is level with the platform when they land on it.
Compare the HumanoidRootPart instead. It sits at the centre of the character, roughly two studs above their feet, so it is unambiguously above the platform when standing on it and unambiguously below when jumping up through.
platform.Touched:Connect(function(otherPart) local character = otherPart:FindFirstAncestorOfClass("Model") if not character then return end local root = character:FindFirstChild("HumanoidRootPart") if not root then return end platform.CanCollide = root.Position.Y > platform.Position.Yend)CanCollide belongs to the part, not the player
Everything above is correct, tested, and works perfectly. Then a second player joins, and it stops working for both of them at once.
The reason is not in the logic. It is that CanCollide is a property of the part. One part, one value, shared by everybody in the server. The script is trying to store per-player state in a place that can only hold one answer.
local root = character:FindFirstChild("HumanoidRootPart") platform.CanCollide = root.Position.Y > platform.Position.Y
platform.CanCollide → true
With one player this works, and works well. Slide A above the platform and it goes solid; slide below and it lets them through. Now turn on the second player.
Turn on the second player and watch what a Touched event from below does to the person standing on top. Whichever event fired most recently wins, so the platform flickers between solid and intangible as two players move — and a player standing on it drops through a floor that was there a frame ago.
This is worth recognising as a shape of bug rather than a fact about platforms. Any time a per-player decision is written into a property that the part owns, the second player breaks it. A door that opens for whoever has the key, a block that is solid only for players who have finished stage three — same bug, same cause.
Collision groups are the real fix
Roblox has a mechanism for exactly this, and it works because it stops asking one property to hold several answers. A collision group is a named category, and you configure which groups collide with which.
Put the platform in one group, put each player's character in a group of their own, and then turning collision on or off for one player is a change to that player's group rather than to the platform.
local PhysicsService = game:GetService("PhysicsService") PhysicsService:RegisterCollisionGroup("Platform")PhysicsService:RegisterCollisionGroup("PassesThrough") -- Members of PassesThrough ignore the platform entirely.PhysicsService:CollisionGroupSetCollidable("PassesThrough", "Platform", false) platform.CollisionGroup = "Platform"Then, per player, move their character's parts between groups as they rise and fall. Each player's collision is now their own, and one player passing through changes nothing for anybody else.
local function setGroup(character, groupName) for _, descendant in character:GetDescendants() do if descendant:IsA("BasePart") then descendant.CollisionGroup = groupName end endendTwo details that bite. You must set the group on every BasePart of the character, not the model — a model has no CollisionGroup. And registering a group that already exists throws, so a script that runs more than once needs to check first or be somewhere that only runs once.
When to accept the simple version
Collision groups are the correct answer and they are meaningfully more work. There is a real decision here rather than an obvious one.
The simple version is fine when
The obby is single-player, or the platform sits somewhere two players are unlikely to be at once. Ship it, and know exactly what it will do if that assumption breaks.
Use collision groups when
Players will genuinely be there together, or the platform is on the critical path and dropping someone through it ends their run. The cost of the bug is now higher than the cost of the fix.
What is not acceptable is not knowing which you have. A one-way platform written the simple way is a documented limitation if you know about it, and an unexplainable bug report if you do not.
Key takeaways
- A one-way platform is one comparison: is the player higher than the platform's top surface.
- Position is the centre of an object. Add half of Size.Y to compare against the surface players actually stand on.
- Compare the HumanoidRootPart, not the part Touched gave you — that is a foot, and a foot is level with the platform on landing.
- AssemblyLinearVelocity.Y tells you which way they are going, which is more robust than height alone.
- CanCollide belongs to the part. One part, one value, shared by every player in the server.
- With two players the last Touched event wins, and someone standing on the platform falls through a floor that was solid a frame ago.
- This is a shape of bug, not a fact about platforms: per-player state written into a property the part owns always breaks on the second player.
- Collision groups fix it properly by giving each player their own collision, so one player passing through changes nothing for anyone else.
- Set the group on every BasePart of the character — a Model has no CollisionGroup — and registering an existing group throws.
- Shipping the simple version is a legitimate choice. Not knowing which version you shipped is not.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.