Beryllium.InputBinder 2.0.0

Prefix Reserved
dotnet add package Beryllium.InputBinder --version 2.0.0
                    
NuGet\Install-Package Beryllium.InputBinder -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Beryllium.InputBinder" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Beryllium.InputBinder" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Beryllium.InputBinder" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Beryllium.InputBinder --version 2.0.0
                    
#r "nuget: Beryllium.InputBinder, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Beryllium.InputBinder@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Beryllium.InputBinder&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Beryllium.InputBinder&version=2.0.0
                    
Install as a Cake Tool

Beryllium Input Binder

Class library for managing various types of input binding to game actions.

Actions come in three shapes — button, one-axis and two-axes — and each action can hold several input bindings (2 by default). Registration and binding are separate on purpose: a game first registers its actions (by name/description), then the player binds inputs to them later in a settings menu.

See ARCHITECTURE.md for a high-level tour of the classes and how they fit together.

Installation

dotnet add package Beryllium.InputBinder

Targets .NET 8 and .NET 10. The public types are spread over a few namespaces:

using BerylliumInputBinder;               // InputBinder
using BerylliumInputBinder.Factories;     // InputFactory
using BerylliumInputBinder.Types;         // ButtonState, ButtonModifier, InputSource, InputBinderResult
using BerylliumInputBinder.Types.Actions; // ButtonAction, OneAxisAction, TwoAxesAction
using BerylliumInputBinder.Types.Inputs;  // ButtonInput, OneAxisInput, TwoAxesInput
using BerylliumInputBinder.Types.Handles; // RegisteredActionHandle
using BerylliumInputBinder.Types.Results; // BindResult

Quick start

var binder = new InputBinder(inputBindingsPerAction: 2);

// 1. Register actions once at startup. Each call returns a handle you can keep or look up later.
//    A button callback runs with whatever state was polled (Pressed, Down, Up or None), so filter on it.
var jump = binder.RegisterButtonAction("Jump",
                                       (state, player) => { if (state == ButtonState.Pressed) Player.Jump(); },
                                       description: "Jump over obstacles")
                 .WithDefaultBinding(0, InputFactory.Key(32))           // keyboard Space
                 .WithDefaultBinding(1, InputFactory.GamePadButton(0)); // gamepad A

var look = binder.RegisterTwoAxesAction("Look", (delta, player) => Camera.Rotate(delta))
                 .WithDefaultBinding(0, InputFactory.MouseAxes(0));

// Batch-register pre-built actions if you prefer defining them elsewhere:
binder.RegisterActionRange(
    new OneAxisAction("Throttle", (v, p) => Ship.SetThrottle(v)),
    new ButtonAction("Fire", (s, p) => { if (s == ButtonState.Down) Ship.Fire(); }));

// 2. Drive the binder each frame with the inputs you polled.
//    Update takes IReadOnlyList (arrays or List<T>) and is O(1) per input with zero allocations,
//    so it is safe to call every frame. Any list may be null.
binder.Update(buttonInputs, oneAxisInputs, twoAxesInputs,
              withShift: shiftHeld, withCtrl: ctrlHeld, withAlt: altHeld);

Update invokes the bound action for every input you pass in, whatever its state or value, so pass only the inputs you polled this frame, or let the callbacks ignore idle ones.

Rebinding menu

Enumerate binder.Actions (in registration order) or call binder.GetAction(name) to build a settings screen, then bind through the handle. Bind reports a conflict instead of silently stealing the input:

foreach (var action in binder.Actions)
    Console.WriteLine($"{action.Name} ({action.Type}): {action.Description}");

var result = jump.Bind(0, InputFactory.Key(newKeyCode));

if (result.Result == InputBinderResult.InputBoundToOtherAction)
{
    // The input is already bound to result.ConflictingActionHandle — ask the player, then override:
    jump.Bind(0, InputFactory.Key(newKeyCode), forceMap: true);
}

jump.Unbind(1); // clear a slot

Binding over an occupied slot releases the input that was there, and force-binding an input away from another action clears that action's slot — a physical input never drives two actions at once. WithDefaultBinding binds the same way but never steals: it throws if the binding is invalid or the input already belongs to another action, so clashing defaults surface at startup.

Each handle exposes Name, Description, Type, Representation and its current Inputs (a read-only view with one slot per binding; a slot is null when unbound, and the list is empty once the action is unregistered), plus GetInput(index) for a single slot, so you can render the current mapping.

binder.UnregisterAction(name) or binder.UnregisterAction(handle) removes an action and releases its inputs. A handle only ever unregisters the action it was issued for, never a later action registered under the same name. Bind on a handle whose action has been unregistered returns InputBinderResult.ActionNotRegistered.

Input factories

InputFactory provides terse factories for binding and for polled inputs:

Kind Factories
Button Key(code, state?, modifier?), MouseButton(n, state?), GamePadButton(n, state?), MidiButton(note, state?), Button(source, id, state?, modifier?)
One axis MouseAxis(id, value?), GamePadAxis(id, value?), MidiAxis(id, value?), Axis(source, id, value?)
Two axes MouseAxes(id, value?), GamePadAxes(id, value?), Axes(source, id, value?)

The optional state/value only matter for polled inputs; bindings match on identity (source, id and modifier) alone.

Keyboard button bindings may carry a ButtonModifier value (Shift/Ctrl/Alt) — pass it by name, e.g. InputFactory.Key(83, modifier: ButtonModifier.Ctrl) for Ctrl+S, since the second parameter is state. The Update flags tell the binder which modifiers are currently held. The most specific binding wins: when a held modifier's binding fires for a key, the plain binding for that key is suppressed, so Ctrl+S does not also trigger the action bound to plain

  1. The match is made when the key is Pressed, and the whole press stays with it: pressing or releasing a modifier while the key is held does not move its Down and Up states to another binding, so an action that received Pressed also receives the matching Up. A key the binder first sees already Down (or idle) is matched by the modifiers held at that moment.

When polling inputs for Update, construct each input once and mutate its live state per frame — State, Value and PlayerNumber are settable precisely so polling allocates nothing:

// once, at startup
var space = new ButtonInput(InputSource.Keyboard, 32);

// each frame
space.State = ButtonState.Pressed;
space.PlayerNumber = 0;

Upgrading from 1.x

2.0 is published as Beryllium.InputBinder (1.x was BerylliumInputBinder); the namespaces are unchanged. Changes that can affect existing code:

  • InputBinderResult gained None as its zero value and lost the unused Failure, so an uninitialised BindResult no longer reads as a success. The other members' numeric values moved up by one.
  • new InputBinder(inputBindingsPerAction) throws ArgumentOutOfRangeException below 1 instead of quietly using 1.
  • WithDefaultBinding throws when the input is already bound to another action; it used to take the input silently.
  • Actions is an IReadOnlyList in registration order, and a handle's Inputs is a read-only view instead of the binder's own array.
  • A keyboard press keeps the binding it was matched to on Pressed until its Up, even if modifiers change meanwhile.
  • UnregisterAction(handle) only removes the action the handle was issued for, never another action with that name.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 97 9/26/2026