当两个 3D 对象在 3D 世界中接触时,就会发生碰撞。为了自定义碰撞处理,BasePart 提供了一组 碰撞事件 和 碰撞过滤 技术,以便您可以控制哪些物理组件与其他组件发生碰撞。
碰撞事件
碰撞 事件 发生在两个 BaseParts 在 3D 世界中接触或停止接触时。您可以通过 Touched 和 TouchEnded 事件来检测这些碰撞,这些事件会在任一部分的 CanCollide 属性值无论如何都会触发。在考虑部件的碰撞处理时,请注意以下几点:
- 部件的 CanCollide 属性影响它是否会与其他部件 物理 碰撞并导致力作用于它们。即使某个部件的 CanCollide 被禁用,您仍然可以通过 Touched 和 TouchEnded 事件检测接触和非接触。
Touched
Touched 事件在 BasePart 与另一个部件或与 Terrain 体素接触时触发。它仅因 物理模拟 而触发,当部件的 Position 或 CFrame 被显式设置为与另一个部件或体素相交时不会触发。
以下代码模式展示了如何将 Touched 事件连接到自定义的 onTouched() 函数。请注意,该事件将 otherPart 参数发送到函数,指示参与碰撞的另一个部件。
local part = workspace.Part
local function onTouched(otherPart)
print(part.Name .. " 与 " .. otherPart.Name .. " 碰撞")
end
part.Touched:Connect(onTouched)请注意,Touched 事件可能会因微妙的物理碰撞而快速多次触发,例如当一个移动的物体“稳定”到一个静止位置时,或者当碰撞涉及到一个 多部件模型 时。为了避免触发过多的 Touched 事件,您可以实现一个简单的去抖动系统,通过实例 属性 强制执行“冷却”周期。
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
TouchEnded 事件在整个碰撞边界的 BasePart 退出另一个 BasePart 或填充的 Terrain 体素的边界时触发。它仅因 物理模拟 而触发,当部件的 Position 或 CFrame 被显式设置为停止与另一个部件或体素相交时不会触发。
以下代码模式展示了如何将 TouchEnded 事件连接到自定义的 onTouchEnded() 函数。与 Touched 一样,该事件将 otherPart 参数发送到函数,指示参与碰撞的另一个部件。
local part = workspace.Part
local function onTouchEnded(otherPart)
print(part.Name .. " 不再接触 " .. otherPart.Name)
end
part.TouchEnded:Connect(onTouchEnded)碰撞过滤
碰撞 过滤 定义哪些物理部件与其他部件发生碰撞。您可以通过 碰撞组 为多个对象配置过滤,或者可以通过 NoCollisionConstraint 实例控制 部件到部件 的碰撞。
碰撞组
碰撞 组 允许您将 BaseParts 分配到专用组,并指定它们是否与其他组中的对象发生碰撞。非碰撞组中的部件会完全穿过彼此,即使两个部件的 CanCollide 属性都设置为 true。碰撞组是在 Workspace 或 WorldModel 的基础上创建和配置的。
您可以通过 Studio 的 碰撞组 编辑器轻松设置碰撞组,该编辑器可以通过 Studio 的 窗口 ⟩ 3D 菜单访问。
注册组
Workspace 存储其自己的可配置碰撞组状态。此外,任何 WorldModel 都可以选择使用自己的碰撞组进行查询。
编辑器包括一个 默认 碰撞组,无法重命名或删除。所有 BaseParts 默认属于此默认组,除非分配到其他组,这意味着它们将与 默认 组中的所有其他对象发生碰撞。
要创建新的碰撞组:
默认情况下,组在 Workspace 上注册,但如果您的地方中至少有一个 WorldModel,则在碰撞组编辑器的顶部会出现一个选择下拉菜单。使用下拉菜单在不同的 WorldModel 实例之间配置碰撞组。

对于任何 WorldModel,您可以启用 使用工作区碰撞组 以使其在查询中使用 Workspace 碰撞组。

点击编辑器面板顶部的 添加组 按钮,输入新组名称,然后按 Enter。新组将出现在列表视图的两列中,或在表视图的左列和上行中。

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

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

将对象分配到组
要通过 Studio 编辑器将对象分配到您已 注册 的组:
选择一个或多个符合碰撞组条件的 BaseParts。
StudioSelectable 组
Studio 中的工具使用碰撞过滤系统来确定在 3D 视口中单击时哪些对象是选择候选者。分配的碰撞组与 StudioSelectable 不发生碰撞的对象将被忽略。
例如,如果您在赛车游戏中有检查点,其有效区域由大型透明部件定义,您可以将它们分配到 Checkpoints 碰撞组,然后使该组与 StudioSelectable 不可碰撞,以便在编辑底层地图几何时不会干扰。

对于插件代码,建议您在查找光标下的部件时将 "StudioSelectable" 作为 RaycastParams 的碰撞组过滤器。这使您的插件能够匹配创作者从内置 Studio 工具中学到的选择机制。
local UserInputService = game:GetService("UserInputService")
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 Players = game:GetService("Players")
workspace:RegisterCollisionGroup("Characters")
workspace: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 属性具有以下选项,按精度和性能影响从低到高排序:
- Box — 创建一个边界碰撞框,适合小型或非交互对象。
- Hull — 生成一个凸包,适合凹陷或腔体不明显的对象。
- Default — 生成一个近似的碰撞形状,支持凹陷,适合具有半详细交互需求的复杂对象。
- PreciseConvexDecomposition — 提供最精确的精度,但仍不是视觉的 1:1 表示。此选项的性能成本最高,且引擎计算所需时间最长。

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

