前言

我以前写过类似功能的脚本,可以使一个键发挥多个键的功能,但是代码较为琐碎,拓展功能时较为麻烦,所以这次趁着有空重新整理一下,分享给有需要的朋友。

效果

相信很多朋友都玩过moba类的游戏,这类游戏中大部分都集成有快速发信号的功能,即按下一个预设键后不要松开,拖动鼠标到四个不同的方向就可以发送四种对应的信号,比如援助、坚守、危险等。这种转盘形式的信号系统简单直观,熟悉之后下意识即可完成操作,而这也正是本篇文章中脚本的灵感来源。

启用脚本后按下预设键位不要松开,此时保持不动、向上、向下、向左或向右移动鼠标后松开预设键即可触发五种不同的功能。搭配ctrlctrl+alt可以再分别拓展五种功能,总计15种不同的功能。理论上配合各种修饰键的组合可以实现更多的功能,但是我个人认为没有必要,一是记不住,二是可能产生键位冲突。

实现

编辑键位

确保自己的鼠标上有至少一个不用的按键,在对应的配置中将该键位禁用。此处也可以不禁用,但是后续在修改脚本模板时需要将鼠标原地不动对应的功能留空。

修改脚本

首先给出一个脚本模板:

-- 转盘移动方向判定的最小鼠标位移
local TURNTABLE_MIN_DISTANCE = 20
-- 修饰键状态查询时间间隔
local TURNTABLE_MODIFIER_HEARTBEAT = 10
-- 转盘两次触发最小时间间隔
local TURNTABLE_MIN_INTERVAL = 50

local turntableLastTriggerTime = GetRunningTime()

local funcs = {
    function()
        OutputLogMessage("test")
    end,
    "test",
    function()
        OutputLogMessage("test")
    end,
}
funcs[6] = "test2"
funcs[15] = "test3"

EnablePrimaryMouseButtonEvents(true)

function OnEvent(event, arg)
    if (event == "MOUSE_BUTTON_PRESSED" and arg == 7) then
        x1, y1 = GetMousePosition()
        -- OutputLogMessage("Mouse pressed at %d, %d\n", x1, y1)
    end
    if (event == "MOUSE_BUTTON_RELEASED" and arg == 7) then
        x2, y2 = GetMousePosition()
        -- OutputLogMessage("Mouse released at %d, %d\n", x2, y2)
        Turntable(x1, y1, x2, y2, funcs)
    end
end

function Turntable(x1, y1, x2, y2, funcs)
    -- 核验转盘上次触发时间
    if (GetRunningTime() - turntableLastTriggerTime < TURNTABLE_MIN_INTERVAL) then
        return
    end
    -- 定义局部变量
    local dx, dy, direction, func
    -- 计算鼠标指针位移
    dx = x2 - x1
    dy = y2 - y1
    -- 鼠标移动方向,1-5分别对应原地、上、下、左、右
    direction = 1
    if (math.abs(dx) < math.abs(dy) and dy <= -TURNTABLE_MIN_DISTANCE) then
        direction = 2
    elseif (math.abs(dx) < math.abs(dy) and dy >= TURNTABLE_MIN_DISTANCE) then
        direction = 3
    elseif (math.abs(dx) > math.abs(dy) and dx <= -TURNTABLE_MIN_DISTANCE) then
        direction = 4
    elseif (math.abs(dx) > math.abs(dy) and dx >= TURNTABLE_MIN_DISTANCE) then
        direction = 5
    end
    -- 根据按住修饰键的不同修改direction的数值,将按键功能由5拓展为15
    if IsModifierPressed("ctrl") and IsModifierPressed("alt") then
        direction = direction + 10
     elseif IsModifierPressed("ctrl") then
        direction = direction + 5
    end
    -- 轮询修饰键状态,所有修饰键松开后开始执行对应功能
    while (IsModifierPressed("ctrl") or IsModifierPressed("alt")) do
        Sleep(TURNTABLE_MODIFIER_HEARTBEAT)
    end
    -- 根据direction数值查找对应的功能并触发
    func = funcs[direction]
    -- OutputLogMessage(direction)
    if (type(func) == "function") then
        func()
    elseif (type(func) == "string") then
        PlayMacro(func)
    end
    -- 更新上次触发时间
    turntableLastTriggerTime = GetRunningTime()
end

具体需要修改的部分见下图:

代码解读

本部分内容可以跳过,只是记录一下写代码时的思路和解决方法,对于有兴趣自己编写罗技Lua脚本的朋友可以随便看看。

罗技的G HUB驱动虽然支持Lua脚本,但是这么多年过去了也没啥突破,还是一个半成品,各种问题很多。

首先要吐槽的一点就是字符串不支持中文,所有定义到变量中的字符串都会被识别为“?”,这一点可以通过sting.byte函数来验证。这就直接导致我想要将中文转为unicode编码通过WinCompose软件直接输出到文本编辑器中的计划破灭。虽然罗技驱动中通过编写宏的方式也可以直接输出中文字符,但是这就少了在代码中即时生成字符串的灵活性。而且实测该种方法输出的字符在WPS软件中会出现未知错误,输出的字符串皆为第一个字符的重复。

罗技的脚本是单线程运行的,希望各位在编写脚本时牢记心头。其实之前看到过一篇文章,该作者自己写了一个包实现了并行的效果,但我没仔细研究,一是懒,二是觉得真没必要,写个鼠标脚本不必如此深入。单线程带来的最大问题就是Sleep函数以及while循环要慎用。以我上面的代码为例,我为了防止按下修饰键对转盘功能的影响,将功能响应放在了松开所有修饰键后。这样设计主要是考虑到如下场景:将按下ctrl并按住转盘键向上滑动的功能设置为按一下S,如果没有上面的设计,就会变为ctrl+S这一保存的快捷键。这样设计的初衷是好的,但是会带来一个问题,那就是如果我在按住ctrl后多次按下转盘键,就会因为代码中轮询修饰键状态而使所有的按键事件卡在一起结算。换个说法,第一次转盘键松开后进入while循环,之后的每次按键都会等待该while循环结束后再依次执行,表现出来的效果就是只有第一次按键执行了正确的功能,后面的每一次按键功能都会变为funcs[1]。为了解决这一弊端,我不得不又引入了执行间隔,也就是转盘键在触发一次后必须等待固定时间间隔后才能再次触发。上面两个操作会带来一个弊端,就是按下修饰键后无法在不松开修饰键的情况下触发多个不同的功能,不过为了兼容性我觉得这点代价还是可以接受的。如果没有键位冲突的顾虑可以自行将模板中对应的两部分代码删除。

应用

游戏发言

仅需一个键就可以在游戏中实现快速喷人沟通的所有语句。玩外服时可以将一些类似good try、we can win、have fun的短句绑定在转盘键上,瞬间化身社交达人,带领团队走向胜利。

文档编辑

类似ctrl+Cctrl+Vctrl+Actrl+Z等常用快捷键。

后记

本篇文章并不是针对一无所知的小白编写的,所有只是简单写了下关键步骤,如果看不太懂的话可以先看看我前两篇文章,里面配图多一些,可能好懂一点。实在看不明白又有强烈使用需求的朋友可以留言或者私信我,如果你的需求很有意思或者使用场景很经典的话,我可能会以你的需求额外写一篇文章。