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
要点:
- 把
keycode定义为 Property,这样用户可以从 mod inspector 重新绑定,而不用改 Lua。 - 先把回调存到
self上再注册。这样反注册时引用是稳定的。 - 务必在
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。