Command Extension Points
Before extending a command, decide which layer should own the rule. The command definition owns semantics, validation, target scoring, and execution-backend selection. CommandSystem owns acceptance, queuing, cancellation, observable state, and terminal results on the Pawn. The execution definition routes one accepted, exact command execution into StateTree, an Ability, a Behavior Tree, or the project runtime. Do not let the execution backend maintain another command queue, and do not let UI or Tactical Assignment bypass command-level validation.
Related documentation: for system context, see Command System; for configuration fields, see Command Definition and Dispatch Parameters; for the minimal integration flow, see Integrating a Minimal Commandable Unit.
Choose the Extension Layer First
Requirement | Preferred location | Example | Do not place it here |
|---|---|---|---|
One stable, editable command configuration | A Blueprint/C++ subclass of | Move, Interact, Heal. | Do not create a raw Definition implementation for every fixed configuration. |
Multiple stable variants behind one entry point | | Melee, ranged, and ability variants of Attack. | Do not use DebugName as runtime identity or duplicate each variant into unrelated assets. |
Configuration must be computed dynamically from the executing Pawn, equipment, class, or state | A | The same Attack returns a different Range and backend depending on the weapon. | Do not secretly rewrite command semantics in UI or the execution layer. |
Determine whether the target is valid or the unit can execute | | Prevent casting while stunned; prevent healing enemies. | Do not rely only on disabled buttons or Tactical Filters. |
Express automatic target/tactical-assignment preference | | Healing prioritizes nearby allies with low health. | Do not collect candidates inside the scoring function. |
Define how an accepted command actually runs | | Enter StateTree, activate an Ability, request a Behavior Tree task. | Do not make CommandSystem depend directly on a specific AI framework. |
When to Inherit Directly from UGAIS_CommandDefinition
Simple and MultiVariant already cover the main path of static configuration plus a small set of stable variants. Directly inheriting from the abstract base class should be reserved for cases where behavior must be chosen dynamically from CommandedActor and Index, for example:
- The same attack semantics switch between melee, ranged, and drone backends depending on the weapon.
- Class, Buffs, or resources change the target type, tag requirements, range, or UI.
- The project has custom target-data rules, group destination algorithms, or executability constraints.
- The same Tactical Intent must resolve to different command fragments for different Units.
Even then, each override should still own only one stage. If there are only three fixed attack variants, keep using MultiVariant. If only fixed values differ, keep using Simple. This preserves most rules as readable asset configuration while C++/Blueprint overrides carry only the genuinely dynamic differences.
Overridable Behavior in UGAIS_CommandDefinition
Extension point | What it changes | Boundary |
|---|---|---|
| Whether the executing Pawn has the required resources, state, class, tags, or project permission. | Evaluates only the executor; it does not replace concrete target validation. |
| Whether an Actor/location target satisfies command rules. | This is the command-level source of truth; UI and Tactical Filters may only prune early. |
| Preference among legal targets. | A score cannot make an illegal target valid; candidate collection belongs to Sources or project code. |
| Computes different destination points per Pawn during group issue. | Produces target locations only; it does not directly drive the movement component. |
| Dynamic TargetType and cancellation policy. | The sender must use the resolved, actual target contract. |
| Dynamic tag requirements and success conditions. | Do not hide a large decision tree here that UI cannot explain. |
| Selects an execution adapter by Pawn or variant. | The returned object must be stateless; CommandSystem still owns termination after the backend starts. |
| Dynamic name, icon, and button slot. | Affects display only; it does not make the command valid. |
| Dynamic distance semantics and scoring scale. | Does not replace navigation completion, candidate Sources, or final target validation. |
Prefer the Default StateTree Execution Layer
UGAIS_CommandExecutionDefinition_StateTree routes the command into the reserved Command layer of StateTreeStack and stores a complete FStateTreeReference, including asset-level StateTree parameters and property overrides. It already fits most AI commands: movement, interaction, attack flows, animation coordination, wait conditions, and activating an Ability from inside StateTree.
Therefore, when a GAS Ability is only one step in a command StateTree, keeping the default StateTree backend is usually the better choice. It preserves Command-layer priority and fallback so Unit/Squad AI can naturally resume when the command ends. Simple can configure one StateTree; each MultiVariant variant can configure its own StateTree, which is enough for one command entry point to drive very different behavior flows.
The default backend requires the command recipient to be a non-player-controlled Pawn, controlled by an AAIController, with a UGAIS_StateTreeStackComponent on that Controller. If these conditions are not met, do not try to patch the StateTree execution definition. Choose a backend appropriate for that Pawn and gameplay context.
When to Create a Custom Execution Definition
Inherit from UGAIS_CommandExecutionDefinition in the following cases:
- The project's existing Behavior Tree runner is the true owner of behavior tasks.
- The command should directly activate and wait for a GAS Ability.
- An animation, interaction, vehicle, or network action system owns an independent asynchronous lifecycle.
- A player-controlled Pawn must respond to commands but does not satisfy the built-in AI StateTree backend requirements.
A custom backend has only two responsibilities: StartExecution establishes the route for this exact command execution, and CancelExecution releases external state still owned by that execution. When asynchronous work completes, report success, failure, or cancellation through the Context/CommandSystem. Returning false means the backend did not accept the start request. Once an asynchronous route has been established, it must eventually report termination; otherwise CommandSystem will wait forever.
Two common backend examples:
Backend | What the adapter should do | Common mistake |
|---|---|---|
Behavior Tree | Request execution of a project BT task/subtree and retain the exact identity needed to report completion for this command. | Starts the Task but never reports Task success/failure to CommandSystem. |
GAS Ability | Activate the selected Ability, subscribe to success, failure, and cancellation, and map those events to command terminal results. | Cancels an Ability activated by a newer command while trying to cancel the old command. |
Exact Execution Identity and Cancellation Boundaries
CommandSystem creates an opaque execution identity for every accepted command. The backend must complete or cancel the exact execution represented by the Context, rather than identifying it only by "the same Definition" or "currently attacking." This is especially important during rapid replacement, insertion, and queue advancement: an old command may already have been canceled while a newer command uses the same Definition as a separate execution.
The safe rule is: record the exact backend-owned execution when it starts; report completion only for the execution that still matches; and on cancellation, release only the task, Ability, StateTree route, or visual state owned by that execution. CommandSystem invalidates the old identity first, so late callbacks cannot terminate a later command. The adapter should still unsubscribe from its own events to prevent resource leaks.
Troubleshooting Order
- Confirm that CommandSystem is on the Pawn and that the authority actually receives the request.
- Confirm that Definition, Index, TargetType, and
FGAIS_CommandDataare consistent. - Check error tags from
CanObeyCommandandIsValidTarget. - Confirm that
GetExecutionDefinitionreturns a valid adapter for that Pawn/Index. - When using the StateTree backend, confirm a non-player-controlled Pawn,
AAIController, StateTreeStack, and a valid StateTree reference. - For a custom backend, confirm that Start establishes a route that can terminate, and that completion/cancellation callbacks affect only the exact execution.
- Finally, inspect GameplayDebugger Current, Queue, LastResult, and ReasonTag instead of relying only on execution-layer logs.
