3D 拖拽检测器

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

DragDetector 实例促进和鼓励与体验中 3D 对象的互动,例如开门、抽屉、滑动部件、抓住和投掷保龄球、拉回并发射弹弓等等。主要特点包括:

  • 在任何 BasePartModel 下放置 DragDetector,即可通过所有输入(鼠标、触摸、游戏手柄和 VR)实现可拖动,而无需编写任何代码。

  • 从多个 拖拽样式 中进行选择,定义对象如何 响应运动,并可选择性地应用 轴或运动限制

  • 脚本可以 响应拖拽对象的操作,以驱动用户界面或做出逻辑决策,例如根据滑动墙开关调节房间的灯光亮度。

  • 玩家可以操纵固定的部件或模型,在释放时它们会保持在你放置的位置。

  • DragDetectors 在 Studio 中有效,只要你使用 选择移动缩放旋转 工具,这样可以在编辑时更轻松地测试和调整可拖动对象。

使对象可拖动

要使任何部件或模型可拖动,只需添加 DragDetector 作为直接子项。

  1. 资源管理器 窗口中,将鼠标悬停在 PartMeshPartModel 上并单击 ⊕ 按钮。将显示上下文菜单。

  2. 从菜单中插入 DragDetector

  3. 默认情况下,对象现在将在地面平面中可拖动,但你可以自定义其 DragStyle,定义它如何 响应运动,并可选择性地应用 轴或运动限制

自定义拖拽检测器

拖拽样式

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轨迹球旋转,进一步通过 TrackballRadialPullFactorTrackballRollFactor 属性自定义。
BestForDevice鼠标和游戏手柄使用 TranslatePlaneOrLine;触摸使用 TranslatePlane;VR 使用 6DOF
Scriptable通过 SetDragStyleFunction() 提供的自定义函数计算所需的运动。

拖拽方向

默认情况下,3D 运动以及相关的 DragStyle 映射到世界空间。然而,在构建带有可调部件的 模型 时,你可能希望更改 ReferenceInstanceOrientationAxis

属性描述默认
ReferenceInstance一个实例,其 pivot 为拖拽检测器提供 参考框架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() 注册的函数决定。在此设置下,如果未能注册函数或返回无效结果,将阻止所有玩家进行拖拽。
DragDetector - 脚本拖动权限

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。你可以通过拖拽检测器实例的 KeyboardModeSwitchKeyCodeGamepadModeSwitchKeyCodeVRSwitchKeyCode 属性自定义这些修饰符。

复制

RunLocally 属性为 false(默认)时,客户端解释所有输入以生成数据并将其发送到服务器以执行拖动。在这种模式下,所有自定义事件信号和注册的函数必须位于服务器 Scripts 中。

RunLocally 属性为 true 时,无事件被复制到服务器。所有自定义事件信号和注册的函数必须位于客户端 LocalScripts 中,并且必须使用 远程事件 将必要的更改传播到服务器。

脚本对单击和拖动的响应

通过 事件信号、属性变化、Scriptable 拖动样式和自定义函数,脚本可以响应拖动对象的操作,以驱动用户界面或做出逻辑决策,例如根据滑动墙开关调节房间的灯光亮度。

事件信号

通过以下事件信号,你可以检测用户何时开始、继续和结束拖动对象。

事件描述
DragStart当用户开始拖动对象时触发。
DragContinue当用户在 DragStart 启动后继续拖动对象时触发。
DragEnd当用户停止拖动对象时触发。
DragDetector - 事件信号

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 的变化。

DragDetector - DragFrame 变化

local dragDetector = script.Parent.DragDetector
dragDetector:GetPropertyChangedSignal("DragFrame"):Connect(function()
local currentDragTranslation = dragDetector.DragFrame.Position
print(currentDragTranslation)
end)

脚本拖动样式

如果将检测器的 DragStyle 设置为 Scriptable,则可以提供自己的函数,该函数接受 Ray 并返回一个世界空间的 CFrame。检测器将移动运动,使拖动的对象去到该自定义位置/方向。

DragDetector - 脚本拖动样式

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 之前对其进行编辑。例如,您可以通过使位置四舍五入为网格增量的倍数来保持运动在网格上,或用每个棋子的合法运动规则来模拟棋盘游戏。

DragDetector - 自定义约束函数

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,其默认 DragStyleTranslatePlane,以便玩家可以将块向外拉,但不能向上或向下移动。

带有可调部件的锚定模型

你可以轻松地创建并共享主要是锚定的模型,但具有一个或多个玩家可以拖动的子部件/模型。例如,下面的桌子有两个抽屉,玩家可以打开以检查里面的东西。

拖拽检测器和约束

你可以将拖拽检测器与 Constraints 结合使用,例如一个木偶。在以下设置中,控制手柄被锚定,身体部件为未锚定,并且约束将木偶保持在一起。使用 TranslateViewPlane DragStyle 移动手柄,让木偶跳舞,同时也可以使用拖拽检测器移动各个身体部件,所有这些都能保持模型的完整性。

3D 用户界面

通过拖拽检测器,可以轻松实现 3D 用户界面,例如根据滑动开关调节 SpotLight 的亮度。你还可以单独检测 XZ 轴,以控制 3D 用户界面的两个不同方面,例如 SizeSpeedColorParticleEmitter

DragDetector - 3D 用户界面

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)
©2026 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。