3D 拖动检测器

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

DragDetector 实例促进并鼓励与游戏中的 3D 对象进行交互,例如打开门和抽屉、移动部件、抓取和投掷保龄球、拉回并发射弹弓等。主要功能包括:

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

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

  • 脚本可以 响应拖动对象的操作,以驱动 UI 或做出逻辑决策,例如根据滑动墙开关调光器调整房间的光线级别。

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

  • 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一个实例,其枢轴提供拖动检测器的 参考框架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 拖动样式和自定义函数,脚本可以响应拖动对象的操作,以驱动 UI 或做出逻辑决策,例如根据滑动墙开关调光器调整房间的光线级别。

事件信号

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

事件描述
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 - 脚本化 DragStyle
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 是我们在美国及其他国家或地区的注册与未注册商标。