---
title: "Input"
description: "Action maps over keyboard and mouse, axes, and the Input facade."
canonical_url: "https://docs.ctrl-neo.dev/en/neogamelib/input"
---
# Input

NeoGameLib gives you action maps (`NeoInputMap`) over keyboard and mouse, a
one-frame-refreshed bus, and a static `Input` facade for the mouse. Raw
`Keyboard.GetState()` keeps working if you prefer it.

## How input ticks

`NeoGame.Update` runs `InputBus.Update()` **first** every frame, before UI,
worlds, collisions, and timers. Input maps therefore carry this frame's state
for everything downstream. `IsPressed`/`IsReleased` are edge-triggered: they
compare against last frame's snapshot.

## NeoInputMap

Map keys/mouse buttons to named actions; query by name.

```csharp
NeoInputMap gameplay = new();

gameplay.AddDigital("jump", Keys.Space, Keys.W);            // any bound key fires it
gameplay.AddAxis("moveX", NeoAxisRange.Full, Keys.D, Keys.A); // positive, negative
gameplay.AddMouseButton("fire", MouseButtons.Left);         // combines with keys on the same action

gameplay.Subscribe();   // registers on NeoGame.InputBus (the default bus)

// each frame, after input refresh:
gameplay.IsDown("jump");      // held
gameplay.IsPressed("jump");   // went down this frame
gameplay.IsReleased("jump");  // went up this frame
gameplay.GetAxis("moveX");    // -1..1
```

Mapping API:

<table>
<thead>
  <tr>
    <th>
      Member
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        AddDigital(string action, params Keys[] keys)
      </code>
    </td>
    
    <td>
      any bound key holds the action
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        AddMouseButton(string action, params MouseButtons[] buttons)
      </code>
    </td>
    
    <td>
      any bound button holds it; same action can have both keys and buttons
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        AddAxis(string action, NeoAxisRange range, Keys? positive = null, Keys? negative = null)
      </code>
    </td>
    
    <td>
      key pair through an axis range
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SetAction(string, params Keys[])
      </code>
    </td>
    
    <td>
      replace an existing digital action's keys; throws <code>
        KeyNotFoundException
      </code>
      
       if unknown
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SetAction(string, NeoAxisRange, Keys? positive, Keys? negative)
      </code>
    </td>
    
    <td>
      replace an axis action
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        HasAction(string)
      </code>
    </td>
    
    <td>
      exists?
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        RemoveAction(string)
      </code>
    </td>
    
    <td>
      delete entirely
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Subscribe(NeoInputBus bus = null, bool dedupe = true)
      </code>
    </td>
    
    <td>
      register for per-frame refresh (default bus = <code>
        NeoGame.Singleton.InputBus
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Unsubscribe(NeoInputBus bus = null, bool dedupe = true)
      </code>
    </td>
    
    <td>
      unregister
    </td>
  </tr>
</tbody>
</table>

<callout color="warning" icon="i-lucide-alert-triangle">

**Querying an action that was never added throws** (`KeyNotFoundException`) —
a typo is a bug, not a silent zero. Querying a *mapped* action before the
first refresh returns seeded defaults (false / 0).

</callout>

### Axis ranges

`NeoAxisRange` maps the key pair to a float:

<table>
<thead>
  <tr>
    <th>
      Range
    </th>
    
    <th>
      positive key
    </th>
    
    <th>
      negative key
    </th>
    
    <th>
      neither
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        Positive
      </code>
    </td>
    
    <td>
      1
    </td>
    
    <td>
      0
    </td>
    
    <td>
      0
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Negative
      </code>
    </td>
    
    <td>
      0
    </td>
    
    <td>
      -1
    </td>
    
    <td>
      0
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Full
      </code>
    </td>
    
    <td>
      1
    </td>
    
    <td>
      -1
    </td>
    
    <td>
      0
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PositiveInverted
      </code>
    </td>
    
    <td>
      0
    </td>
    
    <td>
      1
    </td>
    
    <td>
      1
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        NegativeInverted
      </code>
    </td>
    
    <td>
      -1
    </td>
    
    <td>
      0
    </td>
    
    <td>
      -1
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        FullInverted
      </code>
    </td>
    
    <td>
      -1
    </td>
    
    <td>
      1
    </td>
    
    <td>
      0
    </td>
  </tr>
</tbody>
</table>

## The Input facade

`NeoGameLib.NeoInput.Input` is a static shorthand over a hidden map with the
five mouse buttons bound as actions:

```csharp
Input.LeftMouseButton.IsDown();
Input.LeftMouseButton.IsPressed();
Input.LeftMouseButton.IsReleased();
Input.RightMouseButton.IsDown();
Input.MiddleMouseButton.IsDown();
Input.XButton1.IsDown();
Input.XButton2.IsDown();

Input.MousePosition;   // Vector2, window pixels (NOT world or UI units)
```

- `MousePosition` is in **window pixels**; convert with
`RenderBus.ScreenToWorld` for world space or `NeoCanvas.ScreenToCanvas` for
UI space.
- The facade map builds and registers itself on first use; `NeoGame` makes
sure it exists before the input refresh each frame. Querying before the
game initializes its bus throws.

## The bus itself

`NeoInputBus` stores the registered maps and refreshes them from a single
keyboard/mouse snapshot each frame.

- `Update()` — refresh from live `Keyboard.GetState()`/`Mouse.GetState()`
(NeoGame calls this).
- `Update(KeyboardState current)` / `Update(KeyboardState, MouseState)` —
refresh from given snapshots (useful for simulated input).
- `ListenInput()` — the keys held this frame.

You rarely touch the bus directly; `Subscribe()` is enough. Unsubscribing a
map you no longer need keeps per-frame work down.

## Raw access

For throwaway reads, plain MonoGame works fine and needs no registration:

```csharp
KeyboardState kb = Keyboard.GetState();
if (kb.IsKeyDown(Keys.Escape)) Exit();
```

Prefer `NeoInputMap` when you want rebindable, named, edge-triggered actions.


## Sitemap

See the full [sitemap](https://docs.ctrl-neo.dev/sitemap.md) for all pages.
