Blog / November 15, 2024 / 6 mins read / By Suneet Agrawal

Lambda in Unreal

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.

Comments