article

AndresGenvid avatar image
AndresGenvid posted

05. Unity Commands & Events Guide   

Unity Commands & Events Guide

This guide was written as a quick reference step by step guide for setting up a simple Unity 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 (Unity)

  1. We will be creating a command that is only available on the admin screen that allows you us to stop the clock from ticking

  2. First select “Create Command Parameter” in the contextual menu under Create->Genvid

  3. The parameter will have one property named “Id”. Set the Id of our command to be “Stop”

  4. In the GenvidPlugin object in your scene under the property SessionManager->Session->Commands->Settings add an element. Drag our new command parameter into element 0

  5. In the Commands Listener object in your scene under the property Commands->Listeners create a new element. Drag our new parameter into the “Command” property in Element 0.

  6. Note there is an “OnCommandTriggered” we can bind a function to. We will make this next. Create a script component named “ClockCommand” on the Commands Listener.

  7. Add a public GameObject property named “Clock” to the script.

  8. Add a function named “OnStopCommand”. It returns void and takes in 3 properties. A string named “Id”, a string named “Command”, and an IntPtr named “CallbackPointer”. Make the function public

    • Note that the 3 properties are the types that “OnCommandTriggered” asks for

  9. Add the following code to the command:

  10.         if (Id == "Stop")
            {
          
                Clock.GetComponent<Clock>().enabled = false;
            }
  11. The component we are getting is our script on the Clock. If you named the script something that is not “Clock” that will be what you are getting instead.

  12. Setting enabled on a script to false stops it from ticking

  13. On your Command Listener there should now be a “Clock” property on the script you created. Drag your Clock in the level into that property.

  14. On Commands Listener - drag a reference to itself to the object property under “OnCommandTriggered”.

  15. In the function dropdown look for “OnStopCommand” - and set this function

1668035511198.pngGenvidPlugin with the new command

1668035577689.png

Command Listener




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

1668024814438.pngStream with Command

Color Change Event (Unity)

  1. We will be creating a event allows viewers to change the clocks color

  2. First select “Create Event Parameter” in the contextual menu under Create->Genvid

  3. The parameter will have one property named “Id”. Set the Id of our event to be “colorChange”

  4. In the GenvidPlugin object in your scene under the property SessionManager->Session->Events->Settings add an element. Drag our new event parameter into element 0

  5. In the Events Listener object in your scene under the property Events->Listeners create a new element. Drag our new parameter into the “Event” property in Element 0.

  6. Note there is an “OnEventTriggered” we can bind a function to. We will make this next. Create a script component named “ClockEvent” on the Commands Listener.

  7. Add a public GameObject property named “Clock” to the script.

  8. Add a function named “OnColorChangeEvent”. It returns void and takes in 4 properties. A string named “Id”, a EventResult array named “Results”, an int32 named “NumResults”, and an IntPtr named “CallbackPointer”. Make the function public

    • Note that the 4 properties are the types that “OnEventTriggered” asks for

  9. Add the following code to the command:

  10.         int PinkEvents = 0;
            int BlackEvents = 0;
    
    
            foreach(EventResult Result in Results)
            {
          
                if (Result.key.fields[0] == "pink")
                {
          
                    foreach (EventValue value in Result.values)
                    {
          
                        if (value.reduce == 2)
                        {
          
                            PinkEvents += (int) value.value; 
                        }
                    }
                }
                else if (Result.key.fields[0] == "black")
                {
          
                    foreach (EventValue value in Result.values)
                    {
          
                        if (value.reduce == 2)
                        {
          
                            BlackEvents += (int)value.value;
                        }
                    }
                }
    
    
            }
    
    
            bool BlackColor = BlackEvents > PinkEvents;
            Clock.GetComponent<Clock>().changeColor(BlackColor);
  11. The component we are getting is our script on the Clock. If you named the script something that is not “Clock” that will be what you are getting instead.

  12. We loop through the events and look for a key of “pink” or “black”. Then if the reduce operation is type “2” (or ‘count’) then we add those values to a counter we are tracking

  13. We then call a function on our clocks script that does not exist yet but we will make soon (if the clock script was imported it will already exist)

  14. The next few steps will already be done if the clock was imported

  15. Open your clock script and create 5 public properties. 3 GameObjects: HourHandCube, MinuteHandCube, and SecondHandCube. 2 Materials: BlackMaterial and PinkMaterial.

  16. Create a function called “changeColor” that returns void and takes in one parameter named “BlackColor” of type boolean. Make it public. Add the following code:

  17.         if (BlackColor)
            {
          
                HourHandCube.GetComponent<MeshRenderer>().material = BlackMaterial;
                MinuteHandCube.GetComponent<MeshRenderer>().material = BlackMaterial;
                SecondHandCube.GetComponent<MeshRenderer>().material = BlackMaterial;
            }
            else
            {
          
                HourHandCube.GetComponent<MeshRenderer>().material = PinkMaterial;
                MinuteHandCube.GetComponent<MeshRenderer>().material = PinkMaterial;
                SecondHandCube.GetComponent<MeshRenderer>().material = PinkMaterial;
            }
  18. If we pass in true then we make all the hands of the clock black. Otherwise make them pink

  19. On your Clock object there should now be 3 new properties for the hands of the clock. Drag the hands into each property.

  20. Create a material and make it pink. Drag this material and the existing black material you are using for the clock hands into the 2 other properties of the clock script.

  21. On your Event Listener there should now be a “Clock” property on the script you created. Drag your Clock in the level into that property.

  22. On Event Listener - drag a reference to itself to the object property under “OnEventTriggered”.

  23. In the function dropdown look for “OnColorChangeEvent” - and set this function

