DragDetector 实例促进并鼓励与游戏中的 3D 对象进行交互,例如打开门和抽屉、移动部件、抓取和投掷保龄球、拉回并发射弹弓等。主要功能包括:
在任何 BasePart 或 Model 下放置一个 DragDetector,以通过所有输入(鼠标、触摸、游戏手柄和 VR)使其可拖动,而无需编写一行代码。
脚本可以 响应拖动对象的操作,以驱动 UI 或做出逻辑决策,例如根据滑动墙开关调光器调整房间的光线级别。
玩家可以操作固定的部件或模型,释放时它们将保持在您放置的位置。
DragDetectors 在 Studio 中工作,只要您不使用 选择、移动、缩放或 旋转 工具,这使得在编辑时更容易测试和调整可拖动对象。
使对象可拖动
要使任何部件或模型可拖动,只需将 DragDetector 作为直接子项添加。
从菜单中插入一个 DragDetector。

自定义拖动检测器
拖动样式
DragDetectors 将光标运动映射到虚拟线和面,以计算建议的 3D 运动。通过 DragStyle 属性,您可以选择不同的映射以满足您的需求。例如,TranslatePlane 在虚拟平面中产生平移,而 RotateAxis 在虚拟轴上产生旋转。
| 设置 | 描述 |
|---|---|
| TranslateLine | 沿检测器的 Axis 进行 1D 运动,默认是世界 Y 轴。 |
| TranslatePlane | 在与检测器的 Axis 垂直的平面内进行 2D 运动,默认是世界 XZ 平面。 |
| TranslatePlaneOrLine | 在与检测器的 Axis 垂直的平面内进行 2D 运动,并且当 修饰符 激活时,沿检测器的 Axis 进行 1D 运动。 |
| TranslateLineOrPlane | 沿检测器的 Axis 进行 1D 运动,并且当 修饰符 激活时,在与检测器的 Axis 垂直的平面内进行 2D 运动。 |
| TranslateViewPlane | 在与相机视图垂直的平面内进行 2D 运动。在此模式下,平面会不断更新,即使在拖动时,也会始终面向相机的当前视图。 |
| RotateAxis | 围绕检测器的 Axis 进行旋转,默认是世界 Y 轴。 |
| RotateTrackball | 轨迹球旋转,通过 TrackballRadialPullFactor 和 TrackballRollFactor 属性进一步自定义。 |
| BestForDevice | 鼠标和游戏手柄使用 TranslatePlaneOrLine;触摸使用 TranslatePlane;VR 使用 6DOF。 |
| Scriptable | 通过 SetDragStyleFunction() 提供的自定义函数计算所需的运动。 |
拖动方向
默认情况下,3D 运动和相关的 DragStyle 映射到世界空间。然而,您可能希望更改 ReferenceInstance、Orientation 或 Axis,例如在构建具有可调部件的 模型 时。
| 属性 | 描述 | 默认 |
|---|---|---|
| ReferenceInstance | 一个实例,其枢轴提供拖动检测器的 参考框架。DragFrame 是相对于此参考框架表达的,可以通过 GetReferenceFrame() 检索。如果参考框架为 nil,则平移将在世界空间中沿 Axis 属性的方向(或在与其垂直的平面内)进行。 | nil |
| Orientation | 指定相对于参考框架的运动轴的 YXZ 旋转(不改变参考框架本身的方向)。线性平移和轴向旋转将在此重新定向的 Y 轴上进行,平面内的平移将在 XZ 平面内进行。更改此值会自动更新 Axis,反之亦然。 | (0, 0, 0) |
| Axis | 主要运动轴,相对于参考框架表达。更改此值会自动更新 Orientation,反之亦然。 | (0, 1, 0) |
对运动的响应
ResponseStyle 属性指定对象如何响应建议的运动,具体取决于对象是否 Anchored。
| 设置 | 固定行为 | 非固定行为 |
|---|---|---|
| Geometric | 在运行的游戏和 Studio 编辑模式中,固定对象的位置/方向将更新以准确反映建议的运动。 | 对于非固定对象,行为与固定对象相同。然而,在运行的游戏中,该对象将在拖动开始时被固定,并在拖动释放时恢复为非固定。 |
| Physical | 固定对象将默认采用 Geometric 行为,因为它不受力的影响。 | 非固定对象将受到 约束力 的影响,这些力试图将其移动到建议运动所给出的期望位置和/或方向。 |
| Custom | 对象将完全不移动,但 DragFrame 仍将被更新,您可以根据需要 响应拖动操作。 | (与固定相同) |
轴和运动限制
默认情况下,除了 DragStyle 的固有限制外,没有 3D 运动的限制。如果需要,您可以对平移和旋转应用最小和最大限制。请注意,这些不是约束;它们仅仅阻碍拖动检测器生成运动的尝试,以保持在限制范围内。
| 属性 | 描述 | 默认 |
|---|---|---|
| MinDragTranslation MaxDragTranslation | 限制每个维度的拖动平移。如果 MaxDragTranslation 大于 MinDragTranslation,平移将被限制在该范围内。 | (0, 0, 0) |
| MinDragAngle MaxDragAngle | 仅在 DragStyle 设置为 RotateAxis 时相关。如果 MaxDragAngle 大于 MinDragAngle,旋转将被限制在该范围内。 | 0 |
拖动权限
玩家与给定拖动检测器实例交互的权限可以通过 PermissionPolicy 属性指定。默认情况下,该属性设置为 Enum.DragDetectorPermissionPolicy.Everybody,也可以更改以支持脚本权限控制,如代码示例所示。
| 设置 | 描述 |
|---|---|
| Nobody | 没有玩家可以与 DragDetector 交互。 |
| Everybody | 所有玩家都可以与 DragDetector 交互。 |
| Scriptable | 玩家的拖动权限将由通过 SetPermissionPolicyFunction() 注册的函数决定。在此设置下,未能注册函数或返回无效结果将阻止所有玩家进行拖动。 |
local dragDetector = script.Parent.DragDetector
dragDetector.PermissionPolicy = Enum.DragDetectorPermissionPolicy.Scriptable
dragDetector:SetPermissionPolicyFunction(function(player, part)
if player and player:GetAttribute("IsInTurn") then
return true
elseif part and not part:GetAttribute("IsDraggable") then
return false
else
return true
end
end)物理响应
假设拖动器的 响应样式 设置为 Physical,并且应用于非固定对象,该对象将受到约束力的影响,这些力试图将其移动到建议运动所给出的期望位置/方向。您可以通过以下属性进一步自定义物理响应:
| 属性 | 描述 | 默认 |
|---|---|---|
| ApplyAtCenterOfMass | 当为 false 时,拖动力应用于用户单击的点。当为 true 时,力应用于对象的质心。 | false |
| MaxForce | 对象达到其目标时施加的最大力。 | 10000000 |
| MaxTorque | 对象达到其目标时施加的最大扭矩。 | 10000 |
| Responsiveness | 更高的值使对象更快地达到其目标。 | 10 |
修饰符输入
某些 DragStyle 模式允许用户按住 修饰符 键/按钮以以不同方式操作拖动的对象。默认情况下,修饰符为 PC 上的 LeftControl,游戏手柄上的 ButtonR1,或 VR 上的 ButtonL2。您可以通过拖动检测器实例的 KeyboardModeSwitchKeyCode、GamepadModeSwitchKeyCode 或 VRSwitchKeyCode 属性自定义这些修饰符。
复制
当 RunLocally 属性为 false(默认)时,客户端解释所有输入以生成数据并将其发送到服务器以执行拖动。在此模式下,所有自定义事件信号和注册函数必须在服务器 Scripts 中。
当 RunLocally 属性为 true 时,没有事件被复制到服务器。所有自定义事件信号和注册函数必须在客户端 LocalScripts 中,您必须使用 远程事件 将必要的更改传播到服务器。
脚本对点击和拖动的响应
通过 事件信号、属性更改、Scriptable 拖动样式和自定义函数,脚本可以响应拖动对象的操作,以驱动 UI 或做出逻辑决策,例如根据滑动墙开关调光器调整房间的光线级别。
事件信号
通过以下事件信号,您可以检测用户何时开始、继续和结束拖动对象。
| 事件 | 描述 |
|---|---|
| DragStart | 当用户开始拖动对象时触发。 |
| DragContinue | 当用户在 DragStart 被触发后继续拖动对象时触发。 |
| DragEnd | 当用户停止拖动对象时触发。 |
local dragDetector = script.Parent.DragDetector
local highlight = Instance.new("Highlight")
highlight.Enabled = false
highlight.Parent = script.Parent
dragDetector.DragStart:Connect(function()
highlight.Enabled = true
end)
dragDetector.DragContinue:Connect(function()
end)
dragDetector.DragEnd:Connect(function()
highlight.Enabled = false
end)拖动框架变化
除了 事件信号 外,您还可以直接监控检测器的 DragFrame 的变化。
local dragDetector = script.Parent.DragDetector
dragDetector:GetPropertyChangedSignal("DragFrame"):Connect(function()
local currentDragTranslation = dragDetector.DragFrame.Position
print(currentDragTranslation)
end)脚本化拖动样式
如果您将检测器的 DragStyle 设置为 Scriptable,您可以提供自己的函数,该函数接受一个 Ray 并返回一个世界空间的 CFrame。检测器将移动运动,以便拖动的对象到达该自定义位置/方向。
local Workspace = game:GetService("Workspace")
local dragDetector = script.Parent.DragDetector
dragDetector.DragStyle = Enum.DragDetectorDragStyle.Scriptable
local cachedHitPoint = Vector3.zero
local cachedHitNormal = Vector3.yAxis
local function followTheCursor(cursorRay)
-- 排除被拖动对象的射线检测
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {dragDetector.Parent}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
local hitPoint = Vector3.zero
local hitNormal = Vector3.yAxis
local raycastResult = Workspace:Raycast(cursorRay.Origin, cursorRay.Direction, raycastParams)
if raycastResult then
hitPoint = raycastResult.Position
hitNormal = raycastResult.Normal.Unit
else
hitPoint = cachedHitPoint
hitNormal = cachedHitNormal
end
cachedHitPoint = hitPoint
cachedHitNormal = hitNormal
local lookDir1 = hitNormal:Cross(Vector3.xAxis)
local lookDir2 = hitNormal:Cross(Vector3.yAxis)
local lookDir = if lookDir1.Magnitude > lookDir2.Magnitude then lookDir1.Unit else lookDir2.Unit
return CFrame.lookAt(hitPoint, hitPoint + lookDir, hitNormal)
end
dragDetector:SetDragStyleFunction(followTheCursor)自定义约束函数
拖动检测器没有关于网格和对齐的内置运动规则,但您可以注册自定义约束函数,以在应用之前编辑检测器的 DragFrame。例如,您可以通过将位置四舍五入到网格增量的倍数来保持运动在网格上,或模拟棋局中每个棋子的合法运动规则。
local dragDetector = script.Parent.DragDetector
local startPartPosition = nil
local SNAP_INCREMENT = 4
dragDetector.DragStart:Connect(function()
startPartPosition = script.Parent.Position
end)
dragDetector.DragEnd:Connect(function()
startPartPosition = nil
end)
local function snapToWorldGrid(proposedMotion)
if startPartPosition == nil then
return proposedMotion
end
local snapIncrement = SNAP_INCREMENT // 1
if snapIncrement < 1 then
return proposedMotion
end
local newWorldPosition = startPartPosition + proposedMotion.Position
local roundedX = ((newWorldPosition.X / snapIncrement + 0.5) // 1) * snapIncrement
local roundedY = ((newWorldPosition.Y / snapIncrement + 0.5) // 1) * snapIncrement
local roundedZ = ((newWorldPosition.Z / snapIncrement + 0.5) // 1) * snapIncrement
local newRoundedWorldPosition = Vector3.new(roundedX, roundedY, roundedZ)
return proposedMotion.Rotation + (newRoundedWorldPosition - startPartPosition)
end
local connection = dragDetector:AddConstraintFunction(2, snapToWorldGrid)
-- 在适用时,通过调用 connection:Disconnect() 移除约束函数示例用法
非固定物理对象
拖动检测器的基本实现是一个塔平衡游戏,玩家必须小心地移除部件并尝试保持塔的直立。在以下塔结构中,每个部件都有一个子 DragDetector,其默认 DragStyle 为 TranslatePlane,以便玩家可以将部件向外拉,但不能向上或向下移动。
具有可调部件的固定模型
您可以轻松创建和共享主要是固定的模型,但其中有一个或多个子部件/模型,玩家可以拖动。例如,以下桌子有两个抽屉,玩家可以打开以检查里面的东西。
拖动检测器和约束
您可以将拖动检测器与 Constraints 结合使用,例如木偶。在以下设置中,控制手柄是固定的,身体部件是非固定的,约束将木偶保持在一起。使用 TranslateViewPlane DragStyle 移动手柄使木偶跳舞,单个身体部件也可以使用拖动检测器移动,同时模型保持其完整性。
3D 用户界面
通过拖动检测器,3D 用户界面很容易实现,例如根据滑动开关调光器调整 SpotLight 的亮度。您还可以单独检测 X 和 Z 轴,以控制 3D 用户界面的两个不同方面,例如 Size、Speed 和 Color 的 ParticleEmitter。
local model = script.Parent
local slider = model.SliderPart
local originPart = model.OriginPart
local emitter = script.Parent.EmitterPart.ParticleEmitter
local dragDetector = slider.DragDetector
dragDetector.ReferenceInstance = originPart
dragDetector.MinDragTranslation = Vector3.zero
dragDetector.MaxDragTranslation = Vector3.new(10, 0, 10)
local dragRangeX = dragDetector.MaxDragTranslation.X - dragDetector.MinDragTranslation.X
local dragRangeZ = dragDetector.MaxDragTranslation.Z - dragDetector.MinDragTranslation.Z
local MIN_PARTICLE_SIZE = 1
local MAX_PARTICLE_SIZE = 1.5
local MIN_PARTICLE_SPEED = 2.5
local MAX_PARTICLE_SPEED = 5
local COLOR1 = Color3.fromRGB(255, 150, 0)
local COLOR2 = Color3.fromRGB(255, 0, 50)
local function updateParticles(emitter)
local dragFactorX = (dragDetector.DragFrame.Position.X - dragDetector.MinDragTranslation.X) / dragRangeX
local dragFactorZ = (dragDetector.DragFrame.Position.Z - dragDetector.MinDragTranslation.Z) / dragRangeZ
-- 根据拖动检测器 X 因素调整粒子大小和速度
emitter.Size = NumberSequence.new{
NumberSequenceKeypoint.new(0, 0),
NumberSequenceKeypoint.new(0.1, MIN_PARTICLE_SIZE + ((MAX_PARTICLE_SIZE - MIN_PARTICLE_SIZE) * dragFactorX)),
NumberSequenceKeypoint.new(1, 0)
}
local speed = MIN_PARTICLE_SPEED + ((MAX_PARTICLE_SPEED - MIN_PARTICLE_SPEED) * dragFactorX)
emitter.Speed = NumberRange.new(speed, speed * 1.2)
-- 根据拖动检测器 Z 因素调整粒子颜色
local color = COLOR2:Lerp(COLOR1, dragFactorZ)
emitter.Color = ColorSequence.new{
ColorSequenceKeypoint.new(0, color),
ColorSequenceKeypoint.new(1, color)
}
end
dragDetector:GetPropertyChangedSignal("DragFrame"):Connect(function()
updateParticles(emitter)
end)