Log inGet started

inputMap assetType

Updated 22 August 2026
racer.inputMap/
  init.luau                    -- what the map is, and how it composes
  boost.inputBinding/
    init.luau
  steer.inputBinding/
    init.luau
-- racer.inputMap/init.luau
return {
    name = "racer",
    description = "Throttle, steering and boost.",

    -- The group these controls belong to, and the groups this map stands
    -- down while it is live. Declaring neither composes with everything.
    group = "vehicle",
    suppresses = { "player" },
}

Each control carries kbm, gamepad and touch; the inputBinding assetType covers the record shape.

Reading it

A controller activates the map it needs and subscribes to the controls it drives. Nothing is live until something asks for it.

function awake()
    local map = self.inputMap:activate()
    map.boost:onPressed(function() self:boost() end)
    map.steer:onInput(function(v, dt, active)
        if not active then return end
        self:steer(v, dt)
    end)
end

function onDestroy()
    self.inputMap:deactivate()
end

A control the map does not declare reads as nil, so a typo raises at the subscribe call. A controller written against more than one map tests for an optional control first — if map.crouch then map.crouch:onInput(...) end.

Several maps can be live at once, and the player's surface is the union of them. group / suppresses are how one map stands another down — a car takes the player over by naming the player group, and walking resumes the moment the car releases. Zin.scheme.live() reports each live map's group, suppresses and suppressedBy.

Ref methods

  • :activate() — load every <name>.inputBinding/ child, validate them, add them to the live binding set, and return the handles keyed by control name. Every fault across every child is reported in one error. Activating a map already live returns its existing handles, so two components sharing one map get one set of controls.
  • :deactivate() — release this holder's claim. The last holder to release takes the controls out of the live set and disconnects everything subscribed through the map.
  • :controls(){ name, label, kind, classes, problems } per child, read without activating anything, so an authoring tool can show a map's whole surface and everything wrong with it before it goes live.
  • :record() — the raw record its init.luau returns.
  • :effective() — the materialized effective map, without activating it.
  • :bindingsFor(name, class) — the bindings a named entry has for one device class in this map's effective form.

Writing one

The inputAuthor toolbox writes a map and its controls together, filling in canonical defaults for a control whose name it recognises:

tools.use("inputAuthor", "map", "racer", { "throttle", "steer", "boost" },
    { group = "vehicle", suppresses = { "player" } })
tools.use("inputAuthor", "check", "/zero/source/inputMaps/racer.inputMap")

asset.create("inputMap", name, { record = ... }) bakes a record into both shapes a map is read in — init.luau beside one <name>.inputBinding/ child per entry, each carrying all three device classes, a class the record had nothing for written as false with the reason beside it. That is the hook Zin.map.bake(name) drives to turn the active effective map into an editable asset.

The engine's built-in schemes live at @builtin::inputMaps.default, .editor, .editorMeta, .vehicle and .flight.

  • asset-type
  • reference