当两个3D对象在3D世界中接触时,便会发生碰撞。为了自定义碰撞处理,BasePart提供了一组碰撞事件和碰撞过滤技术,这样您可以控制哪些物理组件与其他组件发生碰撞。
碰撞事件
碰撞事件在两个BaseParts在3D世界中接触或停止接触时发生。您可以通过Touched和TouchEnded事件来检测这些碰撞,这些事件会在任一部分的CanCollide属性值无论如何的情况下触发。在考虑部分的碰撞处理时,请注意以下几点:
接触
Touched事件在BasePart与另一个部分或与Terrain体素接触时触发。它仅因物理模拟而触发,当部分的Position或CFrame被明确设置导致其与另一个部分或体素交叉时,将不会触发。
以下代码模式展示了如何将Touched事件连接到自定义的onTouched()函数。请注意,该事件将otherPart参数传递给该函数,指示发生碰撞的另一个部分。
部分碰撞
local Workspace = game:GetService("Workspace")
local part = Workspace.Part
local function onTouched(otherPart)
print(part.Name .. " 与 " .. otherPart.Name .. " 碰撞")
end
part.Touched:Connect(onTouched)
需要注意的是,Touched事件可能会因细微的物理碰撞而快速多次触发,例如当移动对象“安放”到静止位置时,或者碰撞涉及到多部分模型时。为了避免触发比必要的更多Touched事件,您可以实现一个简单的去抖动(debounce)系统,通过一个实例属性强制执行“冷却”周期。
具有冷却的部分碰撞
local Workspace = game:GetService("Workspace")
local part = Workspace.Part
local COOLDOWN_TIME = 1
local function onTouched(otherPart)
if not part:GetAttribute("Touched") then
print(part.Name .. " 与 " .. otherPart.Name .. " 碰撞")
part:SetAttribute("Touched", true) -- 将属性设置为true
task.wait(COOLDOWN_TIME) -- 等待冷却时间
part:SetAttribute("Touched", false) -- 重置属性
end
end
part.Touched:Connect(onTouched)
接触结束
TouchEnded事件在整个BasePart的碰撞边界退出另一个BasePart或一个填充的Terrain体素的边界时触发。它仅因物理模拟而触发,当部分的Position或CFrame被明确设置,导致其停止与另一个部分或体素交叉时,将不会触发。
以下代码模式展示了如何将TouchEnded事件连接到自定义的onTouchEnded()函数。类似于Touched,该事件也将otherPart参数传递给该函数,指示相关的另一个部分。
非碰撞检测
local Workspace = game:GetService("Workspace")
local part = Workspace.Part
local function onTouchEnded(otherPart)
print(part.Name .. " 不再接触 " .. otherPart.Name)
end
part.TouchEnded:Connect(onTouchEnded)
碰撞过滤
碰撞过滤定义了哪些物理部分与其他部分碰撞。您可以通过碰撞组为多个对象配置过滤,或者您可以通过NoCollisionConstraint实例控制部分到部分的碰撞。
碰撞组
碰撞组允许您将BaseParts分配到专用组中,并指定它们是否与其他组中的部分碰撞。属于不发生碰撞组的部分可以完全穿越彼此,即使两个部分的CanCollide属性都设置为true。
您可以通过Studio的碰撞组编辑器轻松设置碰撞组,该编辑器可以通过Studio的窗口 ⟩ 3D菜单访问。
编辑器可以在列表视图中运行,该视图偏好停靠在Studio左侧或右侧,或在更宽的表格视图中运行,偏好停靠在顶部或底部。

注册组
编辑器包含一个默认碰撞组,无法重命名或删除。所有BaseParts自动属于此默认组,除非分配到其他组,这意味着它们将与默认组中的所有其他对象发生碰撞。
要创建一个新的碰撞组:
点击编辑器面板顶部的添加组按钮,输入一个新组名称,然后按Enter。新组将出现在列表视图的两列中,或在表格视图中的左列和上行中。

如有必要,重复此过程,为每个组选择一个独特且描述性的名称。请注意,您可以通过点击字段或选择它并单击重命名按钮在开发过程中更改组名称。

配置组碰撞
在默认配置下,所有组中的对象都会彼此发生碰撞。要防止一组中的对象与另一组中的对象碰撞,请在相应的行/列中取消选中该框。
在以下示例中,Cubes组中的对象将不与Doors组中的对象发生碰撞。

将对象分配到组
要通过Studio编辑器将对象分配给您已注册的组:
选择一个或多个符合碰撞组条件的BaseParts。
通过单击其行的**⊕**按钮将它们分配到组中。对象一次只能属于一个碰撞组,因此将它们放入一个新组将使它们从当前组中移除。

一旦分配,新组将反映在对象的CollisionGroup属性下。

