Log in to join the discussion
Back to list

在 Lua Mod 中处理键盘输入

General
OPApr 25, 05:20 AM172

TL;DR —— UnityEngine.Input.GetKey / GetMouseButton 已不再可用。请改用 InputAPI.RegisterKeyInput 来注册键盘热键。它基于事件驱动、跨平台,并能与游戏内的按键绑定 UI 集成。


为什么要改?

Panzer War 已经迁移到 Unity 的新 Input System 包。任何调用旧版 UnityEngine.Input API 的 Lua 脚本都会抛出:

InvalidOperationException: You are trying to read Input using the UnityEngine.Input class,
but you have switched active Input handling to Input System package in Player Settings.

仍在使用 Input.GetKey(...)Input.GetMouseButton(...)Input.GetAxis(...) 等接口的 mod 都需要更新。


推荐方案:InputAPI.RegisterKeyInput

InputAPI 是官方提供的 Lua 端输入封装。它为一个命名 action 和键码注册一对回调(按下 / 抬起)。

函数签名

InputAPI.RegisterKeyInput(actionName, keyCode, onPressed, onReleased)
InputAPI.UnregisterKeyInput(actionName)
参数 类型 说明
actionName string 该绑定的全局唯一 ID。请使用 mod 专属前缀避免冲突,例如 "MyMod_Reload"
keyCode string Input System 的键盘键名(小写)。见下方对照表。
onPressed function(ctx) 按键按下时调用一次。
onReleased function(ctx) 按键抬起时调用一次。

重要: actionName 必须在所有已加载的 mod 中保持唯一。重复注册同名 action 会打印错误并静默失败。请始终用你的 mod 名做前缀。


快速示例 —— 最简形式

-- 按下 F 打印 "fire",抬起 F 打印 "stop"
InputAPI.RegisterKeyInput("MyMod_Fire", "f",
    function(ctx) print("fire")  end,
    function(ctx) print("stop")  end)

就这么简单。不需要 Update() 轮询,也不需要逐帧检查。


推荐写法 —— 带清理逻辑的 Behavior 类

只要不是一行代码能搞定的事,都建议把绑定包在 Behavior 类里,这样在 mod 卸载时能自动清理。忘记反注册会导致绑定泄漏 —— 它会一直存活到游戏重启。

Behavior()

Property = {
    keycode = {
        type  = "string",
        value = "space"   -- 可在 mod inspector 中编辑
    }
}

local FireOnSpace = class("FireOnSpace")

function FireOnSpace:ctor()
    -- 用 mod 名做前缀,避免与其他 mod 冲突
    self.actionName = "FireOnSpace_Trigger"
end

function FireOnSpace:OnStarted()
    -- 把方法绑到 self 上,这样后面才能用同一个引用反注册
    self.onPressedCb  = function(ctx) self:OnPressed(ctx)  end
    self.onReleasedCb = function(ctx) self:OnReleased(ctx) end

    InputAPI.RegisterKeyInput(
        self.actionName,
        self.keycode,
        self.onPressedCb,
        self.onReleasedCb)
end

function FireOnSpace:OnDestroyed()
    -- 务必反注册!这是"幽灵绑定"bug 的头号来源。
    InputAPI.UnregisterKeyInput(self.actionName)
end

function FireOnSpace:OnPressed(ctx)
    print("Space pressed")
    -- 在这里写你的开火逻辑
end

function FireOnSpace:OnReleased(ctx)
    print("Space released")
end

return FireOnSpace

要点:

  1. keycode 定义为 Property,这样用户可以从 mod inspector 重新绑定,而不用改 Lua。
  2. 先把回调存到 self再注册。这样反注册时引用是稳定的。
  3. 务必在 OnDestroyed 中反注册。框架不会自动清理绑定。

键码对照表

keyCode 是一个字符串,对应 Unity Input System 的键盘绑定。API 内部会自动加上 keyboard/ 前缀,所以你只需要传键名。

