Input Binding Profiles and Rebinding¶
See Quick Start for lobby setup and Gameplay Integration for owned action binding.
This opt-in runtime API belongs to Couch Co-op Builder. A game such as Rocket
Rumble can build its settings UI on top of it. The framework does not create a
controls menu, change existing Join/Leave listeners, or rebind Smart UI Focus & Navigation.
It changes the owned gameplay action clones exposed by GameplayActionInput.
Source InputActionAssets and GenericControllerProfile assets are not rewritten.
Identity and shared profiles¶
PlayerIndex is a reusable session seat displayed as P1–P8; it remains stable until permanent Leave. InputSlot separately identifies the reserved input role/source, and SessionId is stable for that session. None is a persistent binding-profile key. Editing "P3 Controls" means editing
playerBinding.Input.BindingProfile, the input source currently assigned to P3.
After Leave, the next occupant receives its own source's profile.
| Source | Default persistent identity | Consequence |
|---|---|---|
| Keyboard A | KeyboardA |
Follows the logical A role across seat changes. |
| Keyboard B | KeyboardB |
Follows the logical B role across seat changes. |
| Standard gamepad | Gamepad:Profile: plus a metadata family fingerprint |
Same reported family shares saved overrides. Xbox and DualSense families do not inherit each other's overrides through seat reuse. |
| Generic joystick | Joystick:Profile: plus the matched GenericControllerProfile identity fingerprint |
Uses the actual discovered profile; identical devices share that profile. |
Gamepad family metadata includes the Unity layout, interface, manufacturer, product and HID VID/PID when reported. Display names, runtime device names, InputDevice.deviceId and serial numbers are not used as unique identity. A serial field alone is not evidence of cross-launch stability or uniqueness. A transport or layout change can select a different family fallback; this is a default/shared profile, not a claim of physical-device identity.
For generic joysticks, assign PersistentBindingProfileId once in the profile
Inspector, or ConfigureBindingProfileId during configuration. Reuse that stable
customer-owned ID when updating the profile asset. Existing assets with a blank
ID use exact base-layout/interface/manufacturer/product/HID match fields. The
fallback does not depend on asset/display names or generated binding GUIDs.
Changing those match fields changes the fallback key. Use distinct explicit IDs
for distinct profiles with otherwise identical match metadata.
Identical controllers cannot be reliably distinguished across launches by this fallback. They have independent actions, sessions and InputUsers, but share binding settings. Editing/resetting that shared profile updates every attached consumer of it. No fuzzy matching or invented physical identity is used.
A platform/customer integration with verified stable, unique physical identity
can implement IInputBindingDeviceIdentityProvider and supply it to
InputBindingProfileResolver. Returning true asserts both properties. Do not
return a transient deviceId, enumerated display name, player seat, or unverified
serial. Verified gamepad keys survive metadata/transport changes; verified
joystick keys remain scoped to their controller profile. The framework itself
provides no cross-platform guarantee of unique physical identity.
Service and storage¶
Keep one InputBindingProfileService for the game's controls settings lifetime,
usually across scene transitions. It owns one complete versioned document in an
IInputBindingProfileStore. The core model has no dependency on UI or PlayerPrefs.
JsonFileInputBindingProfileStore is the simple built-in desktop/mobile backend.
Its default file is Application.persistentDataPath/CouchCoopBuilder/input-bindings.json.
An explicit file path is also supported. It writes a sibling temporary file and
replaces the previous document. Customers can implement the two-method store
interface using their own save system. Save must replace the whole document
atomically, or throw without changing the previous document. Use one writer per
store. Platforms without suitable filesystem support, including WebGL/cloud save
integrations, should supply their own backend.
Load explicitly and handle a failure before exposing editing/resetting. A failed load retains the last successfully loaded document and live bindings. An empty or missing file means defaults. Unsupported versions, malformed data and duplicate records are rejected. Do not silently overwrite an unknown or unreadable save by continuing with defaults after a failed load.
var profiles = new InputBindingProfileService(new JsonFileInputBindingProfileStore());
if (!profiles.TryLoad(out string loadFailure))
{
// Show/report loadFailure and stop editing until the save is resolved.
profiles.Dispose();
return;
}
// After an existing GameplayPlayerBinding has bound its input:
var playerProfile = profiles.Attach(playerBinding.Input);
bool saved = playerProfile.TryRebind(
"PrimaryAction", 0, "<Gamepad>/buttonNorth", out string failure);
Attach after gameplay input is available, for example from the existing
GameplayPlayerBinding.InputBound callback. Reattach to the new input when the
scene representation is recreated. The service automatically detaches when that
input is disposed and retains saved overrides. It never creates another InputUser
or replaces InputUser.actions. Custom integrations can attach to their own
GameplayActionInput.Create result and remain responsible for disposing it.
An input can have one binding profile session. A stable optional domain
(default Gameplay) separates different customer action schemas in the same
profile. Keep the domain stable across scenes using the same schema. Only the
owned gameplay map is enabled/modified, not every map in the source asset.
Settings before joining¶
profiles.OpenProfile(kind, gameplaySettings, device, joystickProfile) opens a
settings preview using the same identity, storage, validation and reset operations.
The returned InputBindingProfileSession.ActionMap is an owned, disabled clone for
display and editing. It creates no PlayerSession, InputUser or synthetic device.
Dispose the preview when changing profile or leaving settings. Later
Attach(playerBinding.Input) consumes the saved overrides for that input source.
Both keyboard roles are validated in the preview even when nobody has joined.
With no device, a preview can display defaults/saved data and reset, but cannot
capture/rebind (CanRebind is false). A device-less gamepad preview represents the
generic default Gamepad family; it is not a saved Xbox/DualSense physical identity.
When a controller is connected, open its actual resolved family profile. Joystick
previews require a configured GenericControllerProfile and use its identity, even
when its device is absent. Connected joysticks must match that profile.
SharedJoystickInput.ResolveProfile exposes the same strict discovery scoring
and ambiguity result without enabling join listeners.
Capture, paths and conflicts¶
var capture = playerProfile.BeginInteractiveRebind("PrimaryAction", 0);
capture.Finished += result =>
{
// Inspect Status: Succeeded, Rejected or Canceled, and Failure on rejection.
// Refresh UI here. Dispose/unsubscribe UI handlers when the menu closes.
};
// The UI's Cancel button or teardown calls capture.Cancel()/Dispose().
Only one capture can run per service. The target map is disabled during capture;
other players' maps keep working. Capture accepts and suppresses only events from
the target's paired devices. Runtime device paths restrict capture but are never
saved. Other controllers and another player's Escape key cannot choose/cancel a
binding. Cancel is explicit, or occurs at the default ten-second timeout, device
loss/disable/unpair, input disposal, session disposal or service disposal. The
operation releases native resources and restores the target map when its input
is still available. The timeout is configurable. A caller can optionally supply cancelControl to
BeginInteractiveRebind; it must belong to the selected source and uses Unity's
existing native cancellation path. It is reserved for cancel during that capture,
not saved as an override. Without it, cancellation remains explicit or by timeout.
Programmatic overrides must be a concrete layout-relative path such as
<Keyboard>/h, <Gamepad>/buttonNorth or <Joystick>/button11. An empty string
unbinds. Runtime instance paths and wildcard/usage paths are rejected. The path
must resolve on the source's paired device and fit the action's expected control
type. Rebind individual composite parts by binding index, not a composite header.
Keyboard sources accept individual keys.
Keyboard A/B share one physical keyboard. The service validates both logical role maps, including the defaults/saved role that has not joined, and rejects cross-role overlap before saving or mutating live actions. Conflicting capture is reported as Rejected with a reason. Reuse by different actions in the same gameplay map is also rejected; multiple bindings of the same action are valid. Identical logical button paths on separate gamepads/joysticks are valid; they are not cross-device conflicts.
The consumer should close or otherwise gate its lobby controls while editing if captured keys also have existing Join/Leave/UI meaning. This foundation does not replace those listeners or implement a modal controls menu.
Reset and action schema updates¶
playerProfile.ResetAction(actionName, out failure)resets that profile/action in the current domain.playerProfile.ResetProfile(out failure)implements Reset Player: reset the current source's persistent profile, across its domains and attached consumers.profiles.ResetAll(out failure)clears all persisted framework profile overrides, including profiles with no currently attached player.- The service also exposes profile-based ResetAction/ResetProfile for settings screens that do not have a live player.
Reset restores the authored defaults captured at attachment, including any
customer-authored template overrides, processors and interactions. It does not
rewrite source assets. Changes validate using scratch maps and save before live
maps change. A failed store save leaves live actions and the document unchanged.
Finish/cancel capture before another mutation or reset. ProfilesChanged signals
successful changes; PersistedProfileKeys lists profiles with saved overrides.
All service operations are intended for Unity's main thread.
Binding addresses use action name and original structural binding information,
not Unity-generated GUIDs (joystick actions are reconstructed with fresh GUIDs).
If an action/binding schema changes, unmatched stored addresses are retained and
listed in playerProfile.UnappliedBindings; they are not applied to a different
binding. Plan an explicit customer save migration or reset for schema changes.
Dispose the service at application teardown. Dispose/cancel captures when their UI closes. Detaching a live session restores its owned map's defaults; disposing the underlying GameplayActionInput detaches without touching destroyed assets.
Simultaneous action conflicts¶
Profile validation rejects one physical control assigned to different actions in
the selected gameplay map/domain, including composite parts and parent controls.
Two bindings of the same action are valid. Both keyboard role maps are compared
because they share a Keyboard. Separate controller devices are not compared.
Lobby/UI maps and other binding domains are independent and may reuse controls.
InputBindingProfileService.LastConflict exposes control display/path, both
semantic action names and profile keys after a rejected transaction. Rejection
leaves live bindings and storage unchanged. Authored defaults must also be valid
for their simultaneously active map; resets are validated before saving.