Appearance
kids.kapish.input
unity
Input utilities (runtime + editor) for Unity's Input System.
Architecture
The package has three main concerns: context stacking, device tracking, and adapter management.
Context Stacking (CarrotInput)
CarrotInput is a static class that maintains a stack of named input contexts. Contexts are strings (e.g. "Gameplay", "Menu", "Dialogue") pushed and popped as game states change. There are two levels:
- Global stack -- shared across all players, used when no player-specific context is set.
- Per-player stacks -- keyed by
int playerId.CurrentContext(playerId)checks the player stack first, then falls back to global.
The ContextScope struct enables scoped activation:
csharp
using (CarrotInput.Context("PauseMenu"))
{
// "PauseMenu" is the active context here.
// Automatically deactivated when the scope exits.
}Signals dispatch on context changes:
| Signal | Payload | When |
|---|---|---|
ContextActivated | Signal<string> | A context is pushed |
ContextDeactivated | Signal<string> | A context is popped |
CurrentContextChanged | Signal<ContextChange> ({ PlayerIndex, ContextName }) | The effective top-of-stack context changes for a player (or global, PlayerIndex = -1) |
Duplicate consecutive pushes of the same name are silently ignored. ResetAll() clears everything and dispatches change signals.
Device Tracking (CarrotInputDeviceManager)
A singleton MonoBehaviour (persists via DontDestroyOnLoad) that:
- Listens to
InputSystem.onDeviceChangeandInputUser.onChange. - Classifies connected devices into
CarrotInputDeviceinstances with brand, model, glyph set, and capability flags. - Caches devices by composite key (
scheme|brand|model|glyphs|vendor|product|name) to avoid repeated allocations. - Dispatches
PlayerDeviceChanged(Signal<PlayerDeviceChange>, payload{ PlayerIndex, Device }) andGlobalDeviceChanged(Signal<CarrotInputDevice>) when the active device changes.
Classification uses display name substring matching:
| Substring Match | Brand | Model(s) |
|---|---|---|
xbox | Xbox | Xbox360 / XboxOne / XboxSeries |
dualshock, ds4 | PlayStation | DualShock4 |
dualsense, ps5 | PlayStation | DualSense |
switch, joy-con | Nintendo | SwitchPro / JoyConPair |
steam | Steam | SteamDeck / SteamController |
Each classification includes a CarrotGlyphSet for UI prompt rendering and a CarrotDeviceCapabilities flags value.
Input Adapters (CarrotInputAdapterBase)
Abstract MonoBehaviour base class for scene-specific input adapters. Configured via the Inspector:
inputActions-- the Input Action Asset to manage.enabledActionMaps/disabledActionMaps-- action maps to toggle on enable/disable.lockCursor/hideCursor-- cursor state management.
On enable, the adapter enables its maps, optionally locks/hides the cursor, and pushes a context (using the adapter's class name) via CarrotInput.NotifyContextActivated. On disable, it reverses everything.
Assembly
- Runtime assembly:
Carrot.Input(referencesCarrot,Unity.InputSystem) - Editor assembly:
Carrot.Input.Editors(referencesCarrot,Carrot.Input)
Key Design Decisions
- Static context stack. Contexts are globally accessible without injection, matching Unity's typical singleton patterns.
- String-based contexts. Simple, debuggable, and zero-allocation on the hot path (no enum boxing).
- Defensive pop.
NotifyContextDeactivatedonly pops if the top of the stack matches -- prevents mismatched push/pop from corrupting state. - Device dedup via cache. Prevents per-frame allocations when the same device is re-detected.
- Keyboard + Mouse combined. By default, keyboard and mouse are treated as a single "KeyboardMouse" scheme. Configurable via
combineKeyboardAndMouseon the manager.
Usage Guide
Input context management, device tracking, and adapter scaffolding for Unity's Input System.
Setup
- Add
kids.kapish.inputto your Unity project's package manifest. - Ensure the Unity Input System package is installed and active.
- Add a
CarrotInputDeviceManagercomponent to a persistent GameObject in your scene (it callsDontDestroyOnLoadon itself). - Add
kids.kapish.signalsto your asmdef references if using the input signals from your own code.
Common Patterns
1. Push/pop input contexts
Use named contexts to control which input actions are relevant:
csharp
using Carrot.Input;
// Push a context manually
CarrotInput.NotifyContextActivated("Gameplay");
// Later, pop it
CarrotInput.NotifyContextDeactivated("Gameplay");2. Scoped contexts with using
The ContextScope struct auto-pops on dispose:
csharp
using Carrot.Input;
using (CarrotInput.Context("PauseMenu"))
{
// PauseMenu is the active context.
// Automatically deactivated when scope exits.
}3. Per-player contexts
For local multiplayer, pass a player ID:
csharp
using Carrot.Input;
// Player 0 enters a menu
CarrotInput.NotifyContextActivated("Inventory", playerId: 0);
// Check effective context (falls back to global if no player-specific context)
string? ctx = CarrotInput.CurrentContext(playerId: 0);4. React to context changes
csharp
using Carrot.Input;
var unsub = CarrotInput.CurrentContextChanged.Add(change =>
{
Debug.Log($"Player {change.PlayerIndex} context is now: {change.ContextName}");
});
// unsub() later, or CarrotInput.CurrentContextChanged.Remove(handler)5. Track the current input device
csharp
using Carrot.Input;
using Carrot.Input.Devices;
CarrotInputDeviceManager.GlobalDeviceChanged.Add(device =>
{
Debug.Log($"Now using: {device.Name} ({device.Scheme}, {device.Brand})");
});
// .Add() returns an unsubscribe Action — capture it if you need to detach later.6. Show correct button glyphs
Use the Glyphs property to select the right sprite set:
csharp
using Carrot.Input;
using Carrot.Input.Devices;
using Carrot.Input.UI;
CarrotInputDevice? device = CarrotInputDeviceManager.Instance.GetCurrentDevice(playerId: 0);
CarrotGlyphSet glyphs = device?.Glyphs ?? CarrotGlyphSet.Generic;
// Use glyphs to select the correct button prompt sprites7. Query device capabilities
csharp
using Carrot.Input.Devices;
CarrotInputDevice? device = CarrotInputDeviceManager.Instance.GetCurrentDevice(0);
if (device != null && device.Capabilities.HasFlag(CarrotDeviceCapabilities.Gyro))
{
// Enable gyro aiming
}8. Build an input adapter
Subclass CarrotInputAdapterBase for scene-specific input wiring:
csharp
using Carrot.Input.Adapters;
public class GameplayInputAdapter : CarrotInputAdapterBase
{
// Configure in Inspector:
// - Input Action Asset
// - Enabled action maps: ["Gameplay"]
// - Disabled action maps: ["UI"]
// - Lock cursor: true
// - Hide cursor: true
}The adapter automatically pushes its class name ("GameplayInputAdapter") as a context on enable and pops it on disable.
Tips
- Reset on scene load. Call
CarrotInput.ResetAll()during scene transitions to clear stale contexts. - One manager per scene hierarchy.
CarrotInputDeviceManagerenforces singleton behaviour and destroys duplicates. - Global fallback. If a player has no explicit context,
CurrentContext(playerId)returns the global stack's top value.