旧版 KeyCode 新版 keyCode 字符串
Space "space"
W / A / S / D "w" / "a" / "s" / "d"
F1F12 "f1""f12"
LeftShift "leftShift"
LeftCtrl "leftCtrl"
LeftAlt "leftAlt"
Return / Enter "enter"
Escape "escape"
Tab "tab"
190 "1""9""0"
小键盘 0–9 "numpad0""numpad9"
方向键 "upArrow" / "downArrow" / "leftArrow" / "rightArrow"
Backspace / Delete "backspace" / "delete"

完整列表请参考 Unity 的 Input System 键盘文档 —— Key 枚举里的任何值都可以用,转成首字母小写形式即可。


InputAPI.RegisterKeyInput 支持什么

需求 替代方案
鼠标按键(左键 / 右键) CompatibleInputManager.Instance.isWeaponFire / .isWeaponAim
鼠标增量 / 轴 CompatibleInputManager.Instance.groundMouseDelta / .flightMouseDelta
手柄按键 / 摇杆 CS.UnityEngine.InputSystem.Gamepad.current.<button>.isPressed
直接轮询 Keyboard.current 状态 CS.UnityEngine.InputSystem.Keyboard.current.<key>.isPressed

InputAPI.RegisterKeyInput 设计上只处理键盘——对于游戏动作类的鼠标输入(开火、瞄准),应该用 CompatibleInputManager,这样你的 mod 才能在移动端、手柄、Android 鼠标捕获等环境下保持兼容。


常见坑

1. 忘记反注册

mod 重载时绑定不会自动消失。热重载几次之后你会看到:

Same action name MyMod_Fire has been registered

请始终把 RegisterKeyInputUnregisterKeyInput 配对,写在销毁生命周期里。

2. mod 之间的 action 名冲突

如果两个 mod 都注册 "Fire",第二个会静默失败。给每个 action 名加上你的 mod ID 前缀

self.actionName = "AwesomeTankMod_Fire"

3. 正确捕获 self

下面这种写法是错的 —— 回调里的 selfnil

InputAPI.RegisterKeyInput("X", "f",
    function(ctx) self:OnPressed(ctx) end, ...)  -- 这里 self === nil

正确做法是先把回调存到 self 上(参见上面的类示例),或者用显式闭包:

local me = self
InputAPI.RegisterKeyInput("X", "f",
    function(ctx) me:OnPressed(ctx) end, ...)

4. 回调收到的是 InputAction.CallbackContext

通常用不到它,但需要时它能提供时间和值信息:

function self:OnPressed(ctx)
    -- ctx.time、ctx.duration、ctx:ReadValue(...) 等
end

迁移速查表

旧代码 新代码
if Input.GetKeyDown(KeyCode.F) then ... end 注册 onPressed 回调
if Input.GetKeyUp(KeyCode.F) then ... end 注册 onReleased 回调
if Input.GetKey(KeyCode.F) then ... end onPressed 里置一个 flag,在 onReleased 里清掉,在 tick 里检查 flag
if Input.GetMouseButton(0) then ... end CompatibleInputManager.Instance.isWeaponFire:Get()
if Input.GetMouseButton(1) then ... end CompatibleInputManager.Instance.isWeaponAim:Get()

示例:模拟 GetKey(按住状态)

function MyMod:OnStarted()
    self.isHeld = false

    self.onPressedCb  = function() self.isHeld = true  end
    self.onReleasedCb = function() self.isHeld = false end

    InputAPI.RegisterKeyInput("MyMod_Hold", "f",
        self.onPressedCb, self.onReleasedCb)
end

function MyMod:Tick()
    if self.isHeld then
        -- 只要 F 键按着,这里就每帧执行
    end
end

参考

  • frame/define/api.lua —— InputAPI 的 Lua 绑定
  • frame/define/csharp.lua(第 1514–1575 行)—— 完整的方法签名

如有疑问或遇到问题,欢迎在 Mod 社区频道发帖 —— 请附上完整的 Lua 代码片段和你使用的 actionName。

0 replies
1 / 1
1/200/2000
Login To Reply