1668035669037.pngGenvidPlugin with the new event

1668035707720.pngEvent Listener


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.



1668023326385.pngStream with event buttons



Events Improvements (Unity)

  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. Open your ClockEvents script

  4. Create two class variables of type int with named “PinkEvents” and “BlackEvents”. Make them both public

  5. Replace your “OnColorChangeEvent” with the following code:

  6.         foreach(EventResult Result in Results)
            {
          
                if (Result.key.fields[0] == "pink")
                {
          
                    foreach (EventValue value in Result.values)
                    {
          
                        if (value.reduce == 2)
                        {
          
                            PinkEvents += (int) value.value; 
                        }
                    }
                }
                else if (Result.key.fields[0] == "black")
                {
          
                    foreach (EventValue value in Result.values)
                    {
          
                        if (value.reduce == 2)
                        {
          
                            BlackEvents += (int)value.value;
                        }
                    }
                }
            }
        }
  7. We no longer reset the pink and black vote count

  8. We no longer call the function to update the color of the clock hands

  9. PinkEvents and BlackEvents are now class properties

  10. Create a new function called “ColorChange” that takes no inputs and returns void. Make it public. Copy the following code into it:

  11.         if (BlackEvents == PinkEvents)
            {
          
                return;
            }
    
    
            bool BlackColor = (BlackEvents > PinkEvents);
            Clock.GetComponent<Clock>().changeColor(BlackColor);
  12. When called this function does nothing if the values are the same. Otherwise it updates to the greater of the two values

  13. In your ClockStream script create a new public class variable called “ClockEvent” of type game object

  14. In the “Update” function. Change the if statement that flips the notification and annotation booleans to the following:

  15.         if (second % 15 == 0 && second != previousSecond)
            {
          
                // Note here how we are sending the notification and the annotation at the same time.
                canSendAnnotation = true;
                canSendNotification = true;
                previousSecond = second;
                ClockEvents.GetComponent<ClockEvents>().ColorChange();
            }
  16. We now call the new function we just created on the Clock Events script to update the colors of the hands every 15 seconds

  17. Now we will add a notification stream for the bid numbers - but first we need a structure that will contain our notification data. Create a serializable public structure named “BidUpdates” with two SerializeFields that are public of type int. One named “pink” and the other is “black”:’

  18.     [System.Serializable]
        public struct BidUpdates
        {
          
            [SerializeField] public int black;
            [SerializeField] public int pink;
        }
  19. Create “SubmitClockColorBidNotification” that takes in a GenvidStreamParameters named “streamParams” and returns void. Add the following code to it:

  20.         if (GenvidPlugin.Instance.IsInitialized && GenvidPlugin.Instance.enabled)
            {
          
                string streamId = streamParams.Id;
    
    
                ClockEvents ClockScript = ClockEvents.GetComponent<ClockEvents>();
                BidUpdates notification = new BidUpdates()
                {
          
                    black = ClockScript.BlackEvents,
                    pink = ClockScript.PinkEvents,
                };
    
    
                GenvidPlugin.Instance.SessionManager.Session.SubmitNotificationJSON(streamId, notification);
            }
  21. We get the pink and black events and stuff them in our new serializable structure before submitting it as a notification

  22. Back in unity create a new genvid stream parameter and set the id to be “ColorBidNotification”

  23. In your GenvidPlugin add a 4th Stream element and drag your new parameter on to this element

  24. On the Streams Listener add a new element to the listeners list. Drag your new parameter to the Stream property. Drag a reference to itself on “OnStreamSubmit” object property. Then select our new “SubmitClockColorBidNotification” function. Drag our new parameters unto the parameters property

  25. Still on the Streams Listener scroll to your ClockStreams script component. For the ClockEvents object drag your “EventsListener” gameobject in the scene to it.


1668035998656.pngGenvidPlugin with the additional streams

1668036413168.pngStream Listener with the additional stream




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



1668025180099.pngStream with improved events


Unity
1668024814438.png (88.7 KiB)
1668025180099.png (189.3 KiB)
1668035511198.png (30.7 KiB)
1668035577689.png (36.9 KiB)
1668035669037.png (30.1 KiB)
1668035707720.png (25.0 KiB)
1668035998656.png (35.7 KiB)
1668036413168.png (16.0 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