Lambda functions, or lambdas, are an essential tool in modern C++ programming and have powerful applications in Unreal Engine. They offer a concise way to define small, inline functions and are ideal for simplifying code, especially when working with delegates, multi-threaded tasks, or passing functions as parameters.
In this blog, we’ll explore what lambdas are, how they work in Unreal Engine, and practical examples for common use cases.
What is a Lambda?
A lambda is an anonymous function defined directly within code, often as an inline expression. Lambdas are beneficial when the function logic is simple and doesn’t need to be reused, as they reduce code clutter by avoiding the need for separate function declarations and definitions.
In Unreal Engine, lambdas are particularly useful for:
- Event handling with delegates
- Multithreading with Async tasks
- Writing concise, inline code for loops or algorithms
A lambda function in C++ has the following basic syntax:
[/* capture */](/* parameters */) -> /* return type */ { /* function body */ };
- Capture: Specifies which variables from the enclosing scope are available within the lambda.
- Parameters: The parameters that the lambda accepts.
- Return Type: Optional; usually inferred automatically.
- Function Body: The code that runs when the lambda is executed.
1. Using Lambdas with Single-cast Delegates
Single-cast delegates in Unreal Engine allow you to bind a function to an event that can only have one listener. Lambdas are a perfect fit for these since you can easily define small, specific responses directly inline without creating additional functions.
Example: Binding a Lambda to a Delegate
// Declare a delegate
DECLARE_DELEGATE(FOnJumpPressed);
class AMyCharacter : public ACharacter
{
public:
// Delegate instance
FOnJumpPressed OnJumpPressedDelegate;
void Jump()
{
OnJumpPressedDelegate.ExecuteIfBound();
}
};
AMyCharacter* Character = GetMyCharacter();
// Bind a lambda to the delegate
Character->OnJumpPressedDelegate.BindLambda([]()
{
UE_LOG(LogTemp, Warning, TEXT("Player pressed jump!"));
});
Character->Jump(); // This will log "Player pressed jump!"
In this example, we define a Jump
function that triggers the OnJumpPressedDelegate
. By binding a lambda to the delegate, we log a message whenever the jump function is called.
2. Lambdas with Parameters
Lambdas can accept parameters just like regular functions. This is especially useful for delegates that pass data to their listeners, such as when passing player input values or state changes.
Example: Binding a Lambda with Parameters
// Declare a delegate with parameters
DECLARE_DELEGATE_OneParam(FOnHealthChanged, float);
class AMyCharacter : public ACharacter
{
public:
// Delegate instance
FOnHealthChanged OnHealthChangedDelegate;
void TakeDamage(float DamageAmount)
{
Health -= DamageAmount;
OnHealthChangedDelegate.ExecuteIfBound(Health); // Pass health as parameter
}
private:
float Health = 100.0f;
};
AMyCharacter* Character = GetMyCharacter();
// Bind a lambda that accepts the current health as a parameter
Character->OnHealthChangedDelegate.BindLambda([](float CurrentHealth)
{
UE_LOG(LogTemp, Warning, TEXT("Health changed! New Health: %f"), CurrentHealth);
});
Character->TakeDamage(20.0f); // This will log the new health value
Here, OnHealthChangedDelegate
is triggered whenever the character’s health changes. We bind a lambda that takes the current health as a parameter and logs it, making it easy to track health changes in real-time.
3. Using Lambdas for Multithreading with Async
The Async
function in Unreal Engine simplifies running tasks on a separate thread, which is useful for tasks that could block the game thread, like heavy computations or network requests. Lambdas make it easy to define these tasks inline.
Example: Running a Heavy Computation with Async and a Lambda
// Perform a heavy computation asynchronously
Async(EAsyncExecution::Thread, []()
{
int32 Result = 0;
for (int32 i = 0; i < 1000000; ++i)
{
Result += i; // Simulate heavy computation
}
UE_LOG(LogTemp, Warning, TEXT("Computation finished with result: %d"), Result);
});
In this example, we execute a lambda on a separate thread to handle a heavy computation, which prevents it from blocking the game thread. Using Async
with a lambda keeps the code compact and readable.
4. Using Lambdas in Timers
Unreal Engine’s GetWorldTimerManager().SetTimer
function allows you to set up delayed events or recurring timers. Lambdas are convenient here, as they allow you to define the callback function directly within the timer call, making the code concise and keeping related logic in one place.
Example: Setting Up a Timer with a Lambda
FTimerHandle TimerHandle;
GetWorldTimerManager().SetTimer(TimerHandle, []()
{
UE_LOG(LogTemp, Warning, TEXT("Timer tick!"));
}, 1.0f, true); // Executes every second
This code sets up a timer to log Timer tick!
every second. Using a lambda for the callback keeps the timer setup code clean and concise.
5. Capturing Variables in Lambdas
Capturing variables in a lambda means allowing it to access variables from the surrounding scope. In Unreal, this can be handy when you need to use class members or external variables within the lambda body.
There are three common capture modes:
[=]
: Captures variables by value (a copy).[&]
: Captures variables by reference (allows modification).[this]
: Captures the current object by reference (used often in Unreal for class member access).
Example: Capturing Variables in a Lambda
float PlayerHealth = 100.0f;
float Damage = 25.0f;
// Lambda that captures PlayerHealth by reference and Damage by value
auto ApplyDamage = [&, Damage]()
{
PlayerHealth -= Damage;
UE_LOG(LogTemp, Warning, TEXT("Player Health after damage: %f"), PlayerHealth);
};
// Call the lambda
ApplyDamage(); // Outputs updated health
In this example, PlayerHealth
is captured by reference (using [&]
), allowing the lambda to modify it, while Damage
is captured by value, creating a local copy of the original variable.
6. Using Lambdas with TArray::Sort
Lambdas are often used with Unreal’s array functions like Sort
, FilterByPredicate
, etc., to quickly define sorting or filtering behavior inline.
Example: Sorting an Array of Integers with a Lambda
TArray<int32> Scores = { 78, 95, 60, 89, 72 };
// Sort scores in descending order
Scores.Sort([](int32 A, int32 B)
{
return A > B; // Sort descending
});
for (int32 Score : Scores)
{
UE_LOG(LogTemp, Warning, TEXT("Score: %d"), Score);
}
This lambda sorts an array of scores in descending order. By defining the sorting criteria directly in the lambda, you keep the code concise and focused.
Summary Table of Lambda Use Cases in Unreal Engine
Use Case | Example Code Snippet | Description |
---|---|---|
Single-cast Delegate Binding | Delegate.BindLambda([](){ /*...*/ }); |
Inline event handling for specific events |
Delegate with Parameters | Delegate.BindLambda([](float Value){ /*...*/ }); |
Passes parameters to inline delegate actions |
Async Task Execution | Async(EAsyncExecution::Thread, [](){ /*...*/ }); |
Runs heavy tasks on separate threads |
Timer Callback | SetTimer(TimerHandle, [](){ /*...*/ }, 1.0f, true); |
Inline timer callbacks |
Capture by Reference/Value | auto Lambda = [&, Var](){ /*...*/ }; |
Access outer scope variables in lambda |
Array Sorting | Array.Sort([](Type A, Type B) { return A < B; }); |
Custom sort directly within Sort function |
Conclusion
Lambdas are versatile, anonymous functions that add flexibility and conciseness to Unreal Engine code. By using them with delegates, timers, async tasks, and sorting functions, you can streamline code and keep related functionality localized to the place where it’s needed. Whether you’re handling input events, managing game state, or creating efficient multithreaded tasks, lambdas can help you keep your Unreal Engine codebase efficient and readable.