Hello, all! I’m here to discuss a very odd behavior that I’m having with a CLIENT script that makes a flat platform part tilt based on wherever the LOCAL player’s character is currently sitting on it.
A couple of notes about this:
-
The platform part itself was generated locally by another clientside script specifically a LOCALSCRIPT instance, and the script that controls the tilt effect is a client-context script, making, as far as I know, the CLIENT responsible for all physics behavior/network ownership for the part and its relevant constraints.
-
Mechanically speaking, it is pretty simple in setup. I force the position of the platform to a point in space using an AlignPosition constraint with two attachments: One on the platform and one on another anchored part at the position I want. The orientation of platform is regulated by an AlignOrientation constraint connected by the same two attachments. The base mechanics in that respect work just fine.
-
As far as script logic goes, I generate a small touchpad block that rests on the absolute top of the platform that serves as a “softened” way of detecting whether or not the LOCAL PLAYER’s character is currently touching it or not. I soften it because of how rough the touch/touchEnded events can be whenever an object touches two flat surfaces are touching “flush” rather than a collidable object “falling into” the a non-collidable but touchable object. The touchpad really isn’t relevant and the touch/touchEnded logic is stable.
-
*Here’s how the runtime logic works: When I have confirmed that the local player’s character’s rig is touching it, I significantly lower to torque on the alignOrientation constraint so that the platform gives out and tilts toward wherever the rig currently is resting on the platform. When they leave, the torque resets to maximum power so that the platform can correct its orientation will all the force that is needed to get back to its default orientation.
Problem
- ** I INTENDED to make sure that the platform dropping effect ONLY occurs when the local player’s character is touching it so that other player’s don’t tilt the platform and interfere with the local player’s play. The problem is, the tilt physics STILL seem to occur even when ANOTHER player is touching the platform and the main player is just watching it uninvolved. What should happen is the platforms should remain stagnant until the local player touches them.
Workaround
I figured out that if you keep the collidablity of the platform part disabled until it is confirmed that the local player’s rig is currently touching its touchpad, you don’t get that unexpected physics coupling. While this works, it seems a bit hackey and unecessary and not really addressing the root of the problem. Can anyone tell me what is going wrong here? Or is it just some odd bug from the engine? Remember, the platform part was created by a clientside script and the tilt logic is ALSO handled by another clientside script, so, in theory, one client shouldn’t even “see” the same platform instance as the other, let alone affect the physics of it.
I have attached a copy of the script snippet below for the platform tilt logic, with the collision workaround etched out. I have also attached two videos: One WITH the collision workaround and one WITHOUT, to show you the fundamental difference in behavior.
REMEMBER: The tilt physics shouldn’t couple with players OTHER than the local player. The expected behavior is that the platform doesn’t tilt if another player touches the local player’s platform and the local player is uninvolved.
--This script serves as a handler for platforms that retain their position but can have their orientation affected whenever the local player's rig lands on them.
local Players = game:GetService("Players")
local localPlayer = Players.LocalPlayer
local platform = script.Parent
local GRAVITY = game:WaitForChild("Workspace").Gravity
local LAST_VERIFIED_LOCAL_PLAYER_SPHERE = nil --global variable that tracks the last verified sphere pertaining to a rig from the local player. It is updated on a new touch start and, if available, it is used as a fallback should the character be an old rig no longer aliased to the local player during a respawning.
local CURRENTLY_TOUCHING = false --global flag logic regarding whether or not the LOCAL player is currently touching the platform or NOT.
local PASSIVE_TORQUE = nil --global value that holds the "passive" torque value for the platform. Passive torque is a torque that passively tries to correct the orientation of the platform WHILE the player's rig is on it. The torque is low, so the rig's weight will overpower it. It will NOT be the torque when the player is not on it.
local alignOrientation = nil --physics constraint that regulates the current orientation of the platform...
--v FUNCTIONS START
local function getPassiveTorque() --v This function provides a reasonable amount of "passive" torque that eventually pulls the platform back to its expected orientation.
local mass = platform.Mass
local torque = mass * GRAVITY * 0.035
return torque
end --^
local function buildTouchPad(mainBrick, mainFolder) --v This function builds a short touchable but NOT collidable "layer" directly on top of the main platform to help pad touching/not touching logic for smoother and more efficent and accurate gameplay.
local touchpad = Instance.new("Part") --v Building touchpad...
touchpad.Name = "Touchpad"
touchpad.CanCollide = false
touchpad.CanQuery = false
touchpad.CanTouch = true
touchpad.Size = Vector3.new(mainBrick.Size.X, 2, mainBrick.Size.Z)
touchpad.EnableFluidForces = false
touchpad.Anchored = true
touchpad.Transparency = 1
local localOffset = CFrame.new(0, (mainBrick.Size.Y/2) + (touchpad.Size.Y/2), 0)
touchpad.CFrame = mainBrick.CFrame * localOffset
touchpad.Parent = mainFolder --^
local touchpadAttachment = Instance.new("Attachment") --v Building attachments for platform and touchpad that will be used to connect them together...
local mainBrickAttachment = Instance.new("Attachment")
mainBrickAttachment.CFrame = localOffset
touchpadAttachment.Parent = touchpad
mainBrickAttachment.Parent = mainBrick --^
local rigidConstraint = Instance.new("RigidConstraint") --v Building rigid constraint to hold touchpad to platform...
rigidConstraint.Attachment0 = mainBrickAttachment
rigidConstraint.Attachment1 = touchpadAttachment
rigidConstraint.Parent = mainFolder --^
touchpad.Anchored = false
return touchpad
end --^
local function buildPivotMechanics() --v This function builds a platform whose orientation can change based on an uneven physical load on it, like a player's rig. It's designed in such a way that there's minimal corrective torque that will eventually pull the platform back to its expected orientation after some delay.
local mainFolder = Instance.new("Folder") --v Building organizational folder for platform mechanical stuff and organizing it...
mainFolder.Name = "Pivotable_Platform"
mainFolder.Parent = platform.Parent
platform.Parent = mainFolder
platform.Name = "Platform" --^
local platformAttachment = Instance.new("Attachment") --v Adding in attachment for the main platform used for orientation regulation...
platformAttachment.Orientation = platform.Orientation
platformAttachment.Parent = platform --^
PASSIVE_TORQUE = getPassiveTorque()
alignOrientation = Instance.new("AlignOrientation") --v Setting up alignOrientation constraint that MARGINIALLY regulates the orientation of the platform to what its initial orientation was. It is designed to resist orientation changes, but can't compensate fully for whenever a rig is on it.
alignOrientation.Mode = Enum.OrientationAlignmentMode.OneAttachment
alignOrientation.Attachment0 = platformAttachment
alignOrientation.RigidityEnabled = false
alignOrientation.MaxAngularVelocity = 5
alignOrientation.MaxTorque = math.huge --by default, while the player isn't on the platform, the corrective orientation torque is always at its maximum, so that it can always go back to its original orientation asap whenever other objects/players step on it. It only loosens while the local player touches it.
alignOrientation.Responsiveness = 20
alignOrientation.Parent = platform --^
local positioningPoint = Instance.new("Part") --v Building part point in space designed to house attachment serving as a reference point for where, positionally, the platform should be. Please note that this does NOT regulate the orientation of the platform.
positioningPoint.Name = "positioningPoint"
positioningPoint.EnableFluidForces = false
positioningPoint.Transparency = 1
positioningPoint.Anchored = true
positioningPoint.Size = Vector3.new(0.1, 0.1, 0.1)
positioningPoint.CanCollide = false
positioningPoint.CanTouch = false
positioningPoint.CanQuery = false
positioningPoint.Position = platform.Position
positioningPoint.Parent = mainFolder --^
local positioningPointAttachment = Instance.new("Attachment") --v Building in attachment into positioning point...
positioningPointAttachment.Parent = positioningPoint --^
local alignPosition = Instance.new("AlignPosition") --v Building physics constraint that strictly holds up the position of the platform but doesn't regulate its orientation...
alignPosition.Enabled = true
alignPosition.Mode = Enum.PositionAlignmentMode.TwoAttachment
alignPosition.RigidityEnabled = true
alignPosition.Attachment0 = platformAttachment
alignPosition.Attachment1 = positioningPointAttachment
alignPosition.Parent = mainFolder --^
platform.Anchored = false
--platform.CanCollide = false --due to unexpected physics conflicts with other clients, we make sure that the platform cannot be collided with until the local player touches it...
return mainFolder
end --^
local function onTouch(hit) --v this function handles, if the conditions allow, for the enabling of the INFLUENTIAL forces on the character if their sphere (if present) is touching it.
local success, result = pcall(function()
if CURRENTLY_TOUCHING == false then
if hit.Name == "Main_Sphere" then
local hitParent = hit.Parent
if hitParent then
local player = Players:GetPlayerFromCharacter(hitParent)
if player == localPlayer then --making sure that ONLY the local player themselves can trip this.
local humanoid = hitParent:FindFirstChild("Humanoid")
if humanoid and alignOrientation and PASSIVE_TORQUE then --verifying integrity of player's character rig and platform...
if humanoid.Health > 0 then
CURRENTLY_TOUCHING = true
--platform.CanCollide = true --if the local player is touching the platform touchpad, we turn on the collisions to mitigate physics interference from other clients because Roblox is... well... Roblox...
LAST_VERIFIED_LOCAL_PLAYER_SPHERE = hit --once we have verified that the rig sphere belongs to a character from the local player, we update it to keep it in peristant memory. We do it this way because we cannot necessary verify the character's alias to a player properly if they die and/or respawn WHILE still touching the pad, as the character alias for the local player will change reference to the RESPAWNED character rig.
alignOrientation.MaxTorque = PASSIVE_TORQUE --we passify the corrective torque while the local player is touching it. Otherwise, the corrective torque goes back up to the maximum allowed force for correction. This is designed this way to mitigate other players from interfering with the behavior of the platform. Only the LOCAL player should affect them.
end
end
end
end
end
end
end)
pcall(function() --v If anything unexpected occurs, we reset the platform runtime logic to default values if possible...
if success == false then
CURRENTLY_TOUCHING = false
LAST_VERIFIED_LOCAL_PLAYER_SPHERE = nil
if platform then
--platform.CanCollide = false
end
if alignOrientation then
alignOrientation.MaxTorque = math.huge
end
end
end) --^
end --^
local function onTouchEnded(hit) --v Function that checks to see if a touch ended for the sphere platform part (if present) of a rig. If it did, and it's the local player's we cleanup any sliding effect constraints that might be on the rig.
local success, result = pcall(function()
if CURRENTLY_TOUCHING == true then
if hit.Name == "Main_Sphere" then
local hitParent = hit.Parent
if hitParent then
local player = Players:GetPlayerFromCharacter(hitParent)
if player == nil then --v This intervening/interjecting code snippet runs if the local player's character died/respawned WHILE STILL touching the brick. If they died/respawned, the old character rig being removed that would trip this event handling code no longer belongs to any player whatsoever, so we cannot verify whether or not it belongs to the LOCAL player to properly release the touch logic and so forth. It also intervenes if, for some reason, the last local player's verified rig sphere was unavailable.
if hit == LAST_VERIFIED_LOCAL_PLAYER_SPHERE or LAST_VERIFIED_LOCAL_PLAYER_SPHERE == nil then
LAST_VERIFIED_LOCAL_PLAYER_SPHERE = nil --cleaning up last verified local player sphere reference...
--v Reset crucial global variables in here!
CURRENTLY_TOUCHING = false
--platform.CanCollide = false
if alignOrientation then
alignOrientation.MaxTorque = math.huge
end
--^
return
end
end --^
if player == localPlayer then --making sure that ONLY the local player themselves can trip this.
CURRENTLY_TOUCHING = false
--platform.CanCollide = false --if the local player is no longer touching the platform touchpad, we turn off the collisions to mitigate physics interference from other clients because Roblox is... well... Roblox...
alignOrientation.MaxTorque = math.huge --we reset the corrective torque while the local player is no longer touching it to get it back to its original orientation.
end
end
end
end
end)
pcall(function() --v If anything unexpected occurs, we reset the platform runtime logic to default values if possible...
if success == false then
CURRENTLY_TOUCHING = false
LAST_VERIFIED_LOCAL_PLAYER_SPHERE = nil
if platform then
--platform.CanCollide = false
end
if alignOrientation then
alignOrientation.MaxTorque = math.huge
end
end
end) --^
end --^
--^ FUNCTIONS END
--v INIT START
pcall(function()
local mainFolder = buildPivotMechanics() --building pivot mechanics...
local touchpad = buildTouchPad(platform, mainFolder) --building touchpad that determines whether alignOrientation's torque will be "passive" or "aggressive..."
--v EVENT HANDLERS START
touchpad.Touched:Connect(onTouch) --v Connecting touchpad to touch start/end handling events/functions...
touchpad.TouchEnded:Connect(onTouchEnded) --^
--^ EVENT HANDLERS END
end)
--^ INIT END