roblox-physics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRoblox Physics & Constraints
Roblox 物理与约束
Use this skill when building physics-driven gameplay: vehicles, ragdolls, projectiles, mechanical contraptions, elevators, swinging platforms, or anything using constraints and forces.
当构建由物理驱动的玩法时使用本技能:载具、布娃娃系统、投射物、机械装置、电梯、摆动平台,或任何使用约束与力的内容。
Constraint Types
约束类型
Mechanical Constraints
机械约束
| Constraint | What it does | Use for |
|---|---|---|
| Rotation around one axis | Doors, wheels, pendulums, flaps |
| Slide along one axis | Elevators, pistons, sliding doors |
| Rotate + slide on one axis | Telescoping arms, drill bits |
| Free rotation (3 DOF) | Ragdoll joints, chains, wrecking balls |
| 2-axis rotation (no twist) | Steering columns, gimbal joints |
| Rigid connection | Attach parts permanently |
| Rigid (like Weld but with offset) | Precise attachment with maintained offset |
| 约束类型 | 功能 | 适用场景 |
|---|---|---|
| 绕单轴旋转 | 门、车轮、钟摆、挡板 |
| 沿单轴滑动 | 电梯、活塞、滑动门 |
| 单轴旋转+滑动 | 伸缩臂、钻头 |
| 自由旋转(3自由度) | 布娃娃关节、链条、破坏球 |
| 双轴旋转(无扭转) | 转向柱、万向节 |
| 刚性连接 | 永久附着部件 |
| 刚性连接(类似Weld但支持偏移) | 保持偏移的精确附着 |
Motion Constraints
运动约束
| Constraint | What it does | Use for |
|---|---|---|
| Move toward target position | Floating platforms, magnetic attraction |
| Rotate toward target orientation | Auto-leveling, look-at behavior |
| Constant velocity in direction | Conveyor belts, moving platforms |
| Constant rotation speed | Spinning obstacles, fans |
| Apply constant force | Gravity modification, thrust |
| Apply constant torque | Spinning objects |
| 约束类型 | 功能 | 适用场景 |
|---|---|---|
| 向目标位置移动 | 悬浮平台、磁力吸引 |
| 向目标方向旋转 | 自动调平、朝向行为 |
| 沿方向保持恒定速度 | 传送带、移动平台 |
| 保持恒定旋转速度 | 旋转障碍物、风扇 |
| 施加恒定力 | 重力修改、推力 |
| 施加恒定扭矩 | 旋转物体 |
Spring/Rope
弹簧/绳索
| Constraint | What it does | Use for |
|---|---|---|
| Bouncy connection | Suspension, trampolines, bouncy bridges |
| Max distance (slack allowed) | Grappling hooks, hanging objects |
| Fixed distance (rigid) | Rigid linkages, pendulum arms |
| 约束类型 | 功能 | 适用场景 |
|---|---|---|
| 弹性连接 | 悬挂系统、蹦床、弹性桥梁 |
| 最大距离(允许松弛) | 抓钩、悬挂物体 |
| 固定距离(刚性) | 刚性连杆、钟摆臂 |
Attachment Pattern
附着点模式
All constraints connect via Attachments, not Parts directly:
luau
local function connectHinge(part0: BasePart, part1: BasePart, pivotOffset: Vector3)
local att0 = Instance.new("Attachment")
att0.Position = pivotOffset
att0.Parent = part0
local att1 = Instance.new("Attachment")
att1.Position = Vector3.new(0, 0, 0) -- at part1's origin
att1.Parent = part1
local hinge = Instance.new("HingeConstraint")
hinge.Attachment0 = att0
hinge.Attachment1 = att1
hinge.ActuatorType = Enum.ActuatorType.Motor -- or None, Servo
hinge.MotorMaxTorque = 1000
hinge.AngularVelocity = 5 -- rad/s
hinge.Parent = part0
return hinge
end所有约束通过Attachment而非直接连接Part:
luau
local function connectHinge(part0: BasePart, part1: BasePart, pivotOffset: Vector3)
local att0 = Instance.new("Attachment")
att0.Position = pivotOffset
att0.Parent = part0
local att1 = Instance.new("Attachment")
att1.Position = Vector3.new(0, 0, 0) -- at part1's origin
att1.Parent = part1
local hinge = Instance.new("HingeConstraint")
hinge.Attachment0 = att0
hinge.Attachment1 = att1
hinge.ActuatorType = Enum.ActuatorType.Motor -- or None, Servo
hinge.MotorMaxTorque = 1000
hinge.AngularVelocity = 5 -- rad/s
hinge.Parent = part0
return hinge
endVehicles
载具
Basic Car (4 wheels + body)
基础汽车(4轮+车身)
luau
local function createWheel(chassis: BasePart, offset: Vector3, steer: boolean): HingeConstraint
local wheel = Instance.new("Part")
wheel.Shape = Enum.PartType.Cylinder
wheel.Size = Vector3.new(1, 3, 3) -- width, diameter, diameter
wheel.CFrame = chassis.CFrame * CFrame.new(offset) * CFrame.Angles(0, 0, math.pi/2)
wheel.CustomPhysicalProperties = PhysicalProperties.new(1, 0.5, 0, 1, 1)
wheel.Parent = chassis.Parent
-- Suspension (spring between chassis and wheel)
local springAtt0 = Instance.new("Attachment")
springAtt0.Position = offset + Vector3.new(0, 1, 0)
springAtt0.Parent = chassis
local springAtt1 = Instance.new("Attachment")
springAtt1.Parent = wheel
local spring = Instance.new("SpringConstraint")
spring.Attachment0 = springAtt0
spring.Attachment1 = springAtt1
spring.FreeLength = 2
spring.Stiffness = 5000
spring.Damping = 200
spring.Parent = chassis
-- Axle (hinge for rotation)
local axleAtt0 = Instance.new("Attachment")
axleAtt0.Position = offset
axleAtt0.Parent = chassis
local axleAtt1 = Instance.new("Attachment")
axleAtt1.Parent = wheel
local hinge = Instance.new("HingeConstraint")
hinge.Attachment0 = axleAtt0
hinge.Attachment1 = axleAtt1
hinge.ActuatorType = Enum.ActuatorType.Motor
hinge.MotorMaxTorque = 500
hinge.AngularVelocity = 0 -- controlled by input
hinge.Parent = chassis
return hinge
endluau
local function createWheel(chassis: BasePart, offset: Vector3, steer: boolean): HingeConstraint
local wheel = Instance.new("Part")
wheel.Shape = Enum.PartType.Cylinder
wheel.Size = Vector3.new(1, 3, 3) -- width, diameter, diameter
wheel.CFrame = chassis.CFrame * CFrame.new(offset) * CFrame.Angles(0, 0, math.pi/2)
wheel.CustomPhysicalProperties = PhysicalProperties.new(1, 0.5, 0, 1, 1)
wheel.Parent = chassis.Parent
-- Suspension (spring between chassis and wheel)
local springAtt0 = Instance.new("Attachment")
springAtt0.Position = offset + Vector3.new(0, 1, 0)
springAtt0.Parent = chassis
local springAtt1 = Instance.new("Attachment")
springAtt1.Parent = wheel
local spring = Instance.new("SpringConstraint")
spring.Attachment0 = springAtt0
spring.Attachment1 = springAtt1
spring.FreeLength = 2
spring.Stiffness = 5000
spring.Damping = 200
spring.Parent = chassis
-- Axle (hinge for rotation)
local axleAtt0 = Instance.new("Attachment")
axleAtt0.Position = offset
axleAtt0.Parent = chassis
local axleAtt1 = Instance.new("Attachment")
axleAtt1.Parent = wheel
local hinge = Instance.new("HingeConstraint")
hinge.Attachment0 = axleAtt0
hinge.Attachment1 = axleAtt1
hinge.ActuatorType = Enum.ActuatorType.Motor
hinge.MotorMaxTorque = 500
hinge.AngularVelocity = 0 -- controlled by input
hinge.Parent = chassis
return hinge
endVehicle Input (server-authoritative)
载具输入(服务器权威)
luau
-- Server: receive input, apply to constraints
local DriveRemote = Instance.new("RemoteEvent")
DriveRemote.Name = "Drive"
DriveRemote.Parent = ReplicatedStorage
DriveRemote.OnServerEvent:Connect(function(player, throttle: number, steer: number)
-- Validate
throttle = math.clamp(throttle, -1, 1)
steer = math.clamp(steer, -1, 1)
local vehicle = getPlayerVehicle(player)
if not vehicle then return end
-- Apply throttle to rear wheels
for _, hinge in vehicle.rearWheels do
hinge.AngularVelocity = throttle * MAX_SPEED
end
-- Apply steering to front wheels
for _, servo in vehicle.frontSteering do
servo.TargetAngle = steer * MAX_STEER_ANGLE
end
end)luau
-- Server: receive input, apply to constraints
local DriveRemote = Instance.new("RemoteEvent")
DriveRemote.Name = "Drive"
DriveRemote.Parent = ReplicatedStorage
DriveRemote.OnServerEvent:Connect(function(player, throttle: number, steer: number)
-- Validate
throttle = math.clamp(throttle, -1, 1)
steer = math.clamp(steer, -1, 1)
local vehicle = getPlayerVehicle(player)
if not vehicle then return end
-- Apply throttle to rear wheels
for _, hinge in vehicle.rearWheels do
hinge.AngularVelocity = throttle * MAX_SPEED
end
-- Apply steering to front wheels
for _, servo in vehicle.frontSteering do
servo.TargetAngle = steer * MAX_STEER_ANGLE
end
end)Ragdoll
布娃娃系统
Activate Ragdoll (replace Motor6Ds with BallSockets)
激活布娃娃系统(用BallSocket替换Motor6D)
luau
local function enableRagdoll(character: Model)
local humanoid = character:FindFirstChildOfClass("Humanoid")
humanoid:ChangeState(Enum.HumanoidStateType.Physics)
for _, motor in character:GetDescendants() do
if motor:IsA("Motor6D") and motor.Name ~= "Root" then -- keep Root for HRP
local att0 = Instance.new("Attachment")
att0.CFrame = motor.C0
att0.Parent = motor.Part0
local att1 = Instance.new("Attachment")
att1.CFrame = motor.C1
att1.Parent = motor.Part1
local socket = Instance.new("BallSocketConstraint")
socket.Attachment0 = att0
socket.Attachment1 = att1
socket.LimitsEnabled = true
socket.UpperAngle = 45 -- prevent unnatural bending
socket.Parent = motor.Part0
motor.Enabled = false
end
end
end
local function disableRagdoll(character: Model)
local humanoid = character:FindFirstChildOfClass("Humanoid")
-- Remove sockets, re-enable motors
for _, obj in character:GetDescendants() do
if obj:IsA("BallSocketConstraint") then
obj:Destroy()
elseif obj:IsA("Motor6D") then
obj.Enabled = true
end
end
humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
endluau
local function enableRagdoll(character: Model)
local humanoid = character:FindFirstChildOfClass("Humanoid")
humanoid:ChangeState(Enum.HumanoidStateType.Physics)
for _, motor in character:GetDescendants() do
if motor:IsA("Motor6D") and motor.Name ~= "Root" then -- keep Root for HRP
local att0 = Instance.new("Attachment")
att0.CFrame = motor.C0
att0.Parent = motor.Part0
local att1 = Instance.new("Attachment")
att1.CFrame = motor.C1
att1.Parent = motor.Part1
local socket = Instance.new("BallSocketConstraint")
socket.Attachment0 = att0
socket.Attachment1 = att1
socket.LimitsEnabled = true
socket.UpperAngle = 45 -- prevent unnatural bending
socket.Parent = motor.Part0
motor.Enabled = false
end
end
end
local function disableRagdoll(character: Model)
local humanoid = character:FindFirstChildOfClass("Humanoid")
-- Remove sockets, re-enable motors
for _, obj in character:GetDescendants() do
if obj:IsA("BallSocketConstraint") then
obj:Destroy()
elseif obj:IsA("Motor6D") then
obj.Enabled = true
end
end
humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
endProjectiles
投射物
Server-Authoritative Raycast Projectile (hitscan)
服务器权威型射线投射物(命中扫描)
luau
local function fireProjectile(origin: Vector3, direction: Vector3, damage: number, ignore: {Instance})
local params = RaycastParams.new()
params.FilterDescendantsInstances = ignore
params.FilterType = Enum.RaycastFilterType.Exclude
local result = workspace:Raycast(origin, direction * 300, params)
if result then
local hit = result.Instance
local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
or hit.Parent.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid:TakeDamage(damage)
end
return result.Position
end
return origin + direction * 300
endluau
local function fireProjectile(origin: Vector3, direction: Vector3, damage: number, ignore: {Instance})
local params = RaycastParams.new()
params.FilterDescendantsInstances = ignore
params.FilterType = Enum.RaycastFilterType.Exclude
local result = workspace:Raycast(origin, direction * 300, params)
if result then
local hit = result.Instance
local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
or hit.Parent.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid:TakeDamage(damage)
end
return result.Position
end
return origin + direction * 300
endPhysics Projectile (arcing, grenade-style)
物理投射物(弧线运动,手榴弹样式)
luau
local function launchProjectile(origin: CFrame, velocity: Vector3, lifetime: number)
local projectile = Instance.new("Part")
projectile.Size = Vector3.new(0.5, 0.5, 0.5)
projectile.Shape = Enum.PartType.Ball
projectile.CFrame = origin
projectile.Anchored = false
projectile.CanCollide = true
projectile.Parent = workspace
-- Apply initial velocity
projectile.AssemblyLinearVelocity = velocity
-- Cleanup after lifetime
task.delay(lifetime, function()
if projectile.Parent then
-- Explode or just destroy
projectile:Destroy()
end
end)
-- Detect hits
projectile.Touched:Connect(function(hit)
if hit.Parent:FindFirstChildOfClass("Humanoid") then
-- Deal damage, create explosion, etc.
projectile:Destroy()
end
end)
return projectile
endluau
local function launchProjectile(origin: CFrame, velocity: Vector3, lifetime: number)
local projectile = Instance.new("Part")
projectile.Size = Vector3.new(0.5, 0.5, 0.5)
projectile.Shape = Enum.PartType.Ball
projectile.CFrame = origin
projectile.Anchored = false
projectile.CanCollide = true
projectile.Parent = workspace
-- Apply initial velocity
projectile.AssemblyLinearVelocity = velocity
-- Cleanup after lifetime
task.delay(lifetime, function()
if projectile.Parent then
-- Explode or just destroy
projectile:Destroy()
end
end)
-- Detect hits
projectile.Touched:Connect(function(hit)
if hit.Parent:FindFirstChildOfClass("Humanoid") then
-- Deal damage, create explosion, etc.
projectile:Destroy()
end
end)
return projectile
endCommon Patterns
常见模式
Elevator / Moving Platform
电梯/移动平台
luau
local function createElevator(platform: BasePart, bottomY: number, topY: number, speed: number)
local att = Instance.new("Attachment")
att.Parent = platform
local prismatic = Instance.new("PrismaticConstraint")
prismatic.Attachment0 = att
-- Attachment1 on a fixed anchor
local anchor = Instance.new("Part")
anchor.Anchored = true
anchor.CanCollide = false
anchor.Transparency = 1
anchor.Position = platform.Position
anchor.Parent = workspace
local anchorAtt = Instance.new("Attachment")
anchorAtt.Parent = anchor
prismatic.Attachment1 = anchorAtt
prismatic.ActuatorType = Enum.ActuatorType.Servo
prismatic.Speed = speed
prismatic.ServoMaxForce = 100000
prismatic.LowerLimit = 0
prismatic.UpperLimit = topY - bottomY
prismatic.Parent = platform
platform.Anchored = false
return prismatic -- set .TargetPosition to move
endluau
local function createElevator(platform: BasePart, bottomY: number, topY: number, speed: number)
local att = Instance.new("Attachment")
att.Parent = platform
local prismatic = Instance.new("PrismaticConstraint")
prismatic.Attachment0 = att
-- Attachment1 on a fixed anchor
local anchor = Instance.new("Part")
anchor.Anchored = true
anchor.CanCollide = false
anchor.Transparency = 1
anchor.Position = platform.Position
anchor.Parent = workspace
local anchorAtt = Instance.new("Attachment")
anchorAtt.Parent = anchor
prismatic.Attachment1 = anchorAtt
prismatic.ActuatorType = Enum.ActuatorType.Servo
prismatic.Speed = speed
prismatic.ServoMaxForce = 100000
prismatic.LowerLimit = 0
prismatic.UpperLimit = topY - bottomY
prismatic.Parent = platform
platform.Anchored = false
return prismatic -- set .TargetPosition to move
endSwinging Platform
摆动平台
luau
local function createSwing(platform: BasePart, pivot: Vector3, maxAngle: number)
platform.Anchored = false
local pivotAtt = Instance.new("Attachment")
pivotAtt.WorldPosition = pivot
pivotAtt.Parent = workspace.Terrain -- fixed world point
local platformAtt = Instance.new("Attachment")
platformAtt.Position = platform.CFrame:PointToObjectSpace(pivot)
platformAtt.Parent = platform
local hinge = Instance.new("HingeConstraint")
hinge.Attachment0 = pivotAtt
hinge.Attachment1 = platformAtt
hinge.LimitsEnabled = true
hinge.LowerAngle = -maxAngle
hinge.UpperAngle = maxAngle
hinge.Parent = platform
return hinge
endluau
local function createSwing(platform: BasePart, pivot: Vector3, maxAngle: number)
platform.Anchored = false
local pivotAtt = Instance.new("Attachment")
pivotAtt.WorldPosition = pivot
pivotAtt.Parent = workspace.Terrain -- fixed world point
local platformAtt = Instance.new("Attachment")
platformAtt.Position = platform.CFrame:PointToObjectSpace(pivot)
platformAtt.Parent = platform
local hinge = Instance.new("HingeConstraint")
hinge.Attachment0 = pivotAtt
hinge.Attachment1 = platformAtt
hinge.LimitsEnabled = true
hinge.LowerAngle = -maxAngle
hinge.UpperAngle = maxAngle
hinge.Parent = platform
return hinge
endNetwork Ownership
网络所有权
Critical for physics objects: By default, the nearest player "owns" the physics simulation of unanchored parts. This means exploiters can fling your physics objects.
luau
-- Keep physics server-authoritative
local function setServerOwnership(model: Model)
for _, part in model:GetDescendants() do
if part:IsA("BasePart") and not part.Anchored then
part:SetNetworkOwner(nil) -- server owns
end
end
end
-- Give ownership to driver (for responsive vehicles)
local function setDriverOwnership(vehicle: Model, player: Player)
for _, part in vehicle:GetDescendants() do
if part:IsA("BasePart") and not part.Anchored then
part:SetNetworkOwner(player)
end
end
endTrade-off: Server ownership = secure but laggy (server tick rate). Player ownership = responsive but exploitable. For vehicles, give ownership to the driver. For NPCs and world objects, keep server-owned.
物理对象的关键注意事项:默认情况下,最近的玩家“拥有”未锚定部件的物理模拟权。这意味着 exploit 者可以随意操纵你的物理对象。
luau
-- Keep physics server-authoritative
local function setServerOwnership(model: Model)
for _, part in model:GetDescendants() do
if part:IsA("BasePart") and not part.Anchored then
part:SetNetworkOwner(nil) -- server owns
end
end
end
-- Give ownership to driver (for responsive vehicles)
local function setDriverOwnership(vehicle: Model, player: Player)
for _, part in vehicle:GetDescendants() do
if part:IsA("BasePart") and not part.Anchored then
part:SetNetworkOwner(player)
end
end
end权衡:服务器所有权 = 安全但延迟高(受服务器 tick 速率限制)。玩家所有权 = 响应迅速但存在被 exploit 的风险。对于载具,将所有权赋予驾驶员;对于 NPC 和世界对象,保持服务器所有权。
Common Mistakes
常见错误
- Forgetting Anchored = false: Constraints do nothing on anchored parts.
- Missing Attachments: Constraints need Attachment0 AND Attachment1. Missing one = silent failure.
- No network ownership control: Physics objects get owned by nearest player. Exploiters fling them.
- Over-constraining: Too many constraints on one assembly = physics solver instability (jitter).
- No mass tuning: Default density makes small parts too light. Use CustomPhysicalProperties.
- Touched for projectiles: Touched fires for every contact. Use Raycast for hitscan, Touched only for slow physics projectiles.
- No lifetime on projectiles: Forgotten projectiles accumulate and kill server performance.
- 忘记设置Anchored = false:约束对锚定部件无效。
- 缺少Attachment:约束需要同时设置Attachment0和Attachment1。缺少任意一个都会导致静默失败。
- 未控制网络所有权:物理对象会被最近的玩家接管所有权,exploit者可随意操纵它们。
- 过度约束:单个组件上添加过多约束会导致物理求解器不稳定(抖动)。
- 未调整质量:默认密度会使小型部件过轻。使用CustomPhysicalProperties调整。
- 用Touched处理投射物:Touched会在每次接触时触发。命中扫描投射物使用Raycast,仅对低速物理投射物使用Touched。
- 未设置投射物生命周期:未清理的投射物会不断累积,导致服务器性能下降。