Adds a USFConditional base class, which allows designers to easily configure complex
questions about objects. Answers to Conditional questions return both a binary and fuzzy answer and are thus
open for threshold-based or weighted answer handling.
Conditionals are open for extension: this plugin provides a few project-agnostic conditionals, but the intention is for you to build your own subclasses for your systems, as needed.
This is basically an implementation of the "P2Conditional" system Double Fine implemented and presented in this blog post: Behind The Code: Locked On Target.
This plugin includes some code for scoped automation test worlds from the excellent Unreal Weekend Utils.
The plugin was developed for Unreal Engine 5.5+, though it should work for all 5.X versions.
The best way is to clone this repository as a submodule; that way you can contribute
pull requests if you want and more importantly, easily get latest updates.
The project should be placed in your project's Plugins folder.
> cd YourProject
> git submodule add https://github.com/Strayfarer/SFConditional
> git add ../.gitmodules
> git commit
Alternatively you can download the ZIP of this repo and place it in
YourProject/Plugins/.
This plugin is currently in an beta state. As of publishing, there are no known bugs. Included are quality of life features such as Blueprint, Gameplay Debugger and data validation support. It hasn't been battle-tested by me in a full-on production yet, but it got iterated already during a few of my private projects and is unit-tested.
Feel free to open new issues in the GitHub issues tab. Any general feedback and pull-requests are much appreciated!
To configure a Conditional question, add a new SFConditional property via BP or C++,
expose it to be editable in the editor and compile.
Now click on the property and in the details tab, build your question, by assigning the conditional tree modelling the question you have.
in this example, we building a question asking whether the instigator can give an item to someone:
We're requiring the object to be an interactable target component, in reasonable range and screen area, component of an NPC. And we're requiring the instigator to carry something.
After having done the setup above, you can drag the variable into your graph, and call Evaluate(EvaluationContext) on it,
assigning an object to test and an optional instigator (which might be mandatory for some conditionals though!):
In C++, you can additionally pass in a FSFConditionalDebugTrace, to which the conditional evaluation
will write debug information per conditional, with an option to retrieve an easily readable debug string
at the end.
FSFConditionalDebugTrace DebugTrace;
FSFConditionalAnswer Answer = SearchCondition->Evaluate({
MyCandidateForReceivingItem, // = TestObject
PlayerPawn, // = Instigator
&DebugTrace // optional debug infos
});
UE_LOG(LogMyGame, VeryVerbose, TEXT("%s"), *DebugTrace->ToString())
if (Answer.bBinaryAnswer)
{
// Give Item
}New Conditionals can easily be implemented by deriving a C++ or BP class from USFConditional.
In both languages, you need to override the EvaluateConditional method:
In both languages, you furthermore have the option to override CreateConfigurationDebugString,
in which you can describe the current configuration of your conditional to debug systems such as
the FSFConditionalDebugTrace.
Conditional evaluations may have additional requirements on the tested object and instigator passed in.
To express such runtime errors, conditionals may return a FSFConditionalAnswer with an error message.
The SFConditional plugin comes with a few predefined error answers. In C++, those can be found in
the SF::Conditional::Answer::Error namespace. For BP the function library exposes the same set
of errors in the SF|Conditional|Answer|Error category.
In C++ you additionally have the option to add data validation and child conditionals.
IsDataValid will be called automatically on all children in a conditional tree, if it's
called on the root and all parent conditionals in the tree implement GetImmediateChildren.
It's also wise to add TitleProperty=ConditionalTitlePropertyString as meta attribute for
child conditional properties, to get a nice title string per container entry.
Contents of ConditionalTitlePropertyString will be automagically computed for you!
/**
* Answers Yes if *any* sub-conditional gives true as binary answer, otherwise No.
*
* Note that the fuzzy answers of the sub-conditionals aren't considered in any way.
*/
UCLASS(DisplayName="LOGIC - Or")
class SFCONDITIONAL_API USFConditional_Logic_Or : public USFConditional
{
GENERATED_BODY()
protected:
// - USFConditional
virtual FSFConditionalAnswer EvaluateInternal_Implementation(const FSFConditionalEvaluationContext& EvaluationContext) override;
virtual FInt32Range GetAllowedChildrenNumRange_Implementation() const override;
virtual TArray<USFConditional*> GetImmediateChildren_Implementation() const override;
virtual FString CreateConfigurationDebugString_Implementation() const override;
// --
/** The conditionals to combine by OR. */
UPROPERTY(EditDefaultsOnly, Instanced, meta=(TitleProperty=ConditionalTitlePropertyString))
TArray<TObjectPtr<USFConditional>> Conditions = {};
};Because USF_Conditional_Logic_Or assigns TitleProperty=ConditionalTitlePropertyString as meta attribute
to its Conditions property, each child array entry in the editor gets a nice title.
Conditionals do have an override to draw debug visualization for themselves (see
USFConditional_Area_ScreenBox::VisualizeWithGameplayDebugger for an example), but the plugin itself doesn't come
with a debugger category, instead you have to build one for your own systems where you use conditionals.
The reason is that Conditionals don't have a global registry or anything a gameplay debugger could draw data from: Conditionals are meant to be used as a tool being a part in systems built on top. If those systems on top are able to expose their conditionals to the debugger, then the override can be easily used to visualize them.
For an example see the target search system implemented by Double Fine and also presented in the aforementioned blog post Behind The Code: Locked On Target.




