TL;DR —
UnityEngine.Input.GetKey / GetMouseButtonno longer works. UseInputAPI.RegisterKeyInputfor keyboard hotkeys instead. It's event-driven, cross-platform, and integrates with the in-game key-binding UI.
Why the change?
Panzer War has migrated to Unity's new Input System package. Any Lua script that calls the legacy UnityEngine.Input API will throw:
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.
Mods that still use Input.GetKey(...), Input.GetMouseButton(...), Input.GetAxis(...) etc. need to be updated.
The recommended approach: InputAPI.RegisterKeyInput
InputAPI is the official Lua-side input wrapper. It registers a callback pair (pressed / released) for a named action and a key code.
Signature
InputAPI.RegisterKeyInput(actionName, keyCode, onPressed, onReleased)
InputAPI.UnregisterKeyInput(actionName)
| Parameter | Type | Description |
|---|---|---|
actionName |
string |
Globally unique ID for this binding. Use a mod-specific prefix to avoid collisions, e.g. "MyMod_Reload". |
keyCode |
string |
Input System keyboard name (lowercase). See the table below. |
onPressed |
function(ctx) |
Called once when the key is pressed down. |
onReleased |
function(ctx) |
Called once when the key is released. |
Important:
actionNamemust be unique across all active mods. Registering the same name twice prints an error and silently fails. Always prefix with your mod name.
Quick example — simplest form
-- Press F to print "fire", release F to print "stop"
InputAPI.RegisterKeyInput("MyMod_Fire", "f",
function(ctx) print("fire") end,
function(ctx) print("stop") end)
That's it. No Update() polling, no per-frame checks.
Recommended pattern — Behavior class with cleanup
For anything beyond a one-liner, wrap your binding in a Behavior class so it gets cleaned up when the mod is unloaded. Forgetting to unregister leaks the binding — it stays active until the game restarts.
Behavior()
Property = {
keycode = {
type = "string",
value = "space" -- editable from the mod inspector
}
}
local FireOnSpace = class("FireOnSpace")
function FireOnSpace:ctor()
-- Prefix with mod name to avoid collisions with other mods
self.actionName = "FireOnSpace_Trigger"
end
function FireOnSpace:OnStarted()
-- Bind methods to self so we can unregister the exact same reference later
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()
-- Always unregister! This is the #1 source of "ghost binding" bugs.
InputAPI.UnregisterKeyInput(self.actionName)
end
function FireOnSpace:OnPressed(ctx)
print("Space pressed")
-- your fire logic here
end
function FireOnSpace:OnReleased(ctx)
print("Space released")
end
return FireOnSpace
Key points:
- Define
keycodeas a Property so the user can rebind it from the mod inspector without editing Lua. - Store callbacks on
selfbefore registering. This keeps a stable reference for unregistering. - Always unregister in
OnDestroyed. The framework does not clean up bindings automatically.
Key code reference
keyCode is a string that maps to a Unity Input System keyboard binding. The API internally prefixes it with keyboard/, so you only pass the key name.
Legacy KeyCode |
New keyCode string |
|---|---|
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" |
| Numpad 0–9 | "numpad0" … "numpad9" |
| Arrows | "upArrow" / "downArrow" / "leftArrow" / "rightArrow" |
| Backspace / Delete | "backspace" / "delete" |
For the full list see Unity's Input System keyboard documentation — any value from the Key enum works in lowercase first-letter form.
What InputAPI.RegisterKeyInput does not do
| Need | Use instead |
|---|---|
| Mouse buttons (left/right click) | CompatibleInputManager.Instance.isWeaponFire / .isWeaponAim |
| Mouse delta / axis | CompatibleInputManager.Instance.groundMouseDelta / .flightMouseDelta |
| Gamepad buttons / sticks | CS.UnityEngine.InputSystem.Gamepad.current.<button>.isPressed |
Polling raw Keyboard.current state |
CS.UnityEngine.InputSystem.Keyboard.current.<key>.isPressed |
InputAPI.RegisterKeyInput is keyboard-only, by design — for game-action mouse input (fire, aim) you should use CompatibleInputManager so your mod stays compatible with mobile, controller, and Android mouse capture.
Common gotchas
1. Forgetting to unregister
The binding survives a mod reload. After enough hot-reloads you'll see:
Same action name MyMod_Fire has been registered
Always pair RegisterKeyInput with UnregisterKeyInput in your destruction lifecycle.
2. Action name collisions across mods
If two mods both register "Fire", the second one silently fails. Prefix every action name with your mod ID:
self.actionName = "AwesomeTankMod_Fire"
3. Capturing self correctly
This is wrong — self is nil inside the callback:
InputAPI.RegisterKeyInput("X", "f",
function(ctx) self:OnPressed(ctx) end, ...) -- self === nil here
Always store the callback on self first (see the class example above), or use an explicit closure:
local me = self
InputAPI.RegisterKeyInput("X", "f",
function(ctx) me:OnPressed(ctx) end, ...)
4. The callback receives an InputAction.CallbackContext
You usually don't need it, but it carries timing and value info if you do:
function self:OnPressed(ctx)
-- ctx.time, ctx.duration, ctx:ReadValue(...) etc.
end
Migration cheat sheet
| Old code | New code |
|---|---|
if Input.GetKeyDown(KeyCode.F) then ... end |
Register onPressed callback |
if Input.GetKeyUp(KeyCode.F) then ... end |
Register onReleased callback |
if Input.GetKey(KeyCode.F) then ... end |
Set a flag in onPressed, clear it in onReleased, check the flag in your tick |
if Input.GetMouseButton(0) then ... end |
CompatibleInputManager.Instance.isWeaponFire:Get() |
if Input.GetMouseButton(1) then ... end |
CompatibleInputManager.Instance.isWeaponAim:Get() |
Example: emulating GetKey (held-down state)
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
-- runs every frame while F is down
end
end
See also
frame/define/api.lua— Lua binding forInputAPIframe/define/csharp.lua(lines 1514–1575) — full method signatures
If you have questions or run into issues, post in the modding community channel — please include your full Lua snippet and the actionName you're using.