Log in to join the discussion
Back to list

在 Lua Mod 中处理键盘输入

General
OPApr 25, 05:20 AM326

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"
F1 … F12 "f1" … "f12"
LeftShift "leftShift"
LeftCtrl "leftCtrl"
LeftAlt "leftAlt"
Return / Enter "enter"
Escape "escape"
Tab "tab"
1 … 9、0 "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

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

2. mod 之间的 action 名冲突

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

self.actionName = "AwesomeTankMod_Fire"

3. 正确捕获 self

下面这种写法是错的 —— 回调里的 self 是 nil:

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