
Roblox Physics
- 52 installs
- 11 repo stars
- Updated August 3, 2026
- nonlooped/roblox-suite
Roblox rigid-body physics for vehicles, mechanisms, doors, and platforms: assemblies, constraints, mover forces, network ownership, and collision filtering.
About
Covers Roblox rigid-body physics including assemblies, root parts, anchoring, weld vs rigid constraints, mechanical and mover constraints, network ownership, collision filtering, and the sleep system. A developer uses it for anything that moves or connects under physics simulation.
- Mechanical (hinge, spring, prismatic) and mover (AlignPosition, VectorForce) constraints
- Network ownership, collision filtering, and adaptive timestepping
Roblox Physics by the numbers
- 52 all-time installs (skills.sh)
- +14 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #160 of 247 Game Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nonlooped/roblox-suite --skill roblox-physicsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 52 |
|---|---|
| repo stars | ★ 11 |
| Last updated | August 3, 2026 |
| Repository | nonlooped/roblox-suite ↗ |
What it does
Roblox rigid-body physics for vehicles, mechanisms, doors, and platforms: assemblies, constraints, mover forces, network ownership, and collision filtering.
Files
roblox-physics
Official sources (always check these for the latest):
- https://create.roblox.com/docs/physics
- https://create.roblox.com/docs/physics/assemblies
- https://create.roblox.com/docs/physics/mechanical-constraints
- https://create.roblox.com/docs/physics/mover-constraints
- https://create.roblox.com/docs/physics/network-ownership
- https://create.roblox.com/docs/physics/sleep-system
- https://create.roblox.com/docs/physics/adaptive-timestepping
- https://create.roblox.com/docs/physics/units
- https://create.roblox.com/docs/workspace/collisions
This skill covers the modern constraint-based physics system, not deprecated BodyMover objects. It focuses on building correct, stable, multiplayer-safe mechanisms.
When to use this skill
Activate when:
- Building vehicles, doors, elevators, cranes, swings, suspension, or platforms.
- Moving objects with forces instead of setting
CFrameevery frame. - Tuning stability for complex mechanisms or multiplayer physics.
- Choosing between mechanical and mover constraints.
- Debugging why assemblies sleep, jitter, or behave unexpectedly.
Cross-reference:
- roblox-networking/SKILL.md for network ownership security and server-authoritative validation.
- roblox-core/SKILL.md for services and script locations.
- roblox-testing/SKILL.md for profiling physics with MicroProfiler.
Core concepts
Assemblies
An assembly is one or more parts connected by rigid welds or movable joints, simulated as a single rigid body.
Key BasePart properties (same for any part in the assembly):
AssemblyLinearVelocity/AssemblyAngularVelocity— prefer constraints orApplyImpulseover direct assignment for realistic motion.AssemblyCenterOfMass— force here produces pure linear acceleration.AssemblyMass— sum of all part masses; infinite if any part is anchored.AssemblyRootPart— automatically chosen root for replication and network ownership.
Root-part priority: anchored > non-massless > higher RootPriority > size/name heuristics.
Anchoring
- Anchoring one part in an assembly makes it the root; the rest are implicitly anchored.
- Anchoring multiple parts in the same assembly splits it.
- To anchor an entire assembly, only anchor the root part.
Collision filtering
Use collision groups (PhysicsService:RegisterCollisionGroup, BasePart.CollisionGroup) or NoCollisionConstraint for part-to-part filtering. CanCollide, CanTouch, and CanQuery control different behaviors; see references/collisions-and-filtering.md.
Mechanical constraints
All mechanical constraints connect one or two Attachments (or Bones), except WeldConstraint and NoCollisionConstraint which use Part0/Part1.
| Constraint | Use case |
|---|---|
WeldConstraint | Rigidly lock two parts together, same relative transform |
RigidConstraint | Same as WeldConstraint but attachment-based, supports bones |
HingeConstraint | Doors, levers, rotating parts; motor/servo optional |
PrismaticConstraint | Sliding doors, elevators, pistons |
CylindricalConstraint | Slides + rotates, like hydraulic rams or landing gear |
SpringConstraint | Springs, shocks, suspension |
TorsionSpringConstraint | Rotational springs |
BallSocketConstraint | Shoulders, ball joints |
UniversalConstraint | Drive shafts, constant-velocity joints |
RopeConstraint | Cables, winches, maximum length |
RodConstraint | Fixed separation distance |
PlaneConstraint | Constrain motion to a plane |
NoCollisionConstraint | Disable collisions between two specific parts |
See references/mechanical-constraints.md for creation, orientation, motor/servo tuning, and limits.
Mover constraints
Modern replacements for deprecated BodyMovers:
| Legacy | Modern | Use case |
|---|---|---|
BodyPosition | AlignPosition | Move attachment to a position or another attachment |
BodyGyro | AlignOrientation | Align orientation; LookAtPosition for tracking |
BodyVelocity | LinearVelocity | Maintain constant velocity along vector/line/plane |
BodyAngularVelocity | AngularVelocity | Maintain constant angular velocity |
BodyForce/BodyThrust | VectorForce | Apply constant force |
RocketPropulsion | LineForce + AlignOrientation | Follow + face target |
| — | Torque | Apply constant torque |
| — | LineForce | Force along line between two attachments |
| — | AnimationConstraint | Constraint driven by animation/transform |
See references/mover-constraints.md for force modes (Magnitude vs PerAxis), relativity frames, rigidity, and reaction forces.
Network ownership
- The server owns anchored parts.
- Unanchored parts near a player character are automatically owned by that client.
- Set ownership explicitly with
BasePart:SetNetworkOwner(player)(server only). Reset withSetNetworkOwnershipAuto(). - Assign vehicle/driver ownership carefully: the first seated player may own the whole assembly otherwise.
Security: clients can exploit owned parts (teleport, fake collisions). Validate gameplay-critical events server-side. See references/network-ownership.md.
Sleep system
Assemblies stop simulating when still to save performance. They wake on collisions, property changes, impulses, or gravity/wind changes.
- If a slow mechanism falls asleep, increase motion or use actuated joints (motor/servo constraints) which get stricter sleep thresholds.
- Visualize sleep states with Awake parts in Studio's Visualization Options.
Physics stepping method
Workspace.PhysicsSteppingMethod:
- Fixed (default, 240 Hz) — best general choice; use for racing, destruction, tanks, or when most parts already solve at 240 Hz.
- Adaptive — assigns assemblies to 60/120/240 Hz islands for up to ~2.5× performance in suitable experiences.
Use the MicroProfiler to check island distribution.
Units quick reference
| Roblox | Metric |
|---|---|
| 1 stud | 28 cm |
| 1 RMU | 21.952 kg |
| 196.2 studs/s² | default "Classic" gravity |
| 35 studs/s² | "Realistic" gravity (≈ 9.8 m/s²) |
See references/units-and-physical-properties.md.
Common mistakes this skill prevents
- Using deprecated
BodyMovers instead of modern constraints. - Setting
CFrameevery frame instead of using forces/constraints. - Anchoring every part in an assembly (splits it and hurts performance).
- Ignoring network ownership on vehicles and then wondering why input lags.
- Trusting
Touchedevents from client-owned parts for damage/authority. - Letting actuated joints fall asleep prematurely.
Scripts
scripts/VehicleController.lua— chassis setup withHingeConstraintsteering and motor drive, with client ownership and server validation.scripts/DoorHinge.lua— motorized/servo door with limits and state machine.scripts/PlatformMover.lua—AlignPosition+AlignOrientationplatform with configurable waypoints.scripts/Suspension.lua— spring-damper suspension usingSpringConstraint.
How to proceed
1. Decide if the object is a rigid mechanism (mechanical constraint) or force-driven (mover constraint). 2. Plan assemblies, root parts, and anchoring strategy. 3. Set network ownership for multiplayer responsiveness. 4. Tune forces/velocities using units and physical properties. 5. Profile with MicroProfiler and watch sleep/ownership visualizations.
Collisions and Filtering
Official guide: https://create.roblox.com/docs/workspace/collisions
Collision events
BasePart.Touched— fires when another part touches.BasePart.TouchEnded— fires when contact ends.- These fire regardless of
CanCollide.
Collision filtering
Collision groups
local PhysicsService = game:GetService("PhysicsService")
PhysicsService:RegisterCollisionGroup("Players")
PhysicsService:RegisterCollisionGroup("Projectiles")
PhysicsService:CollisionGroupSetCollidable("Players", "Projectiles", false)
PhysicsService:SetPartCollisionGroup(part, "Projectiles")Useful for team-specific collisions, projectile passthrough, etc.
NoCollisionConstraint
Disable collisions between two specific parts without managing groups:
local noCollide = Instance.new("NoCollisionConstraint")
noCollide.Part0 = partA
noCollide.Part1 = partB
noCollide.Parent = partACanCollide, CanTouch, CanQuery
| Property | Effect |
|---|---|
CanCollide | Physical collision response |
CanTouch | Fires Touched/TouchEnded events |
CanQuery | Included in spatial queries (FindPartOnRay, GetPartsInPart, etc.) |
Important: these are not confidentiality controls. They affect physics and queries, not replication or rendering.
Detecting collisions safely
For gameplay-critical collisions, prefer server-side checks or Shapecasts/Raycasts over Touched events, especially when the touching part is client-owned.
local function onTouched(otherPart)
if otherPart:IsDescendantOf(someSafeModel) then
return
end
-- validate distance, ownership, etc.
endMechanical Constraints
Official guide: https://create.roblox.com/docs/physics/mechanical-constraints
Creating constraints
Most mechanical constraints require two Attachment instances (or Bone instances). The constraint's behavior depends on attachment orientation:
- Axis (yellow arrow) defines primary rotation/translation axes.
- SecondaryAxis defines secondary orientation.
You can create constraints via: 1. Studio Constraint Picker in the Model tab. 2. Explorer → insert constraint → link Attachment0/Attachment1 (or Part0/Part1 for Weld/NoCollision).
Constraint reference
WeldConstraint
- Connects
Part0andPart1rigidly. - Maintains relative position and orientation.
- Deactivates if parts are anchored into different assemblies.
- No attachments needed.
RigidConstraint
- Attachment-based rigid connection.
- Supports
Bones for skinned mesh applications. - Same relative transform behavior as WeldConstraint.
HingeConstraint
- Rotates around one shared axis.
- Motor mode: target
AngularVelocitywith acceleration/torque limits. - Servo mode: target
TargetAnglewith speed/torque limits. LimitsEnabled+LowerAngle/UpperAngle/Restitutionfor swing doors, levers, etc.
Tip: make sure both attachments' Axis properties point the same direction.
PrismaticConstraint
- Slides along one axis, no rotation.
- Motor/servo modes for elevators, pistons, drawers.
- Use
LimitsEnabledto define slide range.
CylindricalConstraint
- Combines prismatic slide + hinge rotation.
- Separate linear and angular actuator controls.
- Useful for landing gear, hydraulic arms, screw mechanisms.
SpringConstraint
- Applies force based on displacement and relative velocity.
FreeLengthis the rest length.StiffnessandDampingtune oscillation.- Optional
MinLength/MaxLength. - Units: stiffness ≈ 0.0456 RMU/s² per N/m; damping ≈ 0.0456 RMU/s per N·s/m.
TorsionSpringConstraint
- Applies torque based on angular displacement and velocity.
- Useful for torsion bars, rotational return springs.
BallSocketConstraint
- Same position, free rotation on all axes.
- Optional cone/limit constraints.
UniversalConstraint
- Keeps two axes perpendicular.
- Common in drive shafts and robotics.
RopeConstraint
- Prevents attachments from separating beyond
Length. WinchEnabledallows motorized length change.
RodConstraint
- Maintains fixed separation distance.
- Optional tilt limits.
PlaneConstraint
- Constrains motion to a plane defined by attachment orientation.
NoCollisionConstraint
- Disables collisions between
Part0andPart1. - Both parts still collide with the rest of the world.
Actuator types
Most powered constraints support:
- None — passive constraint, no force applied.
- Motor — continuous motion toward a velocity.
- Servo — moves to and holds a target position/angle.
Tuning parameters:
MaxForce/MaxTorque— caps applied force.MotorMaxAcceleration/MotorMaxForce— motor responsiveness.Responsiveness— servo stiffness.Restitution— bounciness at limits.
Constraint visualization
Enable in Studio:
- Show Welds (
Alt+W/⌥+W) - Show Constraint Details (
Alt+D/⌥+D) - Assemblies / Mechanisms in Visualization Options
Use these to debug orientation, root parts, and ownership.
Luau example: powered hinge door
local hinge = script.Parent:WaitForChild("HingeConstraint")
-- Open 90 degrees
hinge.ActuatorType = Enum.ActuatorType.Servo
hinge.ServoMaxTorque = 5000
hinge.AngularSpeed = 2
hinge.TargetAngle = 90
-- Close
hinge.TargetAngle = 0Luau example: spring-damper suspension
local spring = script.Parent:WaitForChild("SpringConstraint")
spring.Stiffness = 5000
spring.Damping = 500
spring.FreeLength = 3Mover Constraints
Official guide: https://create.roblox.com/docs/physics/mover-constraints
Mover constraints apply force, velocity, torque, or alignment to assemblies. They are the modern replacement for deprecated BodyMover objects.
Migration table
| Deprecated | Modern | Notes |
|---|---|---|
BodyPosition | AlignPosition | Two modes: Magnitude and PerAxis |
BodyGyro | AlignOrientation | LookAtPosition replaces some look-at logic |
BodyVelocity | LinearVelocity | Vector/line/plane modes |
BodyAngularVelocity | AngularVelocity | RelativeTo for reference frames |
BodyForce / BodyThrust | VectorForce | Apply constant force relative to world/attachment |
RocketPropulsion | LineForce + AlignOrientation | Follow + face target |
AlignPosition
Moves Attachment0 toward Attachment1 or a world Position.
Key properties:
Mode—TwoAttachmentorOneAttachment.RigidityEnabled— iftrue, solver does whatever it takes; iffalse, useMaxForce,MaxVelocity,Responsiveness.ForceLimitMode—Magnitude(scalarMaxForce) orPerAxis(vectorMaxAxesForce).ApplyAtCenterOfMass— apply at CoM instead of attachment point.ReactionForceEnabled— apply equal/opposite force to Attachment1.
AlignOrientation
Aligns Attachment0 orientation with Attachment1 or a goal orientation.
Key properties:
AlignType—PrimaryAxisParallel,PrimaryAxisPerpendicular, orAllAxes.RigidityEnabled,MaxTorque,MaxAngularVelocity,Responsiveness.LookAtPosition— separateVector3that points Attachment0's primary axis at a world position (use withOneAttachmentmode and a target orientation).
LinearVelocity
Maintains constant linear velocity on an assembly.
VelocityConstraintMode:Vector— 3D velocity vector.Line— velocity along attachment axis.Plane— velocity within a plane.RelativeTo— world, Attachment0, Attachment1.ForceLimitMode/MaxForce/MaxAxesForce.
Warning: this applies force to maintain velocity. For one-time velocity, use ApplyImpulse or set AssemblyLinearVelocity.
AngularVelocity
Maintains constant angular velocity.
AngularVelocityvector in rad/s (relative toRelativeTo).MaxTorque.RelativeTo.
VectorForce
Applies constant force.
Forcevector.RelativeTo— world or attachment frame.- Apply at attachment point or CoM depending on setup.
Torque
Applies constant torque about the assembly's center of mass.
Torquevector.RelativeTo.
LineForce
Applies force along the line connecting two attachments.
InverseSquareLaw— falloff with distance (like gravity/magnetism).Magnitude.ApplyAtCenterOfMass.
AnimationConstraint
Drives attachments by a target CFrame offset. Useful for animation-driven physics.
IsKinematic— kinematic or force-based.MaxForce/MaxTorque.
Choosing a mover
| Goal | Constraint |
|---|---|
| Move to position | AlignPosition |
| Face direction / look at target | AlignOrientation |
| Hover / follow | AlignPosition + AlignOrientation |
| Constant speed car | LinearVelocity or VectorForce |
| Spin propeller | AngularVelocity |
| Thruster / rocket | VectorForce |
| Magnet / gravity | LineForce |
| Guided missile | LineForce + AlignOrientation |
Luau example: hover platform
local alignPos = script.Parent:WaitForChild("AlignPosition")
local alignOrn = script.Parent:WaitForChild("AlignOrientation")
alignPos.RigidityEnabled = false
alignPos.MaxForce = 100000
alignPos.MaxVelocity = 50
alignPos.Responsiveness = 50
alignPos.Position = Vector3.new(0, 10, 0)
alignOrn.RigidityEnabled = false
alignOrn.MaxTorque = 100000
alignOrn.MaxAngularVelocity = 5
alignOrn.Responsiveness = 50Network Ownership
Official guide: https://create.roblox.com/docs/physics/network-ownership
What ownership does
Roblox uses distributed physics. Each unanchored assembly is simulated by either the server or a client. The owner simulates locally and replicates state.
- Server-owned — authoritative, higher latency for clients.
- Client-owned — responsive for that player, but exploitable.
- Anchored parts are always server-owned.
Automatic ownership
By default, unanchored parts near a player's character are owned by that client. Ownership can transfer as characters move.
Manual ownership
Server-side only:
part:SetNetworkOwner(player) -- assign to a player
part:SetNetworkOwnershipAuto() -- revert to engine defaults
part:GetNetworkOwner() -- get current ownerRules:
- You can only set ownership on an unanchored assembly root.
- Anchoring resets ownership to server.
- Setting ownership on one assembly in a mechanism sets ownership for the whole mechanism.
Common vehicle pattern
When a player sits in a VehicleSeat, give them ownership so driving feels responsive:
local Players = game:GetService("Players")
local vehicleSeat = script.Parent
vehicleSeat:GetPropertyChangedSignal("Occupant"):Connect(function()
local humanoid = vehicleSeat.Occupant
if humanoid then
local player = Players:GetPlayerFromCharacter(humanoid.Parent)
if player then
vehicleSeat:SetNetworkOwner(player)
end
else
vehicleSeat:SetNetworkOwnershipAuto()
end
end)Also assign ownership of loose parts on the vehicle (e.g., cargo) to the same driver.
Security implications
- Clients can teleport or warp owned parts.
Touchedevents fired by client-owned parts can be faked.- Do not use client-owned physics for authoritative damage, scoring, or checkpoints.
- Validate important gameplay events on the server using position/distance/time checks.
Visualization
Enable Network owners in Visualization Options. Colors are shown per-assembly:
- Blue — the local player owns the assembly.
- Green — another client owns the assembly.
- Red — buffer zone, pending transfer.
- White/grey — server owns the assembly.
- Black — no owner (not simulated).
Debugging tips
- If a vehicle feels laggy for the driver, check that the driver owns it.
- If a mechanism jitters, ensure all connected assemblies share the same owner.
- If a part is black, it may be massless with no owner; anchor it or give it physical significance.
Units and Physical Properties
Official guide: https://create.roblox.com/docs/physics/units
Primary units
| Unit | Roblox | Metric |
|---|---|---|
| Time | 1 second | 1 second |
| Length | 1 stud | 28 cm |
| Mass | 1 RMU | 21.952 kg |
Derived units
| Quantity | Metric | Roblox |
|---|---|---|
| Water density | 1 g/cm³ | 1 RMU/stud³ |
| Air density (sea level) | 0.00129 g/cm³ | 0.00129 RMU/stud³ |
| Spring stiffness | 1 N/m | 0.0456 RMU/s² |
| Spring damping | 1 N·s/m | 0.0456 RMU/s |
| Velocity | 1 m/s | 3.57 studs/s |
| Force | 1 N | 0.163 Rowtons (RMU·stud/s²) |
| Torque | 1 N·m | 0.581 Rowton·studs (RMU·stud²/s²) |
Gravity presets
| Preset | Roblox | Metric |
|---|---|---|
| Classic (default) | 196.2 studs/s² | 54.936 m/s² |
| Realistic | 35 studs/s² | 9.8 m/s² |
| Action | 75 studs/s² | 21 m/s² |
Physical property limits
| Property | Min | Max |
|---|---|---|
| Density | 0.0001 | 100 RMU/stud³ |
| Friction | 0.0 | 2.0 |
| FrictionWeight | 0.0 | 100 |
| Elasticity | 0.0 | 1.0 |
| ElasticityWeight | 0.0 | 100 |
Consistency
Use standard Roblox units throughout an experience. Custom interpretations (e.g., 1 stud = 1 foot) require recalibrating all physics constants and can break compatibility with default character controllers.
Custom physical properties
local props = PhysicalProperties.new(
0.7, -- density
0.3, -- friction
0.5, -- elasticity
1.0, -- frictionWeight
1.0 -- elasticityWeight
)
part.CustomPhysicalProperties = propsSet CustomPhysicalProperties = nil to revert to material defaults.
--!strict
--[[
DoorHinge.lua
A servo-powered door with open/close state.
Setup:
- A door part with a HingeConstraint named "Hinge".
- The hinge's Attachment0 should be on the door frame, Attachment1 on the door.
- The hinge Axis should point up (rotation axis).
- Keep the door assembly server-owned for authoritative collision; call
:destroy() when the door is removed to clean up the servo state.
Usage:
local DoorHinge = require(path.to.DoorHinge)
local door = DoorHinge.new(workspace.DoorModel.Hinge)
door:open()
door:close()
door:destroy()
]]
local DoorHinge = {}
DoorHinge.__index = DoorHinge
function DoorHinge.new(hinge)
local self = setmetatable({}, DoorHinge)
self.hinge = hinge
self.hinge.ActuatorType = Enum.ActuatorType.Servo
self.hinge.ServoMaxTorque = 2000
self.hinge.AngularSpeed = 3
self.hinge.LimitsEnabled = true
self.hinge.LowerAngle = 0
self.hinge.UpperAngle = 90
self.isOpen = false
return self
end
function DoorHinge:open()
self.hinge.TargetAngle = self.hinge.UpperAngle
self.isOpen = true
end
function DoorHinge:close()
self.hinge.TargetAngle = self.hinge.LowerAngle
self.isOpen = false
end
function DoorHinge:toggle()
if self.isOpen then
self:close()
else
self:open()
end
end
function DoorHinge:destroy()
-- Release the servo so the hinge becomes passive after cleanup.
self.hinge.ActuatorType = Enum.ActuatorType.None
self.hinge = nil
end
return DoorHinge
--!strict
--[[
PlatformMover.lua
A moving platform using AlignPosition + AlignOrientation.
Setup:
- A platform part (must be unanchored for AlignPosition to simulate it).
- An AlignPosition and AlignOrientation inside the platform.
- Attachment0 on platform, Attachment1 optional (leave unset for world mode).
- Configure AlignPosition.Mode = OneAttachment to use world Position.
Usage:
local PlatformMover = require(path.to.PlatformMover)
local platform = PlatformMover.new(workspace.MovingPlatform, {
workspace.PointA.Position,
workspace.PointB.Position,
}, 5)
platform:start()
platform:stop()
platform:destroy()
]]
local PlatformMover = {}
PlatformMover.__index = PlatformMover
function PlatformMover.new(platform, waypoints, waitTime)
local self = setmetatable({}, PlatformMover)
self.platform = platform
self.waypoints = waypoints or {}
self.waitTime = waitTime or 2
self.index = 1
self.direction = 1
self.running = false
self.delayTask = nil
-- AlignPosition cannot move an anchored assembly.
platform.Anchored = false
self.alignPos = platform:FindFirstChildOfClass("AlignPosition") or Instance.new("AlignPosition")
self.alignPos.Mode = Enum.PositionAlignmentMode.OneAttachment
self.alignPos.RigidityEnabled = false
self.alignPos.MaxForce = platform.AssemblyMass * 1000
self.alignPos.MaxVelocity = 20
self.alignPos.Responsiveness = 20
self.alignPos.Parent = platform
self.alignOrn = platform:FindFirstChildOfClass("AlignOrientation") or Instance.new("AlignOrientation")
self.alignOrn.Mode = Enum.OrientationAlignmentMode.OneAttachment
self.alignOrn.AlignType = Enum.AlignType.AllAxes
self.alignOrn.RigidityEnabled = false
self.alignOrn.MaxTorque = platform.AssemblyMass * 500
self.alignOrn.MaxAngularVelocity = 5
self.alignOrn.Responsiveness = 20
self.alignOrn.CFrame = platform.CFrame.Rotation
self.alignOrn.Parent = platform
if not self.alignPos.Attachment0 then
local att = Instance.new("Attachment")
att.Parent = platform
self.alignPos.Attachment0 = att
end
if not self.alignOrn.Attachment0 then
local att = Instance.new("Attachment")
att.Parent = platform
self.alignOrn.Attachment0 = att
end
return self
end
function PlatformMover:moveToNext()
if #self.waypoints == 0 then return end
local target = self.waypoints[self.index]
self.alignPos.Position = target
self.delayTask = task.delay(self.waitTime, function()
self.delayTask = nil
if not self.running then return end
self.index += self.direction
if self.index > #self.waypoints then
self.index = #self.waypoints - 1
self.direction = -1
elseif self.index < 1 then
self.index = 2
self.direction = 1
end
self:moveToNext()
end)
end
function PlatformMover:start()
if self.running then return end
self.running = true
self:moveToNext()
end
function PlatformMover:stop()
self.running = false
if self.delayTask then
task.cancel(self.delayTask)
self.delayTask = nil
end
end
function PlatformMover:destroy()
self:stop()
if self.alignPos then
self.alignPos:Destroy()
self.alignPos = nil
end
if self.alignOrn then
self.alignOrn:Destroy()
self.alignOrn = nil
end
end
return PlatformMover
--!strict
--[[
Suspension.lua
Simple spring-damper suspension using SpringConstraint.
Setup:
- A wheel part connected to a chassis part via SpringConstraint.
- Attachment0 on chassis, Attachment1 on wheel.
- Wheel must be unanchored. A single SpringConstraint lets the wheel move
freely in 3D space, so constrain it to vertical motion with a
PrismaticConstraint aligned to the suspension axis. A second
SpringConstraint does not remove degrees of freedom.
Usage:
local Suspension = require(path.to.Suspension)
Suspension.new(springConstraint, {
stiffness = 8000,
damping = 800,
freeLength = 2,
})
]]
local Suspension = {}
Suspension.__index = Suspension
function Suspension.new(spring, config)
local self = setmetatable({}, Suspension)
self.spring = spring
self.spring.Stiffness = config.stiffness or 5000
self.spring.Damping = config.damping or 500
self.spring.FreeLength = config.freeLength or 3
self.spring.MinLength = config.minLength or 0.5
self.spring.MaxLength = config.maxLength or config.freeLength * 2
return self
end
return Suspension
--!strict
--[[
VehicleController.lua
A client-authoritative vehicle chassis with server-side validation.
Setup:
- Four wheels as cylinders or spheres, each connected to the chassis via
HingeConstraints or bearings (do not rigidly weld drive wheels).
- Two HingeConstraints for front wheels (steering).
- Two HingeConstraints for drive wheels with Motor actuator.
- A VehicleSeat parented to the chassis.
Usage:
local VehicleController = require(path.to.VehicleController)
local controller = VehicleController.new(vehicleModel)
controller:destroy() -- call when the vehicle is removed
]]
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local VehicleController = {}
VehicleController.__index = VehicleController
function VehicleController.new(model, config)
config = config or {}
local self = setmetatable({}, VehicleController)
self.model = model
self.seat = model:WaitForChild("VehicleSeat")
self.maxSpeed = config.maxSpeed or 50
self.turnAngle = config.turnAngle or 30
self.wheelRadius = config.wheelRadius or 2
self.driveMotors = {}
self.steerHinges = {}
for _, obj in ipairs(model:GetDescendants()) do
if obj:IsA("HingeConstraint") then
if obj.Name == "DriveMotor" then
obj.ActuatorType = Enum.ActuatorType.Motor
obj.MotorMaxTorque = config.motorMaxTorque or 5000
obj.MotorMaxAcceleration = config.motorMaxAcceleration or 1500
table.insert(self.driveMotors, obj)
elseif obj.Name == "SteerHinge" then
obj.ActuatorType = Enum.ActuatorType.Servo
obj.ServoMaxTorque = config.servoMaxTorque or 5000
obj.AngularSpeed = config.angularSpeed or 3
table.insert(self.steerHinges, obj)
end
end
end
self.heartbeatConnection = nil
self.validationConnection = nil
self.occupantConnection = nil
self:setupOwnership()
self:startLoop()
return self
end
function VehicleController:setupOwnership()
self.occupantConnection = self.seat:GetPropertyChangedSignal("Occupant"):Connect(function()
local humanoid = self.seat.Occupant
if humanoid then
local player = Players:GetPlayerFromCharacter(humanoid.Parent)
if player then
self.seat:SetNetworkOwner(player)
end
else
self.seat:SetNetworkOwnershipAuto()
end
end)
end
function VehicleController:validateState()
if not self.seat or not self.seat:IsDescendantOf(workspace) then
return
end
local assemblyMass = self.seat.AssemblyMass
if assemblyMass == math.huge then
return
end
-- Simple server-side sanity check: if the client-owned assembly moves
-- much faster than the configured limit, revoke ownership to stop exploits.
local speed = self.seat.AssemblyLinearVelocity.Magnitude
if speed > self.maxSpeed * 1.5 then
self.seat:SetNetworkOwnershipAuto()
for _, motor in ipairs(self.driveMotors) do
motor.AngularVelocity = 0
end
end
end
function VehicleController:startLoop()
self.heartbeatConnection = RunService.Heartbeat:Connect(function()
if not self.seat or not self.seat:IsDescendantOf(workspace) then
self:destroy()
return
end
local steer = math.clamp(self.seat.Steer, -1, 1)
local throttle = math.clamp(self.seat.Throttle, -1, 1)
local targetAngle = -steer * self.turnAngle
for _, hinge in ipairs(self.steerHinges) do
hinge.TargetAngle = targetAngle
end
-- Convert linear speed (studs/s) to angular velocity (rad/s).
local targetLinearVelocity = throttle * self.maxSpeed
local targetAngularVelocity = targetLinearVelocity / self.wheelRadius
for _, motor in ipairs(self.driveMotors) do
motor.AngularVelocity = targetAngularVelocity
end
end)
self.validationConnection = RunService.Heartbeat:Connect(function()
self:validateState()
end)
end
function VehicleController:destroy()
if self.heartbeatConnection then
self.heartbeatConnection:Disconnect()
self.heartbeatConnection = nil
end
if self.validationConnection then
self.validationConnection:Disconnect()
self.validationConnection = nil
end
if self.occupantConnection then
self.occupantConnection:Disconnect()
self.occupantConnection = nil
end
for _, motor in ipairs(self.driveMotors) do
motor.AngularVelocity = 0
end
for _, hinge in ipairs(self.steerHinges) do
hinge.TargetAngle = 0
end
end
return VehicleController