Building a SmartGlass DirectX Game

In this tutorial, you will learn how to send and receive messages from SmartGlass devices. You will build a simple DirectX game that receives messages from SmartGlass devices and changes the color of the screen.

A SmartGlass experience has two parts: the code that runs on the console, and the code that runs on the device. This tutorial teaches you how to write and test the console portion, and consists of the following sections:

Create and configure the project

The first part of building a DirectX game with a SmartGlass experience is to create and configure a new project in Visual Studio that uses the Direct3D Game template. You also need to change the Package.appxmanifest file to set the TitleId and PrimaryServiceConfigId attributes that must be set for SmartGlass to work.

To create and configure the project

  1. On the File menu in Visual Studio on your development computer, point to New and click Project.
  2. In the left pane of the New Project dialog box, expand the Installed, Templates, Visual C++, and Xbox One entries as needed, and click XDK.
  3. In the center pane, click Direct3D Game and type a name for your project that does not include spaces or non-alphanumeric characters in the Name box, then click OK.
  4. On the Solution Explorer tab in the right pane, double-click Package.appxmanifest to open the file.
  5. Inside the <Extensions> section that follows the <VisualElements> section of the Package.appxmanifest file, add the following lines of XML:
    <mx:Extension Category="xbox.live">
        <mx:XboxLive TitleId="11111111" PrimaryServiceConfigId="11111111-1111-1111-1111-111111111111"/>
    </mx:Extension>  
    

    Notice that the values of the TitleId and PrimaryServiceConfigId attributes consist of repeated 1s. These values are not official TitleId or PrimaryServiceConfigId values, but you need non-empty values of TitleId and PrimaryServiceConfigId for SmartGlass to work.

Add new code files to the project

When you finish creating and configuring the project for the game, you are ready to start adding code files to the project. For this example, you will add a header file and a code file, which are named Player.h and Player.cpp. These files create a Player class that does the following:

