Skip to Content

Nunu SDK Utility Functions

The Nunu SDK provides three powerful utility classes to simplify writing Flayer functions. These classes are designed to enhance the functionality of your Flayer functions and make them more efficient.

All of them are available for both C++ and Blueprints.

Let’s take a closer look at each of them:

Flayer Promise Utils

These utility functions are designed to simplify the process of creating and managing promises in your Flayer functions. They provide a convenient way to handle asynchronous operations and ensure that your Flayer functions can return results in a clean and efficient manner.

For more information on what Flayer promises are, and when to use them, check out the Flayer Promises section.

Delayed Promise

The simplest form of promise - it resolves after a specified time delay. Perfect for operations that just need to wait.

FFlayerPromise DelayedPromise( UObject* WorldContextObject, float DelaySeconds, const FDynamicParams& Result = FDynamicParams() );

this example demonstrates how to create a delayed promise for reloading a weapon. The function takes a reload time and a logger as arguments, and returns a promise that resolves after the specified reload time.

UFUNCTION() FFlayerPromise ReloadWeapon(float ReloadTime, UPacketLogger* Logger) { // Start the reload animation if (APlayerCharacter* Character = GetPlayerCharacter()) { Character->PlayReloadAnimation(); Logger->LogToAI(TEXT("Starting weapon reload..."), ELogLevel::Info); } // Create result for when reload completes FDynamicParams ReloadResult; ReloadResult.Add(FDynamicParam(true)); // Success flag // Return promise that completes after reload time return UFlayerPromiseUtils::DelayedPromise( GetWorld(), ReloadTime, ReloadResult ); }

Conditional Promise

Waits for a specific condition to be met. Useful when you need to monitor state changes but don’t need to perform actions each tick.

FFlayerPromise ConditionalPromise( UObject* WorldContextObject, FStandardPromiseConditionDelegate Condition, UPacketLogger* Logger, const FDynamicParams& Result = FDynamicParams(), float MaxWaitTime = 0.0f, float TickInterval = 0.1f );

Here’s an example waiting for a door to finish opening:

UFUNCTION() FFlayerPromise WaitForDoorToOpen(ADoor* Door, UPacketLogger* Logger) { if (!Door) { Logger->LogToAI(TEXT("Invalid door reference"), ELogLevel::Error); return FFlayerPromise::CreateCompleted(false); } // Start opening the door Door->Open(); // Create condition delegate FStandardPromiseConditionDelegate Condition; Condition.BindLambda([Door, Logger](UPacketLogger* CondLogger) -> bool { if (!Door->IsValid()) { Logger->LogToAI(TEXT("Door was destroyed while waiting"), ELogLevel::Warning); return true; // End the promise if door is destroyed } float OpenAmount = Door->GetOpenAmount(); // Log progress occasionally static float LastLoggedAmount = 0.0f; if (FMath::Abs(OpenAmount - LastLoggedAmount) > 0.25f) { Logger->LogToAI( FString::Printf(TEXT("Door is %.0f%% open"), OpenAmount * 100.0f), ELogLevel::Info ); LastLoggedAmount = OpenAmount; } return Door->IsFullyOpen(); }); // Wait for door to finish opening return UFlayerPromiseUtils::ConditionalPromise( GetWorld(), Condition, Logger, FDynamicParams(), // Default result 5.0f, // Maximum 5 seconds wait 0.1f // Check every 0.1 seconds ); }

Ticking Promise

The most flexible promise type. Executes code every tick until completion. Perfect for operations that need continuous updates or complex state management.

FFlayerPromise TickingPromise( UObject* WorldContextObject, FStandardPromiseTickDelegate OnTick, UPacketLogger* Logger, const FDynamicParams& Result = FDynamicParams(), float MaxTickTime = 0.0f, float TickInterval = 0.1f );

Here’s a practical example of following another actor while maintaining distance:

UFUNCTION() FFlayerPromise FollowActor(AActor* TargetActor, float DesiredDistance, UPacketLogger* Logger) { if (!TargetActor) { Logger->LogToAI(TEXT("No target actor specified"), ELogLevel::Error); return FFlayerPromise::CreateCompleted(false); } // Create context to hold our operation state struct FFollowContext { AActor* Target; APlayerController* Controller; float Distance; float TimeElapsed = 0.0f; FVector LastTargetLocation; }; // Initialize context FFollowContext* Context = new FFollowContext(); Context->Target = TargetActor; Context->Controller = UGameplayStatics::GetPlayerController(GetWorld(), 0); Context->Distance = DesiredDistance; Context->LastTargetLocation = TargetActor->GetActorLocation(); // Create the ticking promise return UFlayerPromiseUtils::TickingPromise( GetWorld(), FStandardPromiseTickDelegate::CreateLambda( [Context](float DeltaTime, UPacketLogger* TickLogger) -> bool { Context->TimeElapsed += DeltaTime; // Validate our references if (!Context->Controller || !Context->Target || !Context->Controller->GetPawn()) { TickLogger->LogToAI(TEXT("Lost required references"), ELogLevel::Error); delete Context; return true; // End promise } // Get current positions FVector TargetLocation = Context->Target->GetActorLocation(); FVector PlayerLocation = Context->Controller->GetPawn()->GetActorLocation(); // Calculate movement needed float CurrentDistance = FVector::Distance(PlayerLocation, TargetLocation); FVector DirectionToTarget = (TargetLocation - PlayerLocation).GetSafeNormal(); // Determine if target has moved significantly bool TargetMoved = !TargetLocation.Equals( Context->LastTargetLocation, 1.0f // Tolerance ); // Log status periodically or when target moves static float TimeSinceLastLog = 0.0f; TimeSinceLastLog += DeltaTime; if (TimeSinceLastLog >= 1.0f || TargetMoved) { TickLogger->LogToAI( FString::Printf( TEXT("Following target. Current distance: %.1f, Desired: %.1f"), CurrentDistance, Context->Distance ), ELogLevel::Info ); TimeSinceLastLog = 0.0f; } // Update last known position Context->LastTargetLocation = TargetLocation; // Calculate desired position FVector DesiredPosition = TargetLocation - (DirectionToTarget * Context->Distance); // Move towards desired position UAIBlueprintHelperLibrary::SimpleMoveToLocation( Context->Controller, DesiredPosition ); // Continue until operation is cancelled return false; } ), Logger, FDynamicParams(), // Default result 0.0f, // No timeout 0.0f // Tick every frame ); }

Promise with Dynamic Result

Perfect for operations that need to generate or modify their result over time.

FFlayerPromise TickingPromiseWithResult( UObject* WorldContextObject, FStandardPromiseTickWithLoggerAndResultDelegate OnTickWithResult, UPacketLogger* Logger, const FDynamicParams& DefaultResult = FDynamicParams(), float MaxTickTime = 0.0f, float TickInterval = 0.1f );

Example of scanning nearby actors and returning the closest one:

UFUNCTION() FFlayerPromise ReloadWeapon(float ReloadTime, UPacketLogger* Logger) { // Start the reload animation if (APlayerCharacter* Character = GetPlayerCharacter()) { Character->PlayReloadAnimation(); Logger->LogToAI(TEXT("Starting weapon reload..."), ELogLevel::Info); } // Create result for when reload completes FDynamicParams ReloadResult; ReloadResult.Add(FDynamicParam(true)); // Success flag // Return promise that completes after reload time return UFlayerPromiseUtils::DelayedPromise( GetWorld(), ReloadTime, ReloadResult ); }

Flayer Function Utils

The FlayerFunctionUtils library provides a collection of helper functions that simplify common operations needed when writing Flayer functions. From vector manipulation to player state queries, these utilities help speed up development and reduce boilerplate code.

Time Utilities

The AI agent prefers to have time in milliseconds and integers, while Unreal Engine mostly uses floats and seconds. The utilities provide clean conversions between milliseconds and seconds.

// Convert from milliseconds to seconds float DelaySeconds = UFlayerFunctionUtils::MillisecondsToSeconds(1500); // Returns 1.5 // Convert from seconds to milliseconds int32 DelayMs = UFlayerFunctionUtils::SecondsToMilliseconds(1.5f); // Returns 1500

Vector Utilities

Since we can only send primitive types, vectors are sent as three separate floats. The utilities provide a clean way to convert and manipulate vectors.

// Create vectors from components FVector PosFromFloats = UFlayerFunctionUtils::MakeVectorFromFloats(100.0f, 200.0f, 300.0f); FVector PosFromInts = UFlayerFunctionUtils::MakeVectorFromInts(100, 200, 300); // Break vector into components float X, Y, Z; UFlayerFunctionUtils::BreakVectorToFloats(PosFromFloats, X, Y, Z); // Convert vector to string for logging or display FString LocationStr = UFlayerFunctionUtils::VectorToString(PlayerLocation); Logger->LogToAI(FString::Printf(TEXT("Player at: %s"), *LocationStr));

The library provides structured logging functions that ensure consistent formatting:

// Log vector with context UFlayerFunctionUtils::LogVector(Logger, TEXT("Player Position"), PlayerLocation); // Log rotator with context UFlayerFunctionUtils::LogRotator(Logger, TEXT("Camera Rotation"), CameraRotation);

World Interaction

The library provides convenient ways to interact with the game world and its actors:

// Get actor's location FVector Location = UFlayerFunctionUtils::GetActorLocation(SomeActor); // Get distance between actors float Distance = UFlayerFunctionUtils::GetDistanceBetweenActors(ActorA, ActorB); // Get direction to target FVector Direction = UFlayerFunctionUtils::GetDirectionToActor(SourceActor, TargetActor);

String operations

String manipulation utilities help with formatting and parsing:

// Join array of strings TArray<FString> Items = {"A", "B", "C"}; FString Combined = UFlayerFunctionUtils::JoinStrings(Items, TEXT(", ")); // "A, B, C" // Split string into array TArray<FString> Split = UFlayerFunctionUtils::SplitString(Combined, TEXT(", "));

Dynamic Param Utils

Dynamic parameters form the foundation of data exchange between your game and AI agents. When working with primitive types, this conversion is automatically handled for you. However, when dealing with Flayer promises, you need to create dynamic parameters manually. The DynamicParamUtils library provides a set of functions to simplify this process.

The most common primitive types are supported, including:

// Single-value collections FDynamicParams StringParams = UDynamicParamUtils::CreateStringDynamicParams("Message"); FDynamicParams BoolParams = UDynamicParamUtils::CreateBoolDynamicParams(true); FDynamicParams IntParams = UDynamicParamUtils::CreateIntDynamicParams(42); FDynamicParams FloatParams = UDynamicParamUtils::CreateFloatDynamicParams(3.14f);
Last updated on