Skip to content

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:

SignalPayloadWhen
ContextActivatedSignal<string>A context is pushed
ContextDeactivatedSignal<string>A context is popped
CurrentContextChangedSignal<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:

  1. Listens to InputSystem.onDeviceChange and InputUser.onChange.
  2. Classifies connected devices into CarrotInputDevice instances with brand, model, glyph set, and capability flags.
  3. Caches devices by composite key (scheme|brand|model|glyphs|vendor|product|name) to avoid repeated allocations.
  4. Dispatches PlayerDeviceChanged (Signal<PlayerDeviceChange>, payload { PlayerIndex, Device }) and GlobalDeviceChanged (Signal<CarrotInputDevice>) when the active device changes.

Classification uses display name substring matching:

Substring MatchBrandModel(s)
xboxXboxXbox360 / XboxOne / XboxSeries
dualshock, ds4PlayStationDualShock4
dualsense, ps5PlayStationDualSense
switch, joy-conNintendoSwitchPro / JoyConPair
steamSteamSteamDeck / 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 (references Carrot, Unity.InputSystem)
  • Editor assembly: Carrot.Input.Editors (references Carrot, 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. NotifyContextDeactivated only 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 combineKeyboardAndMouse on the manager.

Usage Guide

Input context management, device tracking, and adapter scaffolding for Unity's Input System.

Setup

  1. Add kids.kapish.input to your Unity project's package manifest.
  2. Ensure the Unity Input System package is installed and active.
  3. Add a CarrotInputDeviceManager component to a persistent GameObject in your scene (it calls DontDestroyOnLoad on itself).
  4. Add kids.kapish.signals to 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 sprites

7. 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. CarrotInputDeviceManager enforces singleton behaviour and destroys duplicates.
  • Global fallback. If a player has no explicit context, CurrentContext(playerId) returns the global stack's top value.

Carrot