Unreal Commands & Events Guide
This guide was written as a quick reference step by step guide for setting up a simple Unreal command/event. For more in depth information on each step please check the documentation at https://www.genvidtech.com/for-developers/sdk-latest/
Stop Command (C++)
Create a C++ class that inherits from GameState if you have not already
Create a blueprint object of this GameState
Set the BP game state as the default game state in your game mode
We need to reparent the clock BP to a C++ class. Create a C++ class of type Actor for your clock. Then reparent the clock BP to this class.
In your Genvid Commands C++ class create the following functions:
Override “BeginPlay”
“StopCommand” which returns a FGenvidCommand and takes no parameters
“OnCommandStop” which returns void and takes two parameters - “const FString& CommandID” and “const FString& FullCommand”
In “BeginPlay” add the following code:
Commands.Add(StopCommand()); Super::BeginPlay();
We call the function we created called “StopCommand” and add the result to an existing property in the parent class called Commands. Then we call the parent BeginPlay function
In “StopCommand” add the following code:
FGenvidCommand Stop; Stop.Replicated = true; Stop.Name = "Stop"; Stop.OnGenvidCommandDelegate.BindUFunction(this, "OnCommandStop"); return Stop;
We create a FGenvidCommand and set some values on it before binding to the “OnCommandStop” function we created
The name will be the name of the command. See the documentation for more info on “replicated”
In “OnCommandStop” add the following code:
AMyGameState* GameState = GetWorld()->GetGameState<AMyGameState>(); GameState->MulticastConfirmCommandStop(FullCommand);
AMyGameState is whatever the game state you created is called
The function we are calling does not exist yet. We will create it next
In your newly created Game State class create a function called “MultiCastConfirmCommandStop” with a return value of void and an input parmeter of “const FString& FullCommand”. Additionally give it a UFUNCTION tag with “NetMulticast” and “Reliable”.
See the documentation for what these tags are doing
Because of the nature of RPC functions when actually implementing the function your implementation will be named “MulticastConfirmCommandStop_Implementation”. Add the following code to the implementation:
UMyGameInstance* GameInstance = Cast<UMyGameInstance>(GetGameInstance()); GameInstance->CommandStop();
Normally this is where you would check to make sure a valid command is being sent and also that it was sent from a valid location. But since this is just a test running locally we skip that and call a function that will take care of the actual command logic. This function will be created next
In your Game Instance class create a function called “CommandStop”. It takes no parameters and has a return type of void. Add the following code:
TArray<AActor*> ClockActorArray; UGameplayStatics::GetAllActorsOfClass(GetWorld(), AMyClock::StaticClass(), ClockActorArray); if (ClockActorArray.Num()) { Cast<AMyClock>(ClockActorArray[0])->StopClock = true; }
AMyClock is whatever C++ class you created for your clock
We look for any clocks in the level (there should be only one) then set a property to true. We will create this property next
In your clock C++ class create a boolean called “StopClock”. Make it a Blueprint readable UPROPERTY.
In your clock blueprint - open up the event graph and go to your tick. Add a check to stop clock before doing anything in the tick. If it’s true then don’t do anything. If it’s false then continue as normal
Pulling this all together - we bind a function to a “Stop” command. That function eventually finds its way to the clock actor and flips a bool that causes it to stop ticking
Stop Command (Web)
In your web/config folder open up the web.hcl and make sure the “admin” link templates all end in “/admin”. Then in your web/public folder create an “admin.html” file.
Add the following code to the new html file:
<!doctype html> <html> <head> <title>Genvid Overlay</title> <link rel="stylesheet" href="style.css"> <script src="genvid.umd.js"></script> <script src="genvid-math.umd.js"></script> <script src="overlay-admin.js"></script> </head> <body style="background:black"> <div id="video_player"></div> <div id="overlay"> <tr> <td><button class='commandButton' id='admin_stop_button'>STOP</button></td> </tr> </div> </body> </html>
We are creating a button with the text “STOP” on the admin link
We reference a new .js file named “overlay-admin.js” file. We will create that next
In the public folder create a file named “overlay-admin.js”. This will be the javascript file that will back the admin version of the steam
Add the following code to the new js file:
class AdminController { constructor(videoPlayerId) { this.videoPlayerId = videoPlayerId; } start() { fetch("/api/public/channels/join", { method: "POST", }) .then((data) => data.json()) .then((res) => this.onChannelJoin(res)) .catch((error) => genvid.error(`Can't get the stream info: ${error}`)); } onChannelJoin(joinRep) { this.client = genvid.createGenvidClient( joinRep.info, joinRep.uri, joinRep.token, this.videoPlayerId ); let StopButton = document.getElementById( "admin_stop_button" ); StopButton.addEventListener( "click", () => { this.stopClock(); }, false ); this.client.start(); } sendCommands(bodyCommands, successMessage, errorMessage) { fetch("/api/admin/commands/game", { method: "POST", body: JSON.stringify(bodyCommands), headers: { "Content-Type": "application/json", }, }) } stopClock(){ const commands = { id: "Stop", value: "Clock:Stop", }; const successMessage = "Command sent."; const errorMessage = "Stop Clock Error"; this.sendCommands(commands, successMessage, errorMessage); } } let admin = new AdminController("video_player"); admin.start();
Notice this file is a bit different then the last js file. This version gives us more control over the controller class. We will be updating the other js file in a similar fashion later
We bind a function that sends a command with the id “Stop” to a button click. Recall we named our command “Stop” in the project. This is how they connect
Go through the steps of setting up the local cluster, package the project (or play in editor), and start up the services
Click on the admin link. The username and password will both be “admin”
Click on the stop command. The clock should immediately stop ticking in the project and eventually stop on stream

