Async Blueprint Nodes (to fetch JSON data)

For my upcoming game WARPSQUAD, I was curious how easy it is to fetch data from a web service to be displayed in-game. The initial use case is a simple Message of the Day (MOTD) to be displayed in the main menu. Allowing for easy communication with players during playtests or (service) issues. You could use such web interfacing for posting in-game feedback too or whatever data you want to keep outside of the game executable to update on the fly.

This short tutorial will hit on two main subjects that you can use separately. First, how to set up async (latent) Blueprint Nodes in C++ that can be used for any number of things that may run over multiple frames such as web services (A built-in use case is async loading of assets) Secondly I’ll show a simple HTTP Request to “GET” JSON data from a web page.

Prepare your JSON Data

Here is an example JSON in the simplest format, a single key-value pair.

{
   "MOTD" : "Message of the day!\nYou could insert all sorts of markup and use UMG RichTextBox for detailed formatting."
}

This should be hosted somewhere on your web service such as “https://tomlooman.com/games/YourGame.json“. Use the same URL in-game to retrieve the data.

Prepare your Project

In your MyProject.build.cs file add the following modules: “HTTP”, “Json” to your PublicDependencyModuleNames

Create a new C++ Class derived from UBlueprintAsyncActionBase this allows us to easily create simple latent nodes.

For the HTTP request you’ll need the following #includes in your newly created .cpp file:

#include "Runtime/Online/HTTP/Public/HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"

The Code

The node requires a few elements to function, with Unreal converting it to the complex node automatically. The Activate() function performs the request and sets up the callback once our request has completed. The static function is the Blueprint node and instantiates a new Action that handles the logic. Finally, the event delegate is required to create the async output wire in the blueprint node along with the data pins (in the example below its FString MOTD and bool bSuccess that are both created as output data pins)

Header:

// Event that will be the 'Completed' exec wire in the blueprint node along with all parameters as output pins.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHttpRequestCompleted, const FString&, MOTD, bool, bSuccess);

UCLASS() // Change the _API to match your project
class MYPROJECT_API ULZAsyncAction_RequestHttpMessage : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

protected:

	void HandleRequestCompleted(FString ResponseString, bool bSuccess);

public:

	/** Execute the actual load */
	virtual void Activate() override;

	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", Category = "HTTP", WorldContext = "WorldContextObject"))
	static ULZAsyncAction_RequestHttpMessage* AsyncRequestHTTP(UObject* WorldContextObject, FString URL);

	UPROPERTY(BlueprintAssignable)
	FOnHttpRequestCompleted Completed;

	/* URL to send GET request to */
	FString URL;
};

Class:

void ULZAsyncAction_RequestHttpMessage::Activate()
{
	// Create HTTP Request
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HttpRequest = FHttpModule::Get().CreateRequest();
	HttpRequest->SetVerb("GET");
	HttpRequest->SetHeader("Content-Type", "application/json");
	HttpRequest->SetURL(URL);

	// Setup Async response
	HttpRequest->OnProcessRequestComplete().BindLambda([this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess)
		{
			FString ResponseString = "";
			if (bSuccess)
			{
				ResponseString = Response->GetContentAsString();
			}

			this->HandleRequestCompleted(ResponseString, bSuccess);
		});

	// Handle actual request
	HttpRequest->ProcessRequest();
}


void ULZAsyncAction_RequestHttpMessage::HandleRequestCompleted(FString ResponseString, bool bSuccess)
{
	FString OutString;
	if (bSuccess)
	{
		/* Deserialize object */
		TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());
		TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<>::Create(ResponseString);
		FJsonSerializer::Deserialize(JsonReader, JsonObject);

		// The simplest example parsing of the plain JSON.
		// Here you can expand to fetch your specific layout of values and objects and return
		// it via a UStruct or separate params in the Completed.Broadcast()
		if (!JsonObject->TryGetStringField("MOTD", OutString))
		{
			// While response may be successful, we failed to retrieve the string field
			bSuccess = false;
		}
	}


	Completed.Broadcast(OutString, bSuccess);
}


ULZAsyncAction_RequestHttpMessage* ULZAsyncAction_RequestHttpMessage::AsyncRequestHTTP(UObject* WorldContextObject, FString URL)
{
	// Create Action Instance for Blueprint System
	ULZAsyncAction_RequestHttpMessage* Action = NewObject<ULZAsyncAction_RequestHttpMessage>();
	Action->URL = URL;
	Action->RegisterWithGameInstance(WorldContextObject);

	return Action;
}

Blueprint Result:

Closing

That’s it! This short tutorial is a bit of a two-in-one as both concepts (latent node, and HTTP/JSON communication) are entirely separate and can be used in many different ways.

JSON is not Blueprint exposed by default in Unreal Engine, there are some (free) Plugins available (here and here) which can handle everything we just created and much more.

On a localization side note: If you want to localize the (MOTD) text you can provide multiple Keys in your JSON for all supported languages and use the active language in the game to provide to grab the specific language string from the JSON.

For more Unreal Engine Tutorials, follow me on Twitter and subscribe below!

8 Responses

  1. Hi Tom, Great article. One question, how can I send information in the http header? The service I am trying to connect to requires that an API-Key is provided in the header of the request:
    curl –request GET \
    –url ‘https://api.getinfo.com/info/info_address?option=full’ \
    –header ‘APIKey: ‘ \
    –header ‘Content-Type: application/json’

    • It should be made in the section where the http request is created (look at the comment: //Create http request). Add in the same way the content-type is specified but use the AppendToHeader function instead.

    • From the documentation: “Call to globally register this object with a game instance, it will not be destroyed until SetReadyToDestroy is called This allows having an action stay alive until SetReadyToDestroy is manually called, allowing it to be used inside loops or if the calling BP goes away”.

    • I’m not sure how to easily add multiple input execs. You can use K2Node derived classes to build custom BP nodes but that’s pretty involved to set up. (I believe there is *some* info floating around on how-to)

      To actually cancel the request is easily done with HttpRequest->CancelRequest();

Leave a comment on this post!