To add new code files

  1. On the Solution Explorer tab, right click the name of your project, then point to Add and click New Item.
  2. In the Add New Item dialog box, click Header File (.h) and type Player.h in the Name, then click Add.
  3. On the Player.h tab, remove any existing code and add the code in the following example:
    //------------------------------------------------------------------------------
    // Player.h: Player class. This class coordinates the SmartGlass device with 
    // the game data, and contains the event handlers that respond to input from  
    // the device.
    //-----------------------------------------------------------------------------
          
    #pragma once
    #include "pch.h"
          
    using namespace Windows::UI::Core;
    using namespace Windows::Foundation;
    using namespace Windows::Xbox::SmartGlass;
    using namespace Windows::Devices::Sensors;
    using namespace Windows::Data::Json;
          
    ref class Player sealed
    {
    public:
        virtual ~Player();
          
        // This property indicates whether to change the background color
        // of the screen. This property is set to true whenever the game receives 
        // a message from a SmartGlass device.
        property bool                       ChangeScreenColor;
          
    internal:
        Player();
        Player( SmartGlassDevice^ pDevice );
          
        Platform::String^                  m_message;
          
    private:
        void OnMessageReceived( SmartGlassHtmlSurface^ pHtmlSurface, 
            SmartGlassMessageReceivedEventArgs^ pArgs );
          
        SmartGlassDevice^                  m_pSmartGlassDevice;
    };  
    

    The SmartGlass APIs are included in the Windows::Xbox::SmartGlass namespace, so the header file includes a using statement for that namespace. The player class has one public property, Player::ChangeColor, which the game uses to change the color of the screen when the console gets a message from a SmartGlass device.

  4. On the Solution Explorer tab, right click the name of your project, then point to Add and click New Item.
  5. In the Add New Item dialog box, click C++ File (.cpp) and type Player.cpp in the Name, then click Add.
  6. On the Player.cpp tab, remove any existing code and add the code in the following example:
    //------------------------------------------------------------------------------
    // Player.cpp
    // Player class.  This class coordinates the companion device with the game
    // data and Direct3D objects.
    //
    // Copyright (C) Microsoft Corporation. All rights reserved.
    //-----------------------------------------------------------------------------
          
          
    #include "pch.h"
    #include "Player.h"
          
    Player::Player()
    {
              
    }
          
    //-----------------------------------------------------------------------------
    // Player constructor:
    //  Sets the active surface to HTML.
    //  Attaches the event handler to the MessageReceived event.
    //-----------------------------------------------------------------------------
    Player::Player( SmartGlassDevice^ pDevice )
    {
         // Get the HtmlSurface.
        SmartGlassHtmlSurface^ pHtmlSurface = pDevice->HtmlSurface;
          
        // Set the Mode to HtmlSurface.
        pDevice->SetActiveSurfaceAsync( pHtmlSurface );
          
        // Attach an event handler to the MessageReceived event.
        pHtmlSurface->MessageReceived += 
            ref new TypedEventHandler<SmartGlassHtmlSurface^,  
            SmartGlassMessageReceivedEventArgs^>(this, &Player::OnMessageReceived);
          
        m_pSmartGlassDevice = pDevice;
    }
          
    //-----------------------------------------------------------------------------
    // Destructor
    //-----------------------------------------------------------------------------
    Player::~Player(void)
    {
             
    }
          
    //-----------------------------------------------------------------------------
    // When a message is received, the OnMessageReceived handler:
    //  Stores the message in the m_message variable.
    //  Sends a response.
    //-----------------------------------------------------------------------------
    void Player::OnMessageReceived( SmartGlassHtmlSurface^ pHtmlSurface, 
        SmartGlassMessageReceivedEventArgs^ pArgs)
    {
        // SmartGlass sends messages by using JSON. The expected value is an object
        // with a message field. 
        JsonObject^ object = JsonObject::Parse(pArgs->Message);
          
        m_message = object->GetNamedString( L"message" );
          
        // Update color
        ChangeScreenColor = true;
          
        // Create a message to send back to the SmartGlass device.
        JsonValue^ response = JsonValue::CreateStringValue( 
            ref new Platform::String( L"Hello from the console." ) );
          
        // Send the message back to the SmartGlass device.
        pHtmlSurface->SubmitMessageAsync( response->Stringify() );
    }  
    

    The Player.cpp file contains a lot of code. The key parts of the code include:

    • The constructor, which sets the mode for SmartGlass to SmartGlassHTMLSurface. This setting changes the display of the SmartGlass device to show the HTML, CSS, and JavaScript SmartGlass activity that is a companion to the game instead of the default SmartGlass app user interface. This setting allows the game to communicate with the SmartGlass activity by sending messages back and forth.
    • The Player::OnMessageRecieved method, which handles the MessageReceived event. This event occurs whenever the game gets a new message from a SmartGlass device. Messages are sent in JSON. The Player::OnMessageRecieved method also does the following:
      • Sets the ChangeScreenColor variable to true after the game receives a message from the SmartGlass device. The Game.cpp code uses this variable later to decide whether to change the color of the screen.
      • Sends a message back to the SmartGlass device. Like incoming messages, outgoing messages must also be in JSON.

Update the template Game.h header file to use SmartGlass

To create the SmartGlass experience for the game, you also need to add code to some of the template code files that the Direct3D Game template provides. One of the files that you need to modify is the Game.h header file. The additions that you make to this file include:

