Extension
Authoring Custom Conditions
Add a supported typed pass/fail check for Guards and decision logic.
- Audience
- Unity developers
- Time
- 25 minutes
- Requires
- C#, Flow Core runtime assembly access, and codegen access
- Modules
- Flow Core, C#
On this page
Create a custom Condition when a project-specific rule must return pass or fail inside a Guard or another supported decision surface. A Condition reports a decision; it should not perform gameplay side effects.
Define the Condition
This example checks whether a typed float exceeds an authored threshold:
using System;
using System.Threading.Tasks;
using UnityEngine;
using TwoCatsCode.FlowCore;
[FlowCondition("Float Above Threshold", "Project", "Check a float value.")]
public sealed class ConditionFloatAboveThreshold : ICondition
{
[Serializable]
public sealed class Data : ConditionData
{
[Tooltip("Provides the float compared with Threshold. Select a literal or typed context value; the Condition returns false when this value cannot resolve.")]
public FlowValueGetFloatData Value = new();
[Tooltip("Minimum exclusive boundary for a passing result. The resolved Value must be strictly greater than Threshold; an equal value returns false.")]
public float Threshold = 0.5f;
public override ConditionData Clone()
{
return new Data
{
Value = Value?.Clone() as FlowValueGetFloatData
?? new FlowValueGetFloatData(),
Threshold = Threshold
};
}
public override string Summary(FlowContext context)
{
return $"{Value?.Summary(context)} > {Threshold}";
}
}
private readonly Data data;
public ConditionFloatAboveThreshold(Data data)
{
this.data = data ?? new Data();
this.data.Value ??= new FlowValueGetFloatData();
}
public ValueTask<bool> EvaluateAsync(FlowContext context)
{
if (!FlowValueResolveHelper.TryResolveValue(
data.Value, context, out float value, out _))
{
return new ValueTask<bool>(false);
}
return new ValueTask<bool>(value > data.Threshold);
}
public string Summary(FlowContext context) => string.Empty;
}
Decide failure behavior
Return false when the rule does not pass or when required data cannot resolve. The node or host that evaluates the Condition decides what false means through Guard logic, Reject Result, or module-specific behavior.
Do not mutate Blackboard state to force a later evaluation. Put state changes in an Instruction, then let the Condition read the resulting state explicitly.
Keep optional controls tied to authored state
For an optional range-check extension, these fields illustrate a combined visibility rule:
[Tooltip("Enables the optional range check. Implement the intended range rule explicitly in EvaluateAsync.")]
public bool CheckRange;
[Tooltip("Includes an upper limit when the range check is enabled. The visibility rule only controls authoring UI.")]
public bool UseUpperBound;
[Tooltip("Upper limit used when both range-check toggles are enabled. Preserve this value in Clone and define its runtime comparison in EvaluateAsync.")]
[FlowFieldAutoUI(Show = "$CheckRange && $UseUpperBound")]
public float UpperBound = 1f;
The reflection UI shows Upper Bound when both toggles are enabled and retains its value when hidden. If you adapt this fragment, clone all three fields and implement their intended meaning in EvaluateAsync; the metadata does not add the runtime comparison.
Use supported boolean and enum expressions, not method calls, arithmetic, or relational operators. A visibility expression that cannot be evaluated leaves the field visible and reports a bounded warning. See Control Optional Fields for the supported forms and failure behavior.
Register and test
- Import the supported runtime source and required helpers, then wait for error-free compilation. Flow Core 6.0 supplies its binding automatically; no central registry-generation step is required.
- Add a Guard to a test Action.
- Add
Project > Float Above Threshold. - Bind
Valueto a Local Blackboard Float. - Route the Action to a visible result.
- Test one value below and one value above the threshold.
- Test the missing-key failure case.
The package includes built-in generated Drawers. Regenerate separately maintained Drawer implementations with the current Generator when their templates or field contracts change. Keep output roots and review the generated diff; do not hand-edit generated files. In 6.0, compiler bindings replace central registration-file maintenance. Handwritten custom Drawers keep their own UI. Reflection-only regions receive improvements without regeneration.
Select the test Action in the Flow Inspector and expand the Guard and its Condition. Single Focus preserves the ancestor path required to edit the leaf. Use More > Open Editor for a dedicated Condition editor or Information for the documented fields. Confirm both the configured values and the actual true/false runtime results; reference information does not evaluate the Condition.
The picker and Guard configuration appear at the top. The middle Graph and Console show the expected rejection warning for ThresholdValue 0.25; the lower pair shows an above-threshold invocation logging Guard passed and Downstream result reached. These are separate runtime captures.
Common Mistakes
- Performing an operation inside
EvaluateAsyncthat should be an Instruction. - Treating a missing typed value as an arbitrary default that passes.
- Forgetting to clone nested Flow value data.
- Assuming Guard rejection and busy-node Invoke Mode are the same result.
- Placing the Condition in an editor-only assembly.
What You Built
You added a typed, side-effect-free rule that designers can reuse in Guards and other supported decision surfaces, with predictable behavior for passing, failing, and missing data.
For import/removal, prewarm policies, and Player verification, follow Automatic Registration & Prewarm. Generate a Drawer or other implementation helper only when the extension requires one.
![Native picker and Guard detail crops show Project/Float Above Threshold reading Local[0].ThresholdValue against 0.5; separate below- and above-threshold Graph and Console bands show rejection and passage.](/flow-core/docs/images/05_03_01-custom-threshold-condition-results.webp)