article

AndresGenvid avatar image
AndresGenvid posted

05. Unreal Blueprints Commands & Events Guide   

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/

Stop Command (Blueprints)

  1. Create a blueprint that inherits from GameState if you have not already

    • Set this game state as the default game state in your game mode

  2. Open your GenvidCommands blueprint. In Begin Play add the following:
  3. 1668448042440.pngGenvidCommands code


    1668448041459.pngStop CollapsedGraph


  4. We add a new command to the Commands array
  5. The new command is named “Stop” and is Replicated

    • See documentation for more information on what “replicated” does

  6. We bind this command to a new event named “OnStopCommand”

  7. “OnStopCommand” gets a reference to our game state and calls an event called “ConfirmCommandStop” we will create next

  8. In your Game State BP create an event called “ConfirmCommandStop”

    • Set it’s Replicates property to Multicast and Reliable

    • It has a single input of type string named “FullCommand”

    • Add the following code to it:

  9. 1668448041256.pngEvent "ConfirmCommandStop" on the Game State BP



  10. Normally you would confirm the command is being sent from a valid location and the command itself is valid. However since this is just a local implementation and there is no input to confirm we just get a reference to our Game Instance and call “CommandStop”

  11. In your GameInstance create a function called “CommandStop”

    • Add the following code:


  12. 1668448042409.pngGameInstance function "CommandStop"



  13. MyClock should be whatever your clocks class is


  14. If you did not download your clock asset: in your clock BP create a boolean called “StopClock”.

  15. 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

  16. 1668554553378.png

  17. 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)

  1. 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.

  2. Add the following code to the new html file:

  3. <!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>=
  4. We are creating a button with the text “STOP” on the admin link

  5. We reference a new .js file named “overlay-admin.js” file. We will create that next

  6. 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

  7. Add the following code to the new js file:

  8. 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();
  9. 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

  10. 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

  11. Go through the steps of setting up the local cluster, package the project (or play in editor), and start up the services

  12. Click on the admin link. The username and password will both be “admin”

  13. Click on the stop command. The clock should immediately stop ticking in the project and eventually stop on stream



1668535575604.pngAdmin stream with the "STOP" command




Color Change Event (Blueprints)

  1. In your blueprint for genvid events add the following code:


  2. 1668448042375.pngGenvidEvents Blueprint code




  3. 1668448041594.pngColorChange Collapsed Graph


  4. We add a new GenvidEvent to the existing Events property

  5. The name of the event is colorChange and marked as replicated

  6. The event is then bound to “OnColorChangeEvent” which gets our gamestate blueprint and calls “Confirm EventColor Change” which we will make next

  7. In your Game State blueprint create a custom event named “ConfirmEventColorChange”

    • In the “Replicates” property in the blueprint make it Multicast and Reliable

    • Give it a single input named “Summary” of type Genvid Event

    • Create two local ints. One named “Pink” the other named “Black”

    • Add the following code:


  8. 1668448042044.pngEvent "ConfirmEventColorChange" in Game State BP



  9. 1668448042165.pngCollapsed Graph "CountColors"


  10. 1668448042206.pngCollapsed Graph "ChooseWinner"


  11. Reset the Pink and Black value counts. We will be adding to these

  12. For every result received we loop through their values in CountColors

  13. For every value we see if the value is a “Count” operation. If it is then we check the key for that value.

  14. If the Key is Pink or Blue we add the count value to one or the other.

  15. Once we are done looping we choose the winner

  16. Get our game instance and call “EventColorChange” - a function we will make next passing in a bool that is true if Black is greater than Pink.

  17. In the Game Instance blueprint create a function called “Color Change” that takes in a boolean called “IsBlack”. Then add the following code:


  18. 1668448042464.png"ColorChange" Function in GameInstance


  19. Get the clock actor and call Color Change which we will make next

  20. Create a new material. Make it pink.

  21. In the clock blueprint implement “ColorChange”. Add the following code:

    • If the clock was imported everything from the branch onward will already be done. You will only need to create ColorChange and then connect


  22. 1668448042490.png"ColorChange" on the clock actor BP