To update the template Game.h header file

  1. On the Solution Explorer tab, double-click Game.h to open the file.
  2. After the #include "StepTimer.h" line on the Game.h tab, add the following statement to include the header file for the Player class:
    #include "Player.h"  
    
  3. After the #include statements, add the following statement to define a constant that represents the maximum number of players.
    #define MAX_PLAYERS 4  
    

    Xbox One supports more than 4 players, but for this tutorial, use 4.

  4. After the #define statement you added in step 3, add the following using statements:
    // For SmartGlass.
    using namespace Windows::Foundation;
    using namespace Windows::Xbox::SmartGlass;
    using namespace Windows::Xbox::Input;  
    
  5. After the namespace declarations you added in step 4, add the following code to create a structure that holds information about the player:
    struct PlayerInfo
    {
        Player^             pPlayer;
        Platform::String^   Id;
        bool                changeColor;
    };  
    

    This structure helps keep track of the player that corresponds to each SmartGlass device by using the the Id property., and keeps track of whether the player wants to change the screen color with the colorChange property.

  6. After the void Present(); statement in the public: section, add the following method declarations:
    // SmartGlass device event handling
    void OnDeviceAdded(SmartGlassDeviceWatcher^ sender, SmartGlassDevice^ device);
    void OnDeviceRemoved(SmartGlassDeviceWatcher^ sender, 
        SmartGlassDevice^ device);
    void OnFindAllCompletion( 
        IAsyncOperation<SmartGlassDeviceCollection^>^ asyncOp, 
        AsyncStatus status );  
    
  7. At the beginning of the private: section, add the following destructor definition:
    ~Game();  
    
  8. After the DX::StepTimer m_timer; statement in the private: section, add the following code:
    // SmartGlass variables
    SmartGlassDeviceWatcher^                        m_watcher;
    PlayerInfo                                      m_pPlayerArray[MAX_PLAYERS];
    int                                             m_playerCount;
    wchar_t                                         m_message[256];
    wchar_t                                         m_playerMessage[65532];
    CRITICAL_SECTION                                m_playersCS;
    LPCRITICAL_SECTION                              m_pPlayersCS;
    float                                           m_screenColor[4];
    int                                             m_colorIndex;  
    

    The SmartGlassDeviceWatcher class provides events that let the game know when new SmartGlass devices connect to the console and when existing SmartGlass devices disconnect. This tutorial uses the connect and disconnect events to update the list of players and the SmartGlass device associated with those players. The game stores this data in the m_pPlayerArray variable.
    The m_pPlayerArray variable is 65,532 bytes in size. This size is the maximum message size for a SmartGlass message. A maximum size exists to reduce network latency. To send a bigger message, break up the message and send the message in pieces.

Update the template Game.cpp code file to use SmartGlass

In addition to the changes that you made in the Game.h header file, you also need to make changes to the Game.cpp that implement the items in the header file to support the SmartGlass experience. These changes include:

To update the template Game.cpp code file

  1. On the Solution Explorer tab, double-click Game.cpp to open the file.
  2. In the constructor for the Game class on the Game.cpp tab, add the following code that sets the initial screen color and initializes a critical section:
    // Set the initial screen color to blue.
    m_screenColor[0] = 0.39f;
    m_screenColor[1] = 0.58f;
    m_screenColor[2] = 0.93f;
    m_screenColor[3] = 1.000f;
          
    m_pPlayersCS = &m_playersCS;
    InitializeCriticalSection( m_pPlayersCS );  
    

    The game uses the critical section to synchronize access to the player array.

  3. After the Game::Game constructor method, add a destructor with the following code:
    // Destructor
    Game::~Game()
    {
        DeleteCriticalSection( m_pPlayersCS );
    }  
    
  4. At the end of the Game::Initialize method, add the following code:
     m_watcher = ref new SmartGlassDeviceWatcher();
    m_watcher->DeviceAdded += ref new TypedEventHandler<SmartGlassDeviceWatcher^, 
        SmartGlassDevice^>( this, &Game::OnDeviceAdded );
    m_watcher->DeviceRemoved += ref new TypedEventHandler<SmartGlassDeviceWatcher^, 
        SmartGlassDevice^>( this, &Game::OnDeviceRemoved );
    SmartGlassDevice::FindAllAsync()->Completed =
        ref new AsyncOperationCompletedHandler<SmartGlassDeviceCollection^>( this, 
        &Game::OnFindAllCompletion );
    m_colorIndex=0;  
    

    This code creates a new SmartGlassDeviceWatcher object and listens for the SmartGlassDeviceWatcher::DeviceAdded and SmartGlassDeviceWatcher::DeviceRemoved events. The game uses these events to keep the list that maps players to devices up to date.
    The code also the calls the SmartGlassDevice::FindAllAsync method to get all the SmartGlass devices that are currently connected to the console.

  5. After the Game::Initialize method, add the following method:
    //-----------------------------------------------------------------------------
    // Enumerates any already connected devices.
    //   If any are found, passses them to OnDeviceAdded.
    //-----------------------------------------------------------------------------
    void Game::OnFindAllCompletion( 
        IAsyncOperation<SmartGlassDeviceCollection^>^ asyncOp, 
        AsyncStatus status )
    {
        UNREFERENCED_PARAMETER( status );
          
        SmartGlassDeviceCollection^ devices = asyncOp->GetResults();
        UINT devicesSize = devices->Size;
        for( UINT i = 0; i < devicesSize; i++ )
        {
            OnDeviceAdded(m_watcher, devices->GetAt(i));
        }
    }  
    

    This method runs when the asynchronous SmartGlassDevice::FindAllAsync method returns. The method loops through all of the SmartGlass devices and calls OnDeviceAdded (a function added in the next step) that adds the device to the list of devices.

  6. After the Game::OnFindAllCompletion method that you added in step 5, add the following method:
    //-----------------------------------------------------------------------------
    // When a companion connects, this method does the following:
    //   Displays a message containing the user information for the device.
    //   Creates a new pair of Player and PlayerInfo objects and stores the new
    //   PlayerInfo object in the player array.
    //--------------------------------------------------------------------------------
    void Game::OnDeviceAdded( SmartGlassDeviceWatcher^ sender, 
        SmartGlassDevice^ device )
    {
        UNREFERENCED_PARAMETER( sender );    
        bool knownDevice = false;
          
        // Need a critical section here to prevent changes to the array while 
        // iterating.
        EnterCriticalSection( m_pPlayersCS );
          
        for( int i = 0; i < m_playerCount - 1; i++ )
        {
            // If the game already knows about this player, ignore the player.
            if( m_pPlayerArray[i].Id == device->DirectSurface->Id ) 
            {
                knownDevice = true;
                break;
            }
        }
          
        if( m_playerCount < MAX_PLAYERS && !knownDevice)
        {
            if ( device->User != nullptr )
            {
                swprintf_s( m_message, L"Companion %d connected as user '%s'.", 
                    m_playerCount, device->User->XboxUserId->Data() );
            }
            else
            {
                swprintf_s( m_message, L"Companion %d connected without a user.", 
                    m_playerCount );
            }       
          
            m_pPlayerArray[m_playerCount].pPlayer = ref new Player( device );
            m_pPlayerArray[m_playerCount].Id = device->HtmlSurface->Id;
            m_playerCount++;
        }
          
        LeaveCriticalSection( m_pPlayersCS );
    }  
    

    This method adds the new device to the devices list. The method loops through the list to check if the device is already on the list. If the device is a new device, then the method creates a new Player object and adds the player to the m_pPlayerArray. The method also associates the device with the player by setting the PlayerInfo::Id property of the device.

  7. After the Game::OnDeviceAdded method you added in step 6, add the following method:
    //-----------------------------------------------------------------------------
    // When a companion is removed, find the correct Player and remove it from
    // the array, shifting the contents over if necessary.
    //-----------------------------------------------------------------------------
    void Game::OnDeviceRemoved(SmartGlassDeviceWatcher^ sender, 
        SmartGlassDevice^ device)
    {
        UNREFERENCED_PARAMETER( sender );
          
        // Need a critical section here to prevent changes to the array while 
        // iterating.
        EnterCriticalSection( m_pPlayersCS );
          
        // Get the HtmlSurface object.
        SmartGlassHtmlSurface^ pHtmlSurface = device->HtmlSurface;
        for( int i = 0; i < m_playerCount; i++ )
        {
            if( m_pPlayerArray[i].Id == pHtmlSurface->Id )
            {
                swprintf_s( m_message, L"Companion %d disconnected.", i );
          
                m_pPlayerArray[i].pPlayer = nullptr;
                m_pPlayerArray[i].Id = nullptr;
          
                // If the pCompanion removed is not the last companion in the 
                // array, swap the last companion in the array with the 
                // removed companion.
                if( i < m_playerCount - 1 )
                {
                    m_pPlayerArray[i] = m_pPlayerArray[m_playerCount - 1];
                    m_pPlayerArray[m_playerCount - 1].pPlayer = nullptr;
                    m_pPlayerArray[m_playerCount - 1].Id = nullptr;
                }
                m_playerCount--;
                break;
            }
        }
        LeaveCriticalSection( m_pPlayersCS );
    }  
    

    The Game::OnDeviceRemoved method removes the device from the list of devices when a SmartGlass device disconnects.

  8. In the Game::Render method, add the following code at the start of the method:
    // Need a critical section here to prevent changes to the array while iterating.
    EnterCriticalSection( m_pPlayersCS );
          
    for( int i = 0; i < m_playerCount; i++ ) 
    {
        Player^ player = m_pPlayerArray[i].pPlayer;
        if (player->ChangeScreenColor)
        {
            m_colorIndex = (m_colorIndex +1) % 3;
            switch (m_colorIndex)
            {
            case 0:
                m_screenColor[0] = 1.0f;
                m_screenColor[1] = 0.0f;
                m_screenColor[2] = 0.0f;
                break;
            case 1:
                m_screenColor[0] = 0.0f;
                m_screenColor[1] = 1.0f;
                m_screenColor[2] = 0.0f;
                break;
            case 2:
                m_screenColor[0] = 0.0f;
                m_screenColor[1] = 0.0f;
                m_screenColor[2] = 1.0f;
                break;
            default:
                m_screenColor[0] = 0.0f;
                m_screenColor[1] = 0.0f;
                m_screenColor[2] = 0.0f;
                break;
            }
            player->ChangeScreenColor = false;
        };
    }
              
    LeaveCriticalSection( m_pPlayersCS );  
    

    This code checks to see if one of the SmartGlass devices set the ChangeScreenColor variable to true. This variable is set to true whenever the console receives a SmartGlass message. If the variable is set to true, the code changes the color choice to red, green or blue and sets m_screenColor to that color.

  9. At the beginning of the Game::Clear method, remove the following lines:
    // Clear the views.
    const float clearColor[] = { 0.39f, 0.58f, 0.93f, 1.000f };  
    

    These lines are unneeded because the code declared a member variable for the color earlier.

  10. In the Game::Clear method, change clearColor in the following line to m_screenColor:
    m_d3dContext->ClearRenderTargetView(m_renderTargetView.Get(), clearColor);  
    

    The line should read as follows when you are done:

    m_d3dContext->ClearRenderTargetView(m_renderTargetView.Get(), m_screenColor);  
    

    This code sets the background of the screen to the color generated by the code in step 8.

