实例可以促进和鼓励体验中与 3D 对象的互动,例如打开门和抽屉、滑动部件、抓取并投掷保龄球、拉回并发射弹弓、以及更多。关键功能包括:
在任何 DragDetector 或 BasePart 或 Model 下放置一个 使其可拖动 通过所有输入(鼠标、触摸、游戏手柄和 VR),而且没有单行代验证码。
从多个拖动样式中选择,定义对象如何响应运动,并可选地应用轴或移动限制。
脚本可以回应拖动对象的操作来驱动用户界面或做出逻辑决定,例如根据滑动墙开关调整房间的灯光等级。
玩家可以操纵锚定的零件或模型,它们将保持在你释放时放置的位置。
在工作室工作,只要你不使用选择、移动、缩放或旋转工具,就能更容易在编辑时测试和调整可拖动的对象。
使对象可拖动
要使任何部分或模型可拖动,只需添加一个 DragDetector 作为直接后代。
从菜单中插入一个 拖动探测器 。
自定义拖动探测器
拖动风格
DragDetectors 将鼠标曲标移动到虚拟线和平面,计算提出的 3D 运动。通过 DragStyle 属性,您可以从不同的映射中选择满足需求。例如, 翻译飞机 在虚拟飞机中产生翻译,而 旋转轴 在虚拟轴上产生旋转。
设置 | 描述 |
---|---|
TranslateLine | 沿着探测器的Axis摆动1D运动,默认世界 Y 轴。 |
TranslatePlane | 垂直于探测器的 Axis 平面的 2D 运动,默认世界 XZ 平面。 |
TranslatePlaneOrLine | 在检测器的 Axis 平面垂直于检测器的 2D 运动,当 修改器 激活时,沿检测器的 Axis 1D 运动。 |
TranslateLineOrPlane | 沿着探测器的Axis和当修改器激活时,在平面上的2D运动与探测器的Axis垂直。 |
TranslateViewPlane | 与镜头机查看图平行的飞机上的 2D 运动。在这种模式下,飞机不断更新,即使在拖动时,也总是面向相镜头的当前视查看。 |
RotateAxis | 旋转关于探测器的 Axis ,默认世界 Y 轴。 |
RotateTrackball | 跟踪球旋转,进一步通过 TrackballRadialPullFactor 和 TrackballRollFactor 属性进行自定义。 |
BestForDevice | 翻译飞机或线 对于鼠标和游戏手柄; 翻译飞机 对于触摸; 6DOF 对于 VR。 |
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 | 在运行体验和工作室编辑模式中,都靠近锚定对象的位置/方向将被更新,以准确反映提出的运动。 | 对于未锚定的对象,行为与锚定对象相同。然而,在运行体验中,对象将在拖动开始时被锚定,在拖动释放时恢复为未锚定状态。 |
Physical | 一个被锚定的对象将默认为 几何 行为,因为它不受力的影响。 | 未锚定的对象将被约束力移动,该力试图将其移至建议的运动所给出的位置和/或方向。 |
Custom | 对象不会移全部一毫米,但 仍将更新,你可以根据自己的需要回应拖动操作。 | (与锚定相同) |
轴和运动限制
默认情况下,没有 3D 运动超出 DragStyle 内在限制的限制。如果需要,您可以对翻译和旋转应用最小和最大限制。请注意,这些不是 限制 ;它们只是阻碍拖动探测器尝试产生运动,以便保持在限制内。
属性 | 描述 | 默认 |
---|---|---|
MinDragTranslation MaxDragTranslation | 在每个维度拖动翻译的限制。如果 MaxDragTranslation 大于 MinDragTranslation,翻译将被卡在该范围内。 | (0, 0, 0) |
MinDragAngle MaxDragAngle | 仅在 DragStyle 设置为 旋转轴 时才相关。如果 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)
物理响应
假设拖动器的回应风格已设置为 物理 ,并且应用于未锚定的对象,该对象将被限制力移动到由提出的运动给出的位置/方向。您可以通过以下属性进一步定制物理响应:
属性 | 描述 | 默认 |
---|---|---|
ApplyAtCenterOfMass | 当为 false 时,拖动力被应用在用户单击的点。当为 true 时,力被应用在物对象的质量中心。 | 错误 |
MaxForce | 对象达到目标所需的最大力。 | 10000000 |
MaxTorque | 最大扭矩应用于对象以达到其目标。 | 10000 |
Responsiveness | 更高的值使对象更快地达到目标。 | 10 |
修改器输入
一些 DragStyle 模式允许用户按住 调整器 键/按钮来以不同方式操纵拖动的对象。默认情况下,调整因素在 PC 上是 LeftControl,在游戏手柄上是 ButtonR1,在 VR 上是 ButtonL2。您可以通过 KeyboardModeSwitchKeyCode , GamepadModeSwitchKeyCode 或 VRSwitchKeyCode 拖动探测器实例的属性来自定义这些修改器。
复制
当 RunLocally 属性为 false (默认) 时,客户端解释所有输入产生数据,该数据将发送到服务器进行拖动在这种模式下,所有自定义事件信号和注册函数必须在服务器 Scripts 中。
当 RunLocally 属性为真时,服务器不会复制任何事件。所有自定义事件信号和注册函数必须在客户端 LocalScripts 中,您必须使用 远程事件 将必要的更改传递到服务器。
脚本对单击和拖动的响应
通过事件信号、属性更改、Scriptable和自定义函数,脚本可以回应拖动对象的操作来驱动用户界面或做出逻辑决定,例如根据滑动墙开关调节房间的亮度等级。
事件信号
通过以下事件信号,你可以检测用户是否开始、继续和结束拖动对象。
事件 | 描述 |
---|---|
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 设置为 可脚本化 ,你可以提供自己的函数, которая接受一个 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)
-- When applicable, remove the constraint function by invoking connection:Disconnect()
示例使用
未锚定的物理对象
基本实现拖动探测器的游戏是塔平衡游戏,玩家必须仔细移除部件并尝试保持塔立起。在以下塔结构中,每个部件都有一个子 DragDetector 与默认 DragStyle 的 翻译飞机 以便玩家可以将部件向外拉出但不向上或向下拉出。
带可调整部件的锚定模型
您可以轻松创建和共享主要固定的模型,但模型上有一个或多个可拖动的子模块/模型。例如,以下桌子有两个抽屉,玩家可以打开以检查里面的内容。
拖动检测器和约束
您可以将拖动探测器与 Constraints 结合,例如木偶 puppet。在以下配置中,控制手柄被锚定,身体部件未锚定,约束将木偶合在一起。使用 TranslateViewPlane 移动手柄,让木偶跳舞,个人身体部位也可以用拖动探测器移动,整个模型仍保持完整性。
3D 用户界面
3D用户界面可以通过拖动探测器轻松实现,例如通过调整滑动开关调光器的亮度来调整 SpotLight 基于的亮度。您还可以单独检测 X 和 Z 轴来控制 3D 用户界面的两个不同方面,例如 Size 、 Speed 和 Color 的 ParticleEmitter 。
拖动探测器 - 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)