防抖动:使用时机及原因说明

Time:5 分钟

简而言之,**Debounce(防抖动)**系统就是一个用来防止函数运行次数过多的代码集合。这个概念来自于电子元件开关的接点弹跳现象,也就是开关开启时接点在多次弹跳后才能稳定下来,导致多个信号产生。该问题在 Roblox 中主要体现于 BasePart/Touched 事件:在两个部件短时间内多次触碰时会产生类似问题。不过,防抖动系统在其他情况下也能够派上用场。

理论应用

假设地板上有一个按钮。每当玩家角色跳到按钮上时,按钮就会输出一条讯息。为此情况编写的代码可能会是这样的:

game.Workspace.Button.Touched:Connect(function(hit)
    print("按钮被按下") --打印讯息
    wait(1)                 --等待 1 
    print("Hi :D")          --打印另一条讯息
end)

上述代码将会在输出窗口中产生下列讯息:

按钮被按下
Hi :D

但物理引擎处理碰撞的方式使其不会只记录一次碰撞,而是可能触发多次 Touched 事件。因此,刚才代码的实际输出效果可能更接近下面所示:

按钮被按下
按钮被按下
按钮被按下
按钮被按下
按钮被按下
Hi :D
Hi :D
Hi :D
Hi :D
Hi :D

所有的事件处理函数会同时执行,而非按顺序单个执行。

下面是开发者可能会遇到的情况:

当希望使用按钮进行 Articles/How to Make a Model Regenerate(模型再次生成)时,很可能会导致每按一次按钮都会生成 5 个模型。由于这 5 个模型都会处于_相同位置_,可能会导致多种问题发生。在代码中加入简单的防抖动系统后,就可以轻易避免这些问题。当然,在上述情况中也可以为按钮添加 ClickDetector 来解决重复问题。但在许多无法使用 ClickDetector 的情况下,防抖动系统将会十分有用。

基本的防抖动系统工作原理如下:

当特定动作发生时(例如当玩家跳上之前所提到的地板按钮上时),脚本会封锁所有对函数的再次调用,直到指定时长后或者动作完成后为止。

使用示例

为已有脚本添加防抖动功能其实并不困难。下面就让我们以之前的脚本为例:只需添加几行代码,就能够为这个脚本添加函数能够再次运行前需要等待的时长限制。

local buttonPressed = false
--将按钮是否被按下的状态储存在局部变量中

Workspace.Button.Touched:Connect(function(hit)
    if not buttonPressed then
    -- 状态是否为“未按下”?

        buttonPressed = true
        -- 若是,则将其标记为“已按下”,防止函数多次运行

        print("按钮被按下")
        wait(1)
        print("Hi :D")
        -- 执行操作

        buttonPressed = false
        -- 执行操作后将其标记为“未按下”,以便再次执行函数
    end
end)

进行调整后,输出应该是这样的:

按钮被按下
Hi :D

这样就达到我们最开始的目的了!这四行代码可以被添加至大多数带有函数的脚本中,以达成同样的效果。其也适用于非互相触碰对象的使用情况,如:用来阻止玩家过于频繁地按下按钮或发射武器,或者用来阻止新事件在当前事件完成前触发等。请参考下列示例:

实际应用

下列代码为火箭发射器工具的本地 GUI 脚本:

enabled = true
mouse.Button1Down:Connect(function()
    if not enabled then
        return
    end

    enabled = false
    mouse.Icon = "rbxasset://textures\\GunWaitCursor.png"

    wait(12)
    mouse.Icon = "rbxasset://textures\\GunCursor.png"
    enabled = true

end)

发射火箭发射器后,脚本会显示装填图标,并让函数等待 12 秒。在此期间 enabled 值将为 false,因此当玩家尝试再次使用发射器时,函数会立即返回,导致脚本不会运行。装填图标将会在 12 秒后消失,且 enabled 值将重新设为 true,以便玩家再次使用火箭发射器。

进阶符号用法

在熟悉防抖动的使用方法后,开发者不需要为每个事件处理程序定义不同的防抖动变量,可转为编写防抖动函数,用来返回事件处理函数的防抖动拷贝。该函数将会使用 ... 符号来传递参数

function debounce(func)
    local isRunning = false    -- 创建局部防抖动变量
    return function(...)       -- 返回新函数
        if not isRunning then
            isRunning = true

            func(...)          -- 用原始参数进行调用

            isRunning = false
        end
    end
end

将其应用至原始代码:

Workspace.Button.Touched:Connect(debounce(function(hit)
    print("按钮被按下") --打印讯息
    wait(1)                 --等候 1 
    print("Hi :D")          --打印另一条讯息
end))

练习实践

制作一个被玩家触碰时使玩家生命值减少 5 的部件。通过防抖动功能确保此效果每 3 秒只能触发一次。

显示/隐藏

--将按钮是否被按下的状态储存在局部变量中
local buttonPressed = false
script.Parent.Touched:connect(function(hit)
    -- 状态是否为“未按下”?
    if not buttonPressed then
        -- 若是,则将其标记为“已按下”,防止函数多次运行
        buttonPressed = true
        if hit.Parent then
            hum = hit.Parent:FindFirstChild("Humanoid")
            hum.Health = hum.Health - 5
        end
        wait(3)
        -- 执行操作后将其标记为“未按下”,以便再次执行函数
        buttonPressed = false
    end
end)

***Roblox官方链接:防抖动:使用时机及原因说明