Unreal + AWS + VR Part 2: Blueprints

Daniel Sanchez
UNC Blue Sky Innovations
6 min readApr 22, 2020

Introduction

The last post left you with an Unreal plugin that included parts of the AWS sdk, but there is still more work to be done if you want to expose the sdk to blueprints. Exposing this functionality to blueprints can make the sdks available for a wider range of developers and projects. To understand some of the implementation details, it is helpful to be somewhat familiar with exposing C++ to blueprints. For this blog post, I will be exposing one of the oldest features from AWS to blueprint, downloading a file from S3.

NOTE: The completed project can be found here

Using the AWS Documentation

The best place to find out what is available in each sdk is through the AWS provided C++ documentation

Setup

We will be using Unreal 4.22 on windows and starting from a plain C++ Third Person example. Although this example is not a VR project, the exact same process can be done on an Unreal project that is configured to deploy to the mobile Oculus headsets. There are a lot of good tutorials already made about how to get an Unreal project set up to deploy to Oculus devices. The only additional step that might be necessary is making sure you have a C++ project not a blueprint only project.

Step 1: Expose Our C++ class to blueprints

We need to create a way for our blueprints to get a reference to our C++ class so that it can use the functionality. I choose to do this by using a static function that returns a pointer to an instance of our C++ class. For out AWSS3Client it will look like:

We can call this function from any blueprint to get an instance of our C++ class.

Step 2: Get an instance of the AWS Client

For most the sdks, you can find most of the functions that you need in the client object for the sdk you are using, so for S3, it is this page. One of the first things to notice is the constructor for the AWS S3 client object. It contains a lot of things that do not need to be configured. For most cases, your AWS sdk client constructor should look like.

Aws::S3::S3Client* S3Client (const Aws::Auth::AWSCredentials &credentials, const Aws::Client::ClientConfiguration &clientConfiguration=Aws::Client::ClientConfiguration)

Our UAWSS3ClientObject will create a reference to the AWS S3 Client and and use it to make all of the calls to the S3 service, so lets create a private variable that will hold this reference in our AWSS3ClientObject.h. Also, lets update our CreateS3ClientObject function to take in the needed input to create the AWS client object. I have already created blueprint accessible versions of the Aws::Auth::AWSCredentials and Aws::Client:: ClientConfiguration objects in the AWSBase module. The new constructor function should have this signature and the implementation will be:

Note: When we link to a new part from the AWS sdk you have to make sure you add the proper #include in your header. For this it will be #include “aws/s3/S3Client.h”
Note: The AWSSerializer contains some helper functions for converting between AWS types and Unreal types.

Step 3: Write a function for downloading a file

From the documentation, we can see that the function to download an object has the signature:

virtual Model::GetObjectOutcome GetObject (const Model::GetObjectRequest &request) const

Looking at the documentation for Model::GetObjectRequest, we will need an S3 bucket and key value to identify which object to download. We can create a wrapper function around this that takes the information needed to construct the Model::GetObjectRequest and the location on the local file system to place the downloaded object.

Step 4: Implement it in blueprints

In your Unreal project, create a new actor class and add it to the 3rd person level. Then add this blueprint logic to the begin play node of the actor.

NOTE: You need to be super careful about committing this blueprint or any future ones to a public source control like GitHub because it has your AWS credentials!!!

Then change the parameters for what object you would like to download and give it a try!

Problems with this simple implementation

Although this demo meets the requirement of using AWS S3 to download an object, it still needs more work to be an efficient tool. The main problem is that our function is making a blocking call to download the object from S3, which means the Get Object blueprint node will not return until the object is downloaded and written to your file system. In my small demo this was not a problem, but it will quickly become a problem if you try to download a larger file as any logic after that Get Object call will be delayed. In the more general case of using any AWS service, you do not want to have your main game thread to be blocked on the AWS network calls as this can cause unpredictable delays and could affect the FPS of your game. Luckily, the AWS sdk provides asynchronous versions of most of the functions which we can pair with the Unreal’s delegate system to make efficient calls to AWS.

Step 5: Make the Get Object call Asynchronous

Most functions in the AWS sdks have async versions that handle offloading the network calls to another thread and calling a callback function when the results are available. The async version of the GetObject looks like:

virtual void GetObjectAsync (const Model::GetObjectRequest &request, const GetObjectResponseReceivedHandler &handler, const std::shared_ptr< const Aws::Client::AsyncCallerContext > &context=nullptr)

Like the blocking call, it takes a Model::GetObjectRequest object, but it also takes a GetObjectResponseReceivedHandler, which is a pointer to a static function with the following signature:

typedef std::function<void(const S3Client*, const Model::GetObjectRequest&, Model::GetObjectOutcome, const std::shared_ptr<const Aws::Client::AsyncCallerContext>&)>

We can create a static member function of our UAWSS3ClientObject class that will serve as the handler function for all of our async calls and add a blueprint exposed async function to make the actual call.

All async AWS calls have a Aws::Client::AsyncCallerContext object in the signature. This object allows you to attach a UUID to the call, which will be used to help identify the call when the results are returned in the callback function. For this call, a unique call to get an object can be viewed as the triple {bucket,key,destination}, so we concat these values with “:” as a delimiter to separate the values. We return the UUID so that the blueprint can use it later to identify returned objects.

With those functions implemented, you should be able to call the GetObjectAsync blueprint node and have your file downloaded without blocking the main game thread!

Unfortunately, there is still more that needs to be added because our blueprint now has no idea if the call was successful or when it is finished.

Step 6: Add in the Unreal Delegate System

The Unreal delegate system can be used to create asynchronous events in blueprints. We can create a delegate that blueprints can bind to by adding the following code to our UAWSS3ClientObject header before our class declaration.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FObjectReturned, FS3ObjectResult, result);

To help with passing the information back in a structured form, we created a Ustruct that represents the returned data.

Then we create a public instance of this delegate in our class declaration:

In our blueprint, we can now bind events on this delegate:

Now all we have to do is make a broadcast call on our delegate so that events that are bound to it are notified of the returned call from AWS, but there once again is another problem. The delegate will be a member of an instance of a UAWSS3ClientObject, but UAWSS3ClientObject::GetObjectAsync_Handler is a static function so it does not have access to any instance variables. To get around this, we will keep a static TMap of the UUID to the delegate it needs to call, which looks like:

The final blueprint will now look like this:

Final Thoughts

We hope that these blog posts have been helpful in demonstrating how to integrate AWS into an Unreal project. This is by no means a complete project as there is still a lot of work that can be done to make the implementations more stable like proper error handling. If you have any issues with the project feel free to leave comments here or raise issues on the GitHub repo and we will try our best to solve them. We want to keep building on the plugin generator and implementations of sdks, so you are encouraged to expand what is given here and make pull requests to the repos. Any direct questions, comments, or concerns can be directed to Arturo97@live.unc.edu. We look forward to seeing the cool Unreal experiences created with AWS!

--

--