Studio可选择的碰撞组
Studio中的工具使用碰撞过滤系统来确定在3D视口中单击时哪些对象可以被选中。其分配的碰撞组与StudioSelectable不发生碰撞的对象将被忽略。
例如,如果您在赛车体验中有检查点,其有效区域由大型透明部分定义,您可以将它们分配给一个检查点碰撞组,并使该组与StudioSelectable不相撞,以便在您编辑基础地图几何形状时,它们不会妨碍您。

对于插件代码,建议在查找光标下的部分时,将"StudioSelectable"作为您RaycastParams的碰撞组过滤器。这使得您的插件能够匹配创作者们已学会从内置Studio工具中期待的选择机制。
推荐插件选择射线local UserInputService = game:GetService("UserInputService")local Workspace = game:GetService("Workspace")local raycastParams = RaycastParams.new()raycastParams.CollisionGroup = "StudioSelectable" -- 以遵循惯例raycastParams.BruteForceAllSlow = true -- 以便于“CanQuery”为"false"的部分也能被选中local mouseLocation = UserInputService:GetMouseLocation()local mouseRay = Workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)local filteredSelectionHit = Workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 10000, raycastParams)
部分到部分的过滤
为了防止两个特定部分之间的碰撞而不设置碰撞组,例如车辆的轮子与底盘之间,可以考虑使用无碰撞约束。优点包括:
- 不需要碰撞组和/或配置脚本,因此您可以轻松创建和共享具有自定义碰撞过滤的模型。
- 连接的部分将不会相互发生碰撞,但仍然可以与其他对象发生碰撞。
禁用角色碰撞
Roblox玩家角色默认会相互发生碰撞。这可能导致有趣但意外的游戏玩法,例如角色跳到彼此之上以到达特定区域。如果此行为不受欢迎,您可以通过以下Script在ServerScriptService中防止它。
脚本 - 禁用角色碰撞
local PhysicsService = game:GetService("PhysicsService")
local Players = game:GetService("Players")
PhysicsService:RegisterCollisionGroup("Characters")
PhysicsService:CollisionGroupSetCollidable("Characters", "Characters", false)
local function onDescendantAdded(descendant)
-- 为任何部分后代设置碰撞组
if descendant:IsA("BasePart") then
descendant.CollisionGroup = "Characters"
end
end
local function onCharacterAdded(character)
-- 处理现有和新后代以进行物理设置
for _, descendant in character:GetDescendants() do
onDescendantAdded(descendant)
end
character.DescendantAdded:Connect(onDescendantAdded)
end
Players.PlayerAdded:Connect(function(player)
-- 检测玩家的角色被添加时
player.CharacterAdded:Connect(onCharacterAdded)
end)
模型碰撞
Model对象是部件的容器,而不是从BasePart继承,因此它们不能直接连接到BasePart.Touched或BasePart.TouchEnded事件。要确定一个模型是否触发碰撞事件,您需要遍历其子对象,并将自定义的onTouched()和onTouchEnded()函数连接到每个子BasePart。
以下代码示例将所有BaseParts的多部分模型连接到碰撞事件,并跟踪与其他部分的总碰撞次数。
模型碰撞
local model = script.Parent
local numTouchingParts = 0
local function onTouched(otherPart)
-- 忽略模型与自身交叉的实例
if otherPart:IsDescendantOf(model) then return end
-- 增加接触的模型部件计数
numTouchingParts += 1
print(model.Name, "与", otherPart.Name, "交叉 | 接触的模型部件:", numTouchingParts)
end
local function onTouchEnded(otherPart)
-- 忽略模型与自身不重叠的实例
if otherPart:IsDescendantOf(model) then return end
-- 减少接触的模型部件计数
numTouchingParts -= 1
print(model.Name, "从", otherPart.Name, "脱离 | 接触的模型部件:", numTouchingParts)
end
for _, child in model:GetChildren() do
if child:IsA("BasePart") then
child.Touched:Connect(onTouched)
child.TouchEnded:Connect(onTouchEnded)
end
end
网格和实体模型碰撞
MeshPart和PartOperation(通过实体建模连接的部件)是BasePart的子类,因此网格和实体建模部件继承与常规部件相同的碰撞事件和碰撞过滤选项。然而,由于网格和实体建模部件通常具有更复杂的几何形状,因此它们具有独特的CollisionFidelity属性,该属性决定物理边界与可视表示在碰撞处理中的对齐精度。
CollisionFidelity属性提供以下选项,按精度和性能影响从低到高排列:
- 盒子 — 创建一个包围碰撞盒,适合小型或非交互对象。
- 外壳 — 生成一个凸包,适合凹陷或空腔不那么明显的对象。
- 默认 — 生成一个近似的碰撞形状,支持凹形,适合具有半详细交互需求的复杂对象。
- 精确凸分解 — 提供最高的精度,但仍不是与可视化的1:1表示。此选项具有最高的性能成本,计算响应时间较长。

有关碰撞精度选项对性能影响以及如何减轻影响的更多信息,请参见性能优化。