In this section we'll show an example showing how to send data from a Blueprint to the UI. The first part of the example, will show you how to trigger a simple event from a Blueprint. The second one expands it with exporting whole game objects to the UI.
There are two ways of triggering JavaScript events from a blueprint.
We'll start with the easier one first. It encapsulates all the required logic that you would otherwise have to do yourself with multiple blueprint nodes.
Trigger event
. Spawn a Trigger JavaScript Event
node from the Hummingbird
category. Hummingbird Component
Pin. You need to connect the component that will trigger the event.Event Name
Pin. This will be the name of the event that's going to be triggered in JavaScript.UCLASS
objects.Trigger JavaScript Event
node after you have received the HBScriptingReady
event.Here's how the final blueprint looks like:
The second ways is more verbose and explicit. We'll examine it in the context of the sample game:
Begin Play
EventGet Player Controller
node.Get HUD
Node from the PlayerController
Get HUD
node, drag a pin and add a Cast to HummingbirdFPSHUD
Node.HummingbirdFPSHUD
node, drag a pin and get its Hummingbird HUD
property.Hummingbird HUD
object and select Assign HB Scripting Ready
. The HBScriptingReady_Event
event will be triggered when the page is ready to receive events. Note that you MUST add call to this event manually to your JavaScript code in the UI AFTER all your initial engine.on
event subscriptions.Hummingbird HUD
pin and create a Create JSEvent
node. This node produces the object that will represent our event.Assign to HBScriptingReady
with Create JS Event
. This means that as soon as the page is ready to receive events, we'll send this one.Return value
pin of the Create JS Event
node and create a Sequence
node.Then 0
of the Sequence
node, and create Add String
node. The JS event object that we just created can carry an arbitrary amount of arguments that will be available to the JavaScript of the page (each parameter must be added to the event with the appropriate Add XXXX
node before the event is triggered; the order by which arguments are added to the event will be the order the JavaScript code receives them).Create JSEvent
with the target of the Add String
node.Hummingbird HUD
pin and create a Trigger JSEvent
node. This is the node that will effectively execute the event and send it to JavaScript. It must happen after all arguments have been added.Then 0
in the Sequence
node, drag a pin and create Add String
node.Then 1
to the EventData
pin of Trigger JSEvent
.This guide assumes you have completed the first part of the sample, creating and sending a simple JS event, steps 1 to 13.
We'll make available the whole PlayerCharacter
object to the UI and we'll show on-screen the player name, the max health she can have and the max ammo she can carry.
In the hud.html page you can see that there is a playerInfo
element that contains the player data we want to show. We'll populate this data from the game through Blueprints.
Get Player Character
node.Return value
pin of the Create JS Event
node and create a Add Object
node. The JS event object that we just created can carry an arbitrary amount of arguments that will be available to the JavaScript of the page. Each parameter must be added to the event with the appropriate Add XXXX
node before the event is triggered. The order by which arguments are added to the event will be the order the JavaScript code receives them.Get Player Character
return value to the Object
pin of the Add Object
. This means that our event will send the whole player character object to JavaScript and we'll be able to use it's properties in the UI.Hummingbird HUD
pin and create a Trigger JS Event
node. This is the node that will effectively execute the event and send it to JavaScript. It must happen after all arguments have been added.EventData
pin.Name
property) to SetPlayerState
.Then 0
pin of the sequence to the Add Object
node and the Then 1
to the Trigger JS event
node.The screenshot below shows the complete Blueprint.
As soon as the game starts and the HUD loads, the SetPlayerStats
event will be triggered and the corresponding JavaScript code that populates the Statistics
fields in the UI will execute.
In JavaScript we've added a handler for the SetPlayerStats
event (see HummingbirdFPS/Content/Resources/uiresources/MainUI.html).
engine.on('SetPlayerStats', function (character) { $("#playerName").html(character.PlayerName); $("#playerMaxHealth").html(character.MaxHealth); $("#playerMaxAmmo").html(character.MaxAmmo); $("#playerInfo").css("display", "initial"); }); // IMPORTANT: Signal the game we are redy to receive events // We have setup all JS event listeners (the 'engine.on' calls) engine.call("HBScriptingReady");
The character
variable contains all the properties of the APlayerCharacter
actor in the game. Now we can use them to populate the Statistics
of our UI.
UObjects
, only their primitive type UPROPERTY
-tagged fields are exported. UObjects
contained in exported UObjects
are not recursively exported because they might contain circular dependencies and cause memory outages.The object in JavaScript is a copy of the original one, so changing directly its properties won't affect the object in the game. You can however call events back in the game with parameters and use this mechanism to update logic in the game from the UI. This is explained in the next section.
Objects sent to JavaScript are copies of the ones in the game. Therefore, sending smaller objects less often is preferable as it incurs less resource usage.
As a general note the best way to structure the scripting is to create blueprint functions for every interaction and call them when needed from the master Blueprint. This makes the master Event Graph much less cluttered and more readable.
Hummingbird provides a very powerful binding framework that can be used to connect the C++ logic of the game with the UI JavaScript code and vice-versa.
To trigger events from C++ to JavaScript you can use the TriggerEvent
method of the View.
void AHummingbirdFPSCharacter::OnTabReleased() { cohtml::View* View = GetHummingbirdView(); if (View) { View->TriggerEvent("ToggleMenu"); } }
Any parameters you pass to it will be also sent to the JavaScript code. In JavaScript you must define which function should be called when an event is triggered from C++. The CoherentHummingbird.js file exposes a special JavaScript object named engine
that provides the glue and connections between the events in C++ and JS. You must use that object to define all your event handlers. The on
method will bind an event to a JS handler.
engine.on('ToggleMenu', toggleMenu);
In this case as soon as C++ calls TriggerEvent("ToggleMenu")
, the toggleMenu
function will be executed in JavaScript.
Triggering events from JavaScript to C++ is analogous. First, in C++ you need to define which method (or UE4 delegate) will handle a specific event triggered from JS.
Defining C++ handlers must happen after the View has triggered the ReadyForBindings
event. If you want to handle this event, you must subscribe to it and in its handler you should declare all your bindings. In the AHummingbirdFPSHUD
Actor in the sample game this is done with the following line:
HummingbirdHUD->ReadyForBindings.AddDynamic(this, &AHummingbirdFPSHUD::BindUI);
Now, as soon as the View has been loaded, it will invoke the BindUI
method where you can declare all the bindings for the View.
The BindUI
method is straight-forward:
void AHummingbirdFPSHUD::BindUI(int32 frameid, const FString& path, bool isMain) { HummingbirdHUD->GetView()->BindCall("CallFromJavaScript", cohtml::MakeHandler(&CalledFromJSSampleDelegate, &(FCalledFromJSSample::ExecuteIfBound))); HummingbirdHUD->GetView()->BindCall("CalledFromJSString", cohtml::MakeHandler(this, &AHummingbirdFPSHUD::CalledFromJSStringHandler)); HummingbirdHUD->GetView()->RegisterForEvent( "CalledFromJSUStruct", cohtml::MakeHandler(this, &AHummingbirdFPSHUD::CalledFromJSUStructHandler)); }
Essentially we say: "When JavaScript fires the `CallFromJavaScript`event, in C++ you must execute
`CalledFromJSSampleDelegate` delegate". The same applies for the second binding but in that case it'll call the CalledFromJSStringHandler
method of the class.
In JavaScript you just have to use:
engine.call("CallFromJavaScript", 123); engine.call("CalledFromJSString", "Hello from JavaScript!"); var ustructData = { ... }; engine.trigger("CalledFromJSUStruct", ustructData);
Please note that you can pass arguments from JS to C++ again but the handler signatures must coincide with the arguments passed, otherwise an error is generated.
In the sample game the handlers for those methods are very simple:
void AHummingbirdFPSHUD::CalledFromJSHandler(int32 number) { UE_LOG(LogScript, Log, TEXT("UE4 Delegate called from JavaScript")); } void AHummingbirdFPSHUD::CalledFromJSStringHandler(const FString& str) { UE_LOG(LogScript, Log, TEXT("String received from JS: %s"), *str); }
We just log that we were called from JS, so that we know everything works fine. Hummingbird supports passing parameters to/from JS for all primitive types, STL containers as well as UE4's strings (FString
, FText
, FName
), containers (TMap
, TArray
) and colors & vectors. In addition, any type that UE4 knows about i.e. USTRUCT
s and UCLASS
s can be automatically bound. In order to use these types you must include the relevant headers available in the cohtml/Binding and HummingbirdPlugin/Public folders.
For example to export unreal types, #include "HummingbirdUTypeBinder.h"
and the binding will be available. Please note that nested types are exported by default:
USTRUCT() struct FBar { int32 Value; }; USTRUCT() struct FFoo { FString String; // Will be exported; strings are considered primitive types TArray<int32> SomeValues; // Will be exported; containers of primitive types are exported. FBar Bar; // Will be exported by default as it's a nested type. };
UObject
s are currently not bound recursively as their sheer size makes recursive binding unusable for performance reasons (a recursive binding to an AActor
for example would also need to bind the entire ULevel
as the actor holds a pointer to it).Hummingbird also has support for binding user defined types that don't fit the categories above. For a detailed guide on this topic, please consult the native Hummingbird C++ documentation.