3D拖动探测器

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

实例可以促进和鼓励体验中与 3D 对象的互动,例如打开门和抽屉、滑动部件、抓取并投掷保龄球、拉回并发射弹弓、以及更多。关键功能包括:

  • 在任何 DragDetectorBasePartModel 下放置一个 使其可拖动 通过所有输入(鼠标、触摸、游戏手柄和 VR),而且没有单行代验证码。

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

  • 脚本可以回应拖动对象的操作来驱动用户界面或做出逻辑决定,例如根据滑动墙开关调整房间的灯光等级。

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

  • 在工作室工作,只要你不使用选择、移动、缩放或旋转工具,就能更容易在编辑时测试和调整可拖动的对象。

使对象可拖动

要使任何部分或模型可拖动,只需添加一个 DragDetector 作为直接后代。

  1. 浏览器 窗口中,将鼠标悬停在 Part , MeshPartModel 上,然后单击 ⊕ 按钮。将显示上下文菜单。

  2. 从菜单中插入一个 拖动探测器

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

自定义拖动探测器

拖动风格

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跟踪球旋转,进一步通过 TrackballRadialPullFactorTrackballRollFactor 属性进行自定义。
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 , GamepadModeSwitchKeyCodeVRSwitchKeyCode 拖动探测器实例的属性来自定义这些修改器。

复制

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 基于的亮度。您还可以单独检测 XZ 轴来控制 3D 用户界面的两个不同方面,例如 SizeSpeedColorParticleEmitter

拖动探测器 - 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)