碰撞

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

当两个 3D 对象在 3D 世界中接触时,就会发生碰撞。为了自定义碰撞处理,BasePart 提供了一组 碰撞事件碰撞过滤 技术,以便您可以控制哪些物理组件与其他组件发生碰撞。

碰撞事件

碰撞 事件 发生在两个 BaseParts 在 3D 世界中接触或停止接触时。您可以通过 TouchedTouchEnded 事件来检测这些碰撞,这些事件会在任一部分的 CanCollide 属性值无论如何都会触发。在考虑部件的碰撞处理时,请注意以下几点:

Touched

Touched 事件在 BasePart 与另一个部件或与 Terrain 体素接触时触发。它仅因 物理模拟 而触发,当部件的 PositionCFrame 被显式设置为与另一个部件或体素相交时不会触发。

以下代码模式展示了如何将 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 事件,您可以实现一个简单的去抖动系统,通过实例 属性 强制执行“冷却”周期。

带冷却的部件碰撞
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

TouchEnded 事件在整个 BasePart 的碰撞边界退出另一个 BasePart 或填充的 Terrain 体素的边界时触发。它仅因 物理模拟 而触发,当部件的 PositionCFrame 被显式设置为停止与另一个部件或体素相交时不会触发。

以下代码模式展示了如何将 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 自动属于此默认组,除非分配到其他组,这意味着它们将与 默认 组中的所有其他对象发生碰撞。

要创建一个新的碰撞组:

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

    在列表视图中添加到碰撞组编辑器的新组
  2. 如有必要,重复此过程,为每个组选择一个独特且描述性的名称。请注意,您可以通过单击其字段或选择它并单击 重命名 按钮在开发过程中更改组的名称。

    在碰撞组编辑器中指示重命名组的按钮和字段

配置组碰撞

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

在以下示例中,Cubes 组中的对象将 Doors 组中的对象发生碰撞。

在碰撞组编辑器的列表视图中配置的组

将对象分配到组

要通过 Studio 编辑器将对象分配到您已 注册 的组:

  1. 选择一个或多个符合碰撞组条件的 BaseParts

  2. 通过单击其行的 按钮将它们分配到该组。对象一次只能属于一个碰撞组,因此将它们放入新组会将它们从当前组中移除。

    在碰撞组编辑器中指示将选定部件添加到组的加号按钮

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

所选碰撞组指示为部件的 CollisionGroup 属性

StudioSelectable 碰撞组

Studio 中的工具使用碰撞过滤系统来确定在 3D 视口中单击时哪些对象是选择候选者。分配的碰撞组与 StudioSelectable 不发生碰撞的对象将被忽略。

例如,如果您在赛车游戏中有检查点,其有效区域由大型透明部件定义,您可以将它们分配到 Checkpoints 碰撞组,然后使该组与 StudioSelectable 不可碰撞,以便在编辑底层地图几何时不会妨碍。

配置为与 StudioSelectable 组不可碰撞的 Checkpoints 组

对于插件代码,建议您在查找光标下的部件时,将 "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 玩家角色默认会相互碰撞。这可能导致有趣但意外的游戏玩法,例如角色跳到彼此身上以到达特定区域。如果这种行为不受欢迎,您可以通过以下 ScriptServerScriptService 中防止它。

脚本 - 禁用角色碰撞
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.TouchedBasePart.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

网格和固体模型碰撞

MeshPartPartOperation(通过 固体建模 连接的部件)是 BasePart 的子类,因此网格和固体建模的部件继承与常规部件相同的 碰撞事件碰撞过滤 选项。然而,由于网格和固体建模的部件通常具有更复杂的几何形状,它们具有独特的 CollisionFidelity 属性,该属性决定物理边界与视觉表示在碰撞处理中的对齐精度。

CollisionFidelity 属性具有以下选项,按保真度和性能影响从最低到最高排序:

  • 盒子 — 创建一个边界碰撞盒,适合小型或非交互对象。
  • 外壳 — 生成一个凸包,适合凹陷或腔体不明显的对象。
  • 默认 — 生成一个近似的碰撞形状,支持凹陷,适合具有半详细交互需求的复杂对象。
  • 精确凸分解 — 提供最精确的保真度,但仍不是视觉的 1:1 表示。此选项具有最高的性能成本,并且引擎计算所需时间最长。
城堡塔的原始网格

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

©2026 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。