Color Change Event (C++)
In your Genvid Events C++ class create the following functions:
Override “BeginPlay”
“ColorChangeEvent” which takes no parameters and returns a “FGenvidEvent”
“OnEventColorChange” which takes two parameters - “const FString& eventName” and “const FGenvidEventSummary& summary” with a return of void
In “BeginPlay” add the following code:
Events.Add(ColorChangeEvent()); Super::BeginPlay();
We add the return value from the “ColorChangeEvent” to a property that already exists on the parent Genvid Events class then run the parent begin play.
In “ColorChangeEvent” add the following code:
FGenvidEvent ColorChange; ColorChange.Replicated = true; ColorChange.Name = "colorChange"; ColorChange.OnGenvidEventDelegate.BindUFunction(this, "OnEventColorChange"); return ColorChange;
Create the Event and name it “colorChange”. Then bind it to the function “OnEventColorChange”
See the documentation for more information on “Replicated”
In “OnEventColorChange” add the following code:
AMyGameState* GameState = GetWorld()->GetGameState<AMyGameState>(); GameState->MulticastConfirmEventColorChange(summary);
AMyGameState is your game state C++ class
The function we are calling does not exist yet. We will create it next
In your game state class create the function “MulticastConfirmEventColorChange” with a return value of void and a single parameter of “const FGenvidEventSummary& summary”. Mark it with the UFUNCTION tags “NetMulticast” and “Reliable”
Since this is an RPC call the implementation of the function will be called “MulticastConfirmEventColorChange_Implementation”. Add the following code to it:
int PinkEvents = 0; int BlackEvents = 0; for (FGenvidEventResult Result : summary.results) { if (Result.key.fields[0] == "pink") { for (FGenvidEventValue Values : Result.values) { if (Values.reduce == EGenvidReduceOp::Count) { PinkEvents += Values.value; } } } else if (Result.key.fields[0] == "black") { for (FGenvidEventValue Values : Result.values) { if (Values.reduce == EGenvidReduceOp::Count) { BlackEvents += Values.value; } } } } bool BlackColor = (BlackEvents > PinkEvents); UMyGameInstance* GameInstance = Cast<UMyGameInstance>(GetGameInstance()); GameInstance->EventColorChange(BlackColor);
Slightly more complicated than a command as these are map-reduced as they are scalable to mass amounts of people
We check to see if the key of the “colorChange” event is “black” or “pink”. Then we add it’s count to a counter
If Black is larger we set a boolean to true. Otherwise it’s false. Then we pass that boolean to a function we will create next
In the GameInstance class create “EventColorChange” with a return of void and a parameter of “bool Black”. Add the following code:
TArray<AActor*> ClockActorArray; UGameplayStatics::GetAllActorsOfClass(GetWorld(), AMyClock::StaticClass(), ClockActorArray); if (ClockActorArray.Num()) { Cast<AMyClock>(ClockActorArray[0])->ColorChange(Black); }
Here we look for our clock and call the function “ColorChange” with the bool passed in. We will create this function next
In the C++ class that the clock is parented to create a function named “ColorChange” with a return of void and a parameter of “bool Black”
Give it a UFUNCTION tag of “BlueprintImplementableEvent”
In the unreal editor create a new material. Make it pink.
In the clock blueprint implement “ColorChange”. Add the following blueprint code:
If the clock was imported you will only need to override the ColorChange event and attach to the existing blueprint code
Color Change Event (Web)
Go to your web/public folder
Open your index.html file. Add the following code after the existing streams, notifications, and annotations:
<div> <button class='eventButton' id='pink_button'>Pink</button> </div> <div> <button class='eventButton' id='black_button'>Black</button> </div>
We Declare two buttons. One with the text “Pink” the other with the text “Black”
Open your overlay.js file. Replace the contents with the following code:
var genvidClient; class WebController { constructor(videoPlayerId) { this.videoPlayerId = videoPlayerId; } start() { fetch("/api/public/channels/join", { method: "POST", }) .then(function (data) { return data.json() }) .then((res) => this.onChannelJoin(res)) .catch((error) => genvid.error(`Can't get the stream info: ${error}`)); } onChannelJoin(joinRep) { this.genvidClient = genvid.createGenvidClient( joinRep.info, joinRep.uri, joinRep.token, this.videoPlayerId ); this.genvidClient.onStreamsReceived(function (dataStreams) { for (let stream of [...dataStreams.streams, ...dataStreams.annotations]) { for (let frame of stream.frames) { try { frame.user = JSON.parse(frame.data); } catch (e) { console.log(e, frame.data); } } } }); this.genvidClient.onVideoPlayerReady(function () { console.log("Video Player is Ready"); }); this.genvidClient.onDraw(function (frame) { let gameDataFrame = frame.streams["ClockTime"]; if (gameDataFrame && gameDataFrame.user) { update(gameDataFrame.user); } if ("ClockAnnotation" in frame.annotations && frame.annotations["ClockAnnotation"].length){ for (let gameAnnotation of frame.annotations["ClockAnnotation"]) { if (gameAnnotation && gameAnnotation.user) { annotationUpdate(gameAnnotation.user); } } } }); this.genvidClient.onNotificationsReceived(function (notifications) { for (let notification of notifications.notifications) { try { notification.user = JSON.parse(notification.data); notificationUpdate(notification.user); } catch (e) { console.log(e, notification); } } }); let PinkButton = document.getElementById( "pink_button" ); PinkButton.addEventListener( "click", () => { this.onColorChange("pink"); }, false ); let BlackButton = document.getElementById( "black_button" ); BlackButton.addEventListener( "click", () => { this.onColorChange("black"); }, false ); this.genvidClient.start(); } onColorChange(color) { this.genvidClient.sendEventObject({ colorChange: color, }); } } function update(data) { document.getElementById("hour").innerHTML = "Hour: " + Math.round(data.hour); document.getElementById("minute").innerHTML = "Minute: " + Math.round(data.minute); document.getElementById("second").innerHTML = "Second: " + Math.round(data.second); } function notificationUpdate(data) { document.getElementById("notification").innerHTML = "Notification: " + Math.round(data.second); } function annotationUpdate(data) { document.getElementById("annotation").innerHTML = "Annotation: " + Math.round(data.second); } let web = new WebController("video_player"); web.start();
Don’t be intimidated by the changes. Most of it has remained the same - just restructured in a way that gives us more control.
Note there is some new code related to events
We get the pink button and black button and bind to a function named “onColorChanged” but pass a different string value
This is the string value we look for in MulticastConfirmEventColorChange_Implementation
onColorChange sends the string with the key “colorChange”. Recall we named our event in the C++ “colorChange” as well.
Go to your web/config folder and open events.json. Replace the contents with the following code:
{ "version": "1.7.0", "event": { "game": { "maps": [ { "id": "colorChange", "source": "userinput", "where": {"key": ["colorChange"], "name": "<color>", "type": "string"}, "key": ["colorChange", "<color>"], "value": 1 } ], "reductions": [ { "id": "colorChange", "where": {"key": ["colorChange", "<color>"]}, "key": ["<color>"], "value": ["$count"], "period": 250 } ] } } }
Update your “version” number if necessary
This sets up the map reduce of the “colorChange” event
See the documentation for more info on map reduce. The important part is multiple instances of “pink” and “black” are put together and counted
Copy the events.json file to your config folder that is one step above your project
This is where “game.hcl” and “sample.hcl” live
Both the web and client need to know about the event format
Go through the steps of setting up the local cluster, package the project (or play in editor), and start up the services
Click on the non-admin link
Click on the pink or black. The clock hands will shift color depending on what is pressed. If multiple sessions were connected and clicking at the same time, whatever had the most clicks per map-reduce would win out.