Color Change Event (Web)

  1. Go to your web/public folder

  2. Open your index.html file. Add the following code after the existing streams, notifications, and annotations:

  3.         <div>
                <button class='eventButton' id='pink_button'>Pink</button>
            </div>
            <div>
                <button class='eventButton' id='black_button'>Black</button>
            </div>
  4. We Declare two buttons. One with the text “Pink” the other with the text “Black”

  5. Open your overlay.js file. Replace the contents with the following code:

  6. 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();
  7. Don’t be intimidated by the changes. Most of it has remained the same - just restructured in a way that gives us more control.

  8. 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.

  9. Go to your web/config folder and open events.json. Replace the contents with the following code:

  10. {
         
      "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
            }
          ]
        }
      }
    }
  11. Update your “version” number if necessary

  12. 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

  13. 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.

1668535731207.pngStream with the pink and black event buttons


Events Improvements (Blueprints)

  1. The event is functional. But we can do better.

  2. We will send notifications updating the current bid count of each event and instead only update when the clock strikes a 15 second interval

  3. In your Game State BP promote the “Choose Winner” node to a function also named “ChooseWinner”.

    1. This can be done by right clicking on it

  4. Modify “ChooseWinner” to the following:


  5. 1668448041708.pngSkip Pink & Black being the same


  6. We no longer do anything if Black and Pink are the same
  7. Then change “ConfirmEventColorChange” to the following:


  8. 1668448041405.pngDon't reset Pink and Black and don't choose a winner


  9. We no longer reset the count of Pink and black

  10. We no longer choose a winner here (although we saved that logic off as a function for use later)

  11. Open your Genvid Stream BP. We will instead choose the winner every 15 seconds on tick. Add the following to the end of your tick function:

  12. 1668448041366.pngChoose winner at the end of the tick function


  13. Now we want a stream notification containing the number of bids for each color. Add the following to BeginPlay:


  14. 1668448042351.pngAdd the ColorBidNotification Collapsed Graph to GenvidStreams blueprint



  15. 1668448042008.pngColorBidNotification Collapsed Graph


  16. We add a new node named “ColorBindNotification”

  17. This node creates a stream named “ColorBidNotification”

  18. This stream binds to “OnSubmitColorBidsNotification” which creates a string containing the vote numbers via a function we will make below named “GetColorBidsStringNotification”

  19. Create a function named “GetColorBidsStringNotification” with a return called “ColorBidString” of type String. Add the following code:


  20. 1668448042087.pngconstruct the color bids notification JSON




  21. We get the values of Pink and Black and add them to a JSON string


Events Improvements (Web)

  1. We now have a notification being sent to us we want to intercept. However, first some general improvements.

  2. First open the style.css file and add the following classes:

  3. .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);
    }
  4. We will now have colored buttons that users can click on for the black and pink bids

  5. Open index.html. Replace the contents with the following:

  6. <!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>
  7. We now have a place to display the number of bids for each color next to the buttons

  8. Additionally we added some default values to all the streams

  9. We are now ready to receive the new notification. Open “overlay.js”

  10. Replace the onNotificationsReceived override with the following:

  11.     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);
                }
            }
        });    
  12. We now only call notificationUpdate if the id is “ClockNotification”

  13. There is a new call to a function we will create below called “colorBidUpdate” if the notification has the name “ColorBidNotification”

  14. Add the following function at the bottom of the overlay.js file:

  15. function colorBidUpdate(data) {
         
        document.getElementById("PinkBid").innerHTML = "Pink Bids: " + Math.round(data.pink);
        document.getElementById("BlackBid").innerHTML = "Black Bids: " + Math.round(data.black);
    }
  16. We use the notification to update our overlay with the current bid numbers

  17. 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.



1668535795744.pngStream with event improvements




unreal engineunreal
1668535575604.png (336.9 KiB)
1668535731207.png (196.0 KiB)
1668535795744.png (245.1 KiB)
1668554553378.png (19.1 KiB)
10 |600

Up to 8 attachments (including images) can be used with a maximum of 1.0 MiB each and 10.0 MiB total.

Article

Contributors

AndresGenvid contributed to this article