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:
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.
<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.
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:
//------------------------------------------------------------------------------
// 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.
//------------------------------------------------------------------------------
// 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:
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:
#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"
#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.
#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;
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.
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 );
private: section, add the following destructor definition:
~Game();
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.
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:
// 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.
// Destructor
Game::~Game()
{
DeleteCriticalSection( m_pPlayersCS );
}
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.
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// 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.
// 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.
// 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.
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.
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 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.
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.
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