Build and deploy the game to a dev kit

After you add the new files and add the new code to the template files that the Direct3D Game template provides, you are done with all of the changes that you need to make to the code to add a SmartGlass experience to the game. You can now build the project and deploy it to your dev kit. This section assumes that your dev kit is already configured and you have already established a connection between your development computer and your dev kit.

To build and deploy the game

Test that the game works with an existing SmartGlass companion demo

To test that the SmartGlass experience for this game works with a SmartGlass companion demo, you need to have SmartGlass Studio installed on a device. SmartGlass Studio is available as part of the SmartGlass Hosted Companion SDK. You also need to deploy the game to your dev kit and have the game running during the test.

To test the game with an existing SmartGlass companion demo

  1. Be sure that the game you deployed to your dev kit is running on your dev kit.
  2. On the Login tab in SmartGlasss Studio on your device, tap Log in, enter the credentials for your Microsoft account on the Sign in page that appears, and tap Sign in. or
    Tap Anonymous user.
  3. On the Connect, tap Local console, enter the console IP address for your dev kit, and tap Connect.

    Note Use the console IP address for your dev kit, not the tools IP address.
    or
    Tap Discover Consoles, and tap the box that corresponds to your dev kit when discovery completes.

  4. Under Companion on the Launch tab, enter http://sgsapinext.dfhosted.net in the first text box and tap Launch Companion.
  5. On the SmartGlass API Sample page of the companion, tap Messaging.
  6. On the Messaging page of the companion, tap Send Message. The game running on your dev kit should change the background color to red, green, or blue each time you tap Send Message.

See also

Overview of the SmartGlass Platform

Developing the Console Side of a SmartGlass Experience

Receiving Messages from a SmartGlass-Enabled Device in an Xbox One Game or App

Sending Messages from an Xbox One Console to a SmartGlass-Enabled Device