Events Improvements (C++)
The event is functional. But we can do better.
We will send notifications updating the current bid count of each event and instead only update when the clock strikes a 15 second interval
Open your Game State C++ file
Replace your “MulticastConfirmEventColorChange_Implementation” with the following code:
for (FGenvidEventResult Result : summary.results) { if (Result.key.fields[0] == "pink") { for (FGenvidEventValue Values : Result.values) { if (Values.reduce == EGenvidReduceOp::Count) { PinkEvents += Values.value; } } } else if (Result.key.fields[0] == "black") { for (FGenvidEventValue Values : Result.values) { if (Values.reduce == EGenvidReduceOp::Count) { BlackEvents += Values.value; } } } }
We no longer reset the pink and black vote count
We no longer call the instance function to update the color of the clock hands
PinkEvents and BlackEvents are now class properties
In your header file define “PinkEvents” and “BlackEvents” as public class properties of type int
Create a new function called “ColorChange” that takes no inputs and returns void. Copy the following code into it:
if (BlackEvents == PinkEvents) { return; } bool BlackColor = (BlackEvents > PinkEvents); UMyGameInstance* GameInstance = Cast<UMyGameInstance>(GetGameInstance()); GameInstance->EventColorChange(BlackColor);
When called this function does nothing if the values are the same. Otherwise it updates to the greater of the two values
In your Genvid Stream C++ class go to your “TickComponent”. Change the if statement that flips the notification and annotation booleans to the following:
if (PreviousUsedSecond != CurrentSecond && CurrentSecond % 15 == 0) { PreviousUsedSecond = CurrentSecond; ShouldSendNotification = true; ShouldSendAnnotation = true; GetWorld()->GetGameState<AMyGameState>()->ColorChange(); }
We now call the new function we just created on the game state to update the colors of the hands every 15 seconds
Now we will add a notification stream for the bid numbers
Create “ClockColorBidNotification” that takes in no parameters and returns an “FGenvidStream”. Add the following code to it:
FGenvidStream ColorBidNotification; ColorBidNotification.Name = "ColorBidNotification"; ColorBidNotification.OnGenvidStreamDelegate.BindUFunction(this, "OnSubmitColorBidNotification"); ColorBidNotification.Framerate = 30; return ColorBidNotification;
We create a stream called “ColorBidNotification”
We bind it to a function “OnSubmitColorBidNotification” which we will create later
In “BeginPlay” add the new stream the same way the other streams are added “Streams.Add(ClockColorBidNotification());”
In the header file for your Genvid Streams create the following struct that will hold the data for this new notification:
USTRUCT(BlueprintType) struct FColorBids { GENERATED_BODY() public: UPROPERTY(BlueprintReadWrite, EditAnywhere) int32 Pink = 0; UPROPERTY(BlueprintReadWrite, EditAnywhere) int32 Black = 0; };
Create “OnSubmitColorBidNotification”. It takes in a parameter named “Id” of type FString& and returns void. Add the following code:
FColorBids ColorBids; AMyGameState* MyGameState = GetWorld()->GetGameState<AMyGameState>(); ColorBids.Black = MyGameState->BlackEvents; ColorBids.Pink = MyGameState->PinkEvents; FString ColorBidsString; FJsonObjectConverter::UStructToJsonObjectString(ColorBids, ColorBidsString); SubmitNotification(Id, ColorBidsString);
Events Improvements (Web)
We now have a notification being sent to us we want to intercept. However, first some general improvements.
First open the style.css file and add the following classes:
.btn { opacity: 0.7; border-radius: 50%; padding: 12px; margin: 12px; width:16px; } .black { background-color:rgb(0, 0, 0); border-color:rgb(251,72,196); } .pink { background-color:rgb(251,72,196); border-color:rgb(0, 0, 0); }
We will now have colored buttons that users can click on for the black and pink bids
Open index.html. Replace the contents with the following:
<!doctype html> <html> <head> <title>Genvid Overlay</title> <link rel="stylesheet" href="style.css"> <script src="genvid.umd.js"></script> <script src="genvid-math.umd.js"></script> <script src="overlay.js"></script> </head> <body style="background:black"> <div id="video_player"></div> <div id="overlay"> <div class="child" id="hour">Hour:</div> <div class="child" id="minute">Minute:</div> <div class="child" id="second">Second:</div> <div class="child" id="notification">Notification:</div> <div class="child" id="annotation">Annotation:</div> <div> <button class='btn pink' id='pink_button'></button> </div> <div class="child" id="PinkBid">Pink Bids:</div> <div> <button class='btn black' id='black_button'></button> </div> <div class="child" id="BlackBid">Black Bids:</div> </div> </html>
We now have a place to display the number of bids for each color next to the buttons
Additionally we added some default values to all the streams
We are now ready to receive the new notification. Open “overlay.js”
Replace the onNotificationsReceived override with the following:
this.genvidClient.onNotificationsReceived(function (notifications) { for (let notification of notifications.notifications) { try { notification.user = JSON.parse(notification.data); if (notification.id == "ClockNotification") { notificationUpdate(notification.user); } else if (notification.id == "ColorBidNotification") { colorBidUpdate(notification.user); } } catch (e) { console.log(e, notification); } } });
We now only call notificationUpdate if the id is “ClockNotification”
There is a new call to a function we will create below called “colorBidUpdate” if the notification has the name “ColorBidNotification”
Add the following function at the bottom of the overlay.js file:
function colorBidUpdate(data) { document.getElementById("PinkBid").innerHTML = "Pink Bids: " + Math.round(data.pink); document.getElementById("BlackBid").innerHTML = "Black Bids: " + Math.round(data.black); }
We use the notification to update our overlay with the current bid numbers
Run through the usual steps detailed in the web guide to deploy the client and local cluster. You can now vote for the colors and have their values updated in real time. Additionally the colors only change when a clock hand hit’s a 15 second mark. However the votes are tallied later due to the stream delay.
Stream with event improvements