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
<PackageReference Include="Beryllium.InputBinder" Version="2.0.0" />
<PackageVersion Include="Beryllium.InputBinder" Version="2.0.0" />
<PackageReference Include="Beryllium.InputBinder" />
paket add Beryllium.InputBinder --version 2.0.0
#r "nuget: Beryllium.InputBinder, 2.0.0"
#:package Beryllium.InputBinder@2.0.0
#addin nuget:?package=Beryllium.InputBinder&version=2.0.0
#tool nuget:?package=Beryllium.InputBinder&version=2.0.0
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
- 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 itsDownandUpstates to another binding, so an action that receivedPressedalso receives the matchingUp. A key the binder first sees alreadyDown(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:
InputBinderResultgainedNoneas its zero value and lost the unusedFailure, so an uninitialisedBindResultno longer reads as a success. The other members' numeric values moved up by one.new InputBinder(inputBindingsPerAction)throwsArgumentOutOfRangeExceptionbelow 1 instead of quietly using 1.WithDefaultBindingthrows when the input is already bound to another action; it used to take the input silently.Actionsis anIReadOnlyListin registration order, and a handle'sInputsis a read-only view instead of the binder's own array.- A keyboard press keeps the binding it was matched to on
Presseduntil itsUp, even if modifiers change meanwhile. UnregisterAction(handle)only removes the action the handle was issued for, never another action with that name.
| Product | Versions 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. |
-
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 |