> For the complete documentation index, see [llms.txt](https://dante-3.gitbook.io/dante-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dante-3.gitbook.io/dante-docs/modular-input-system/core-concepts/custom-gates-and-processors.md).

# Custom Gates and Processors

Gates and processors are small objects you add to an Input Mode. The built-in ones cover common needs; for everything else, subclass them in Blueprint or C++.

## Rules for Every Step

Gates and processors share the base class `UMIPS_InputStep`. Keep these rules in mind:

* **Stateless.** One instance inside a mode asset serves every player that uses the mode. Never store per-player data on the object; read and write it on the pawn, the component, or something you find through the context.
* **Filter first.** Set **Input Tags** and **Trigger Events** on the instance (or defaults in the constructor) so your code only runs for events it cares about.
* **Use the context.** Everything you need is reachable from `FMIPS_InputContext`: the component, the event, the owner, the pawn, the player controller, owner tags and components along the owner chain.
* **World access works.** While the pipeline runs, `GetWorld()` returns the component's world, so Blueprint world-context nodes (timers, traces, spawning) are available.
* **Be quick.** Steps run synchronously inside Enhanced Input's callback, possibly every frame for Triggered events.

### Helper Nodes

| Node                                             | Where      | Returns                                                            |
| ------------------------------------------------ | ---------- | ------------------------------------------------------------------ |
| **Get Input Action Value**                       | Any step   | The event's value; plugs into bool, float or vector pins.          |
| **Get Owner Tags**                               | Library    | The tags gates test against.                                       |
| **Get Context Owner / Pawn / Player Controller** | Library    | Actors around the component.                                       |
| **Find Context Component**                       | Library    | A component of a class from the owner chain, already typed.        |
| **Make Allow / Block / Buffer Result**           | Library    | Gate results.                                                      |
| **Make Fail Result**                             | Gates      | A result with the gate's **Fail Verdict** and your reason.         |
| **Make Handled Result**                          | Processors | *Handled*, or *Handled And Consumed* when **Consume Input** is on. |

## Writing a Gate

Create **Modular Input System > Gates & Processors > Input Gate** and override **Evaluate**. Return an allow result to let the event through, or a fail result with a reason tag.

*Example:* a stamina gate that blocks dodges while stamina is too low.

{% tabs %}
{% tab title="Blueprint" %}

```
Event Evaluate (Context)
  ├─ Find Context Component (Context, BP_StaminaComponent) ──► Stamina
  ├─ Branch: Stamina.Current >= DodgeCost
  │     True  ──► Return: Make Allow Result
  │     False ──► Return: Make Fail Result
  │                  Reason: Game.InputReason.OutOfStamina
  │                  Detail: Format Text "Stamina {0}/{1}"
```

<figure><img src="https://4216330531-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsGXprPiDJ68FN8HnOaN%2Fuploads%2FtCNodFSkz5EA70BIKaGT%2Fimage.png?alt=media&amp;token=027880cc-9316-4972-ac75-ef31474d58c0" alt=""><figcaption></figcaption></figure>

Add the gate to your mode's **Gates** list and set its **Input Tags** to `Input.Dodge`. Set **Fail Verdict** to *Buffer* if a dodge pressed while exhausted should wait in the buffer instead of vanishing.
{% endtab %}

{% tab title="C++" %}
{% code title="MyGate\_Stamina.h" %}

```cpp
#pragma once

#include "Gates/MIPS_InputGate.h"
#include "MyGate_Stamina.generated.h"

UCLASS(meta=(DisplayName="Stamina"))
class UMyGate_Stamina : public UMIPS_InputGate
{
    GENERATED_BODY()

public:
    UMyGate_Stamina()
    {
        // React to presses only; Triggered repeats every frame while held.
        TriggerEvents = MIPS::TriggerEventBit(ETriggerEvent::Started);
    }

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Gate", meta=(ClampMin="0"))
    float Cost = 20.f;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Gate")
    FGameplayTag Reason;   // e.g. Game.InputReason.OutOfStamina

    virtual FMIPS_GateResult Evaluate_Implementation(const FMIPS_InputContext& Context) const override
    {
        const APawn* Pawn = Context.GetPawn();
        const UMyStaminaComponent* Stamina = Pawn ? Pawn->FindComponentByClass<UMyStaminaComponent>() : nullptr;
        if (!Stamina || Stamina->GetCurrent() >= Cost)
        {
            return FMIPS_GateResult::Allow();
        }

        return MakeFailResult(Reason,
            FString::Printf(TEXT("Stamina %.0f/%.0f"), Stamina->GetCurrent(), Cost));
    }

    virtual FString GetDebugLabel() const override
    {
        return FString::Printf(TEXT("%s (%.0f)"), *Super::GetDebugLabel(), Cost);
    }
};
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
Reason tags are ordinary gameplay tags. Define your own in **Project Settings > Gameplay Tags**, in an ini file, or natively with `UE_DEFINE_GAMEPLAY_TAG`. A gate that fails without a reason is reported as `ModularInput.Reason.Unspecified`.
{% endhint %}

## Writing a Processor

Create **Modular Input System > Gates & Processors > Input Processor** and override **Handle**. Optionally override **Should Handle** for checks beyond the filter; Handle is only called when it returns true.

*Example:* a dodge that goes in the direction the player is steering.

{% tabs %}
{% tab title="Blueprint" %}

```
Event Handle (Context)
  ├─ Get Context Pawn (Context) ──► Cast To BP_Hero ──► Hero
  ├─ Context.Component ──► Get Last Input Value (Input.Move) ──► Move Axis (Vector 2D)
  ├─ Hero ──► Dodge (Move Axis)
  └─ Return: Make Handled Result
```

<figure><img src="https://4216330531-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsGXprPiDJ68FN8HnOaN%2Fuploads%2FTzGj0kBE9oCBf8xwkxd0%2Fimage.png?alt=media&amp;token=6edad14c-f918-4441-8e7f-cc2e3838dde9" alt=""><figcaption></figcaption></figure>

Returning **Not Handled** lets the next processor try; **Make Handled Result** returns *Handled* or, with **Consume Input** ticked on the instance, *Handled And Consumed*.
{% endtab %}

{% tab title="C++" %}
{% code title="MyProcessor\_Dodge.h" %}

```cpp
#pragma once

#include "Processors/MIPS_InputProcessor.h"
#include "Runtime/MIPS_InputComponent.h"
#include "MyProcessor_Dodge.generated.h"

UCLASS(meta=(DisplayName="Dodge"))
class UMyProcessor_Dodge : public UMIPS_InputProcessor
{
    GENERATED_BODY()

public:
    UMyProcessor_Dodge()
    {
        TriggerEvents = MIPS::TriggerEventBit(ETriggerEvent::Started);
        bConsumeInput = true;
    }

    /** Input whose last value steers the dodge. */
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Dodge")
    FGameplayTag MoveInputTag;

    virtual bool ShouldHandle_Implementation(const FMIPS_InputContext& Context) const override
    {
        const AMyHero* Hero = Cast<AMyHero>(Context.GetPawn());
        return Hero && !Hero->IsDodging();
    }

    virtual EMIPS_HandleResult Handle_Implementation(const FMIPS_InputContext& Context) const override
    {
        AMyHero* Hero = CastChecked<AMyHero>(Context.GetPawn());
        const FVector2D Axis = Context.Component->GetLastInputValue(MoveInputTag).Get<FVector2D>();
        Hero->Dodge(Axis);
        return MakeHandledResult();
    }
};
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Validation (C++)

Steps can report configuration mistakes. The owning mode calls `IsDataValid` on every gate and processor when it is saved or validated:

```cpp
#if WITH_EDITOR
virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override
{
    EDataValidationResult Result = Super::IsDataValid(Context);
    if (!MoveInputTag.IsValid())
    {
        Context.AddError(NSLOCTEXT("MyGame", "DodgeNoMove", "Dodge processor has no Move Input Tag."));
        Result = EDataValidationResult::Invalid;
    }
    return Result;
}
#endif
```

## Titles and Labels

| Member                               | Where it shows                                                        | Default                                                            |
| ------------------------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `BuildEditorTitle()` *(editor only)* | Entry title in the mode's Gates and Processors lists                  | Class display name plus the filter, e.g. `Dodge (Dodge · Started)` |
| `GetDebugLabel()`                    | Input Monitor, gameplay debugger and log ("Stopped By", "Handled By") | Class name                                                         |

Override `GetDebugLabel` to include the settings that matter, as the built-in steps do, e.g. `Cooldown (0.20s)`.

## Choosing Between a Processor and a Receiver

| Use a processor when...                          | Use an [input receiver](/dante-docs/modular-input-system/core-concepts/input-receivers.md) when... |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| the behaviour is reusable across pawns and modes | the behaviour belongs to one pawn or component                                                     |
| designers should configure it per mode           | you want all of a pawn's input handling in its own Event Graph                                     |
| it should run without knowing the pawn class     | it needs the pawn's private state                                                                  |

## Next Steps
