Download PDF
Windows* 8 Sensors Sample Application – A Ball in Hole Game.pdf [1.53 MB]
Introduction
The purpose of this sample application is to provide a step-by-step guide to quickly set up and start using sensor data via Windows* 8 Sensors application programming interfaces (APIs) on both Windows Desktop and Store application platforms. This guide has two sections. The first, SensorDesktopSample, is a Windows 8 Desktop application, and the second, SensorSampleApp, is a Windows Store app. Starting with Visual Studio* 2012 project templates, both applications implement a simple Ball in Hole game with the same logic for easy comparison. Refer to this link for more information on the differences between Windows Desktop and Windows Store Application Sensor APIs. The ball moves around the screen based on accelerometer readings, and the game ends once the ball is in the hole.
How to Run the Sample Applications
Download and unzip the source code. There are two versions: SensorDesktopSample, which uses the Windows Desktop Sensor API, and SensorSampleApp, which uses the Windows Store App Sensor API. Open each solution in Visual Studio 2012 and compile and run the code in debug mode (F5). Assuming the system is equipped with sensor hardware (refer to this link to detect sensor support), start tilting your system to control the red ball. The diagrams below illustrate the device orientation mapped to screen. The game ends when the red ball is completely inside the black hole. Click or touch anywhere on the screen to reset the game with the red ball where you last clicked or touched.
Windows 8 Desktop Sensor Application – SensorDesktopSample
The Windows Desktop Sensor API lets C++ developers create sensor-based programs using a set of COM interfaces. Please refer to this MSDN link. A benefit of Windows Desktop applications is backward compatibility with Windows 7. This sample makes use of the common sensor accelerometer to program the ball and make it move around the screen. The application uses the WM_TIMER to set the game to update and render every 16 milliseconds or around 60 frames per second allowing the application to poll sensor data periodically instead of using the accelerometer’s events driven model. In order to build applications using the desktop sensor API, you must first download and install the Windows 8 SDK, which includes the libraries and header files you need to start using sensors in Windows programs.
System requirements
Step 1: Creating a new C++ Project in Visual Studio 2012
Open Visual Studio 2012, go to File -> New -> Project…, and select Installed -> Templates -> Visual C++ ->Win32 -> Win32 Project, type in a project name in the Name field (e.g., SensorDesktopSample), and click OK.
Check the settings:
Step 2: Setting up the environment for Windows Desktop Sensor API
Once the project is created, add the sensor libraries and include the header files.
Right click on the SensorDesktopSample project, select References… then under Configuration Properties -> Linker -> Input -> Additional Dependencies, click on the dropdown, select Edit… and make sure sensorsapi.lib is there. For this application to work, you will also need to include d2d1.lib for Direct2D. Click OK to add the Sensor and Direct2D libraries to the project.
When a new Visual Studio C++ Project is created, notice that the <Your Project Name>.cpp (in this case, the SensorDesktopSample.cpp) is the main implementation file. This file contains the “main” function for the application and by default includes the stdafx.h header file:
#include "targetver.h" #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files: #include <windows.h> // C RunTime Header Files #include <stdlib.h> #include <malloc.h> #include <memory.h> #include <tchar.h>
To access and use the desktop sensors API, we must include the sensor API header files:
#include <SensorsApi.h> #include <sensors.h>
In addition to the previous header files, include any additional ones after the TODO comment.
// TODO: reference additional headers your program requires here #include <math.h> #include <stdio.h> #include <d2d1.h> #include <d2d1_1.h> #include <d2d1helper.h> #include <d2d1_1helper.h> #include <dwrite.h> #include <wincodec.h>
Step 3: Creating the Sensor Object
There are two different approaches to using the Sensor Object depending on your application needs. One is building and using a Sensors Event Object that lets you use built-in sensor events to be notified of changes in data and retrieve the data asynchronously. The other is just calling the Sensor Data Report’s GetData function to retrieve sensor data synchronously on demand. In our sample, we use the latter approach as it is better suited to our sample’s needs. This approach allows the game to poll data at a specified regular interval to update game state and render scenes. For more information on the differences between retrieving data asynchronously vs. synchronously, please refer to this MSDN link.
Now to create the sensor object, add a new class via Visual Studio 2012, and name it SensorObj. This will create a header file and implementation file for you.
SensorObj.h – the header file
#pragma once #include <initguid.h> class SensorObj { public: SensorObj(void); ~SensorObj(void); // Register the window class and call methods for instantiating drawing resources HRESULT Initialize(); D2D_POINT_2F GetReport(); private: // Sensor interface pointers ISensorManager* pSensorManager; ISensorCollection* pSensorColl; ISensor* pSensor; ISensorDataReport* pReport; };
We need to include the initguid.h as this is “the header file that contains definitions for controlling GUID initialization” (refer to this MSDN link for general requirements for application development using desktop sensor API).
Then we have the classic constructor, destructor, and initializer along with the public GetReport function that essentially gets accelerometer readings on demand. Notice that this function returns a D2D_POINT_2F object, which is generally bad programming practice as it isn’t encapsulated and introduces unnecessary dependencies. However, it can easily be modified to return a non-dependent data type in which you would parse into the object type you need. However, we do so in this project to further simplify the code.
Moving on to its private members. You will need a Sensor Manager, which provides access to all sensors that are available for use. Drilling down, you have the Sensor Collection, which is helpful when you want to work with a group of sensors such as all motion sensors. A Sensor Collection helps you access the sensors in that collection. Further down is the Sensor object itself, which is where you can read its data through the Sensor Data Report object. This object contains sensor data and allows you to generate reports to retrieve sensor data in a sensible manner. For more information on sensor objects, please refer to this MSDN link.
SensorObj.cpp – the implementation file
#include "stdafx.h" #include "SensorObj.h" SensorObj::SensorObj(void) { }; SensorObj::~SensorObj(void) { };
The initialization function is where we initialize all the components in order to start using the sensors object. First, we create the sensor manager:
// Creates the application window and device-independent // resources. HRESULT SensorObj::Initialize() { HRESULT hr = ::CoCreateInstance(CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSensorManager));
Then, we check whether the sensor manager creation was successful.
if(hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DISABLED_BY_POLICY)) { // Unable to retrieve sensor manager due to // group policy settings. Alert the user. return hr; } if(FAILED(hr)){ return hr; }
We then get the sensor collection by type—in this case, the 3D Accelerometer—and check for success. We assume that the first sensor in this sensor collection is the Accelerometer sensor we are after and set that to our Sensor object using the GetAt function. For more information on different methods for getting the sensor object, refer to this MSDN link.
hr = pSensorManager->GetSensorsByType(SENSOR_TYPE_ACCELEROMETER_3D, &pSensorColl); if (FAILED(hr)) { ::MessageBox(NULL, L"No Motion Sensor", NULL, MB_OK); return hr; } ULONG ulCount = 0; hr = pSensorColl->GetCount(&ulCount); if (FAILED(hr)) { ::MessageBox(NULL, L"No Accelerometer Sensor", NULL, MB_OK); return hr; } hr = pSensorColl->GetAt(0, &pSensor);
If we were able to successfully get the sensor object, then we want to set the Event Sink, which is required to retrieve sensor data. Normally, if we were to use event notification, we would create a Sensor Event object and pass it to SetEventSink. However, in our case, we pass in NULL to cancel event notifications and poll data on demand. Finally, we return the hr result to continue propagation of initialization results.
if(SUCCEEDED(hr)){ hr = pSensor->SetEventSink(NULL); } return hr; }
The GetReport function is where we retrieve the accelerometer readings on demand. To retrieve sensor data, use the GetData function and pass in the Sensor Data Report object. We then create a PROPVARIANT to retrieve results. Call the GetSensorValue function to specify which data you are looking for and the variant. Check the results to verify if it is in the right format and get the value as a double by calling the variant property. Clear the variant before re-use. Collect the X and Y acceleration data and directly convert it into a Direct2D Point to return as the velocity. For a complete list of available sensor values, please refer to this MSDN link. You can also find all enumerations types in the sensors.h header file under external dependencies. This file also denotes the data type (e.g., VT_R4, VT_R8, etc.), this is important for you to apply the appropriate variant to your data values to ensure no loss of data.
D2D_POINT_2F SensorObj::GetReport(){ // Get the data report. HRESULT hr = pSensor->GetData(&pReport); D2D_POINT_2F velocity; if(SUCCEEDED(hr)) { PROPVARIANT var = {}; hr = pReport->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_X_G, &var); if(SUCCEEDED(hr)) { if(var.vt == VT_R8) { // Get the X reading velocity.x = var.dblVal; } } PropVariantClear(&var); hr = pReport->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_Y_G, &var); if(SUCCEEDED(hr)) { if(var.vt == VT_R8) { // Get the Y reading velocity.y = var.dblVal; } } } return velocity; }
Step 4: Adding the Direct2D component
For simplicity sake, we will not go into detail on Direct2D. For more info, please visit this MSDN link.
In the meantime, please add in the Direct2DClass.h and Direct2DClass.cpp.
Step 5: Implementing Game Logic
This game has two very simple components: the hole and the ball. We need to track the size and position of these two objects, as well as the velocity of the ball and the state of the game. The game ends once the ball has completely overlapped with the black hole. When this happens, the game stops rendering until the user clicks/touches elsewhere on the screen to reset the game and place the ball in the clicked/touched position (and yes you can cheat by touching the black hole to win, but where’s the fun in that?).
Knowing the position and radius of the black hole, we control the position of the red ball using accelerometer readings by constantly updating its velocity. The velocity is then added to the ball’s position. The accelerometer value is constantly added to velocity, hence the ball will move faster and faster if it is constantly moving in one direction. When tilting the system in the opposite direction, it will decrease velocity and gradually reach 0 before actually moving in the other direction. This provides a smoother, more realistic user experience for the game. The four “walls” are the boundaries of the screen, when the ball “hits” the wall, we simply invert the velocity and decrease the velocity slightly to simulate the “bounce” effect.
The above mentioned game logic is all implemented within the Direct2DCalss structure. We have completely isolated the Direct2DClass to do nothing but keep track of the game state and render the scene.
Step 6: Putting it all together in main
We will need to track two global variables: Direct2D object and Sensor Object.
Direct2DClass *d2dObj; SensorObj *sensorObj;
Below is the default _tWinMain function that comes with a Visual Studio 2012 C++ Windows Project.
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: Place code here. MSG msg; HACCEL hAccelTable; // Initialize global strings LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_SENSORDESKTOPSAMPLE, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SENSORDESKTOPSAMPLE)); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; }
As part of the main loop, it makes a call to InitInstance to initialize required resources. We will add our initializations in this function.
// Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }
Below is the default Visual Studio C++ Project InitInstance function:
// // FUNCTION: InitInstance(HINSTANCE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; // Store instance handle in our global variable hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
As soon as we successfully obtain an instance handle and store it in our global variable, we will initialize the Direct2D object.
d2dObj = new Direct2DClass(hWnd); if (d2dObj == nullptr) { fprintf(stderr, "Failed to allocate memory for new object.\n"); return FALSE; } d2dObj->Initialize();
Now we initialize our Sensor object and store it in our global variable. It is important to note that “before using any COM module, you must initialize the COM library by calling the CoInitialize(NULL) function in your application. You then must close the COM library by calling the CoUninitialize() function before the application exits.” (see this MSDN link).
CoInitialize(NULL); sensorObj = new SensorObj(); if(sensorObj == nullptr) { fprintf(stderr, "Failed to allocate memory for new object.\n"); return FALSE; } sensorObj->Initialize();
Note that at the end of the main function after the message loop, you will need to call the following for clean up:
delete sensorObj; CoUninitialize();
Optionally, if you would like to enable the mouse to act as a pointer and reset the game, add this line to the initialization function right before returning. Otherwise, only touch will work and clicking on the screen will do nothing.
EnableMouseInPointer(TRUE);
Now that everything is properly initialized, let’s move on to process messages for the main window (WndProc). Create a static UINT_PTR to use for keeping track of time, this will allow the game to run continuously at around 60 frames per second using WM_TIMER. Create a POINTER_INFO object to track mouse clicks. Keep all other initializations the same.
int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; static UINT_PTR dwId = 0; POINTER_INFO pointerInfo = {};
Inside the message handler switch statements, add a handler for WM_CREATE to initialize the timer using setTimer and setting it to expire every 16 milliseconds. This ensures the game will update at 60 frames per second while staying idle the rest of the time. Then, implement the WM_TIMER handler to InvalidateRect, which will cause a WM_PAINT event to be fired. Leave the WM_COMMAND implementation as is, run the game logic using runGame in WM_PAINT. Check if the game has ended, and if so, reset the timer using KillTimer and setting dwId back to 0. Implement WM_SIZE to handle window resizes and pass the new dimensions into our Resize function. Implement the WM_POINTERUP handler to reset the game by placing the red ball at the coordinate specified in pointerInfo and restart the timer to run the game. Finally, in the WM_DESTROY handler, be sure to kill the timer.
switch (message) { case WM_CREATE: if (dwId == 0) dwId = SetTimer(hWnd, 1, 16, NULL); break; case WM_TIMER: InvalidateRect(hWnd, NULL, FALSE); break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: Add any drawing code here... if(!runGame()){ KillTimer(hWnd, dwId); dwId = 0; } EndPaint(hWnd, &ps); break; case WM_SIZE: UINT width = LOWORD(lParam); UINT height = HIWORD(lParam); d2dObj->Resize(width, height); break; case WM_POINTERUP: // Get frame id from current message if (GetPointerInfo(GET_POINTERID_WPARAM(wParam), &pointerInfo)) { d2dObj->Reset(pointerInfo.ptPixelLocation); } if (dwId == 0) dwId = SetTimer(hWnd, 1, 16, NULL); break; case WM_DESTROY: KillTimer(hWnd, dwId); dwId = 0; PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); }
Finally, implement the runGame function, which is extremely simple. Add the runGame function definition at the top under Forward declarations of functions included in this code module right before the _tWinMain function.
// Forward declarations of functions included in this code module: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); bool runGame();
Implement the runGame function by calling the GetReport function on our Sensor object, which gets the current accelerometer reading, then pass that in to the Direct2D Update function, which applies the reading to the ball’s velocity and updates state. This function returns a Boolean true or false indicating the game state as active or complete, respectively. Finally, we just call the Direct2D Render function to draw the scene and return the game’s state.
bool runGame(){ bool render = d2dObj->Update(sensorObj->GetReport()); d2dObj->Render(); return render; }
Conclusion
This sample walked you through the steps on how to create and initialize a basic sensor object from scratch (a clean Visual Studio 2012 C++ Project template), disabling its event notification, and simply reading data on demand to play a little game. We showed the bare minimum you must do to implement the Windows desktop sensor data and understand its basic COM structure and flow. It is also simple enough so that you can isolate the sensor-related implementations and just take the pieces you need to incorporate into a more complex game/application. While the Windows 8 Store App sensor API will be much easier to implement, the Windows Desktop Sensor API has the benefits of backwards compatibility to many Windows 7 applications. For more information on the differences between Windows Desktop Sensor API and Windows Store App Sensor API, please refer to this link.
Windows Store App Sensor Sample - SensorSampleApp
Windows Store app development is supported only on Windows 8. You can't develop Windows Store apps on Windows 7 or Windows Server 2012 (see the MSDN link). The Windows Store app template has built-in support for motion and orientation sensors to let C++ developers create sensor-based programs (see the MSDN link). This sample makes use of the built-in Accelerometer object to poll for sensor data in regular intervals to control the ball and move it around the screen. You don’t need to download any additional libraries to start developing a sensor application; all you need is platform sensor support.
System requirements
Step 1: Creating a new Direct2D C++ App with XAML in Visual Studio 2012
Open Visual Studio 2012, go to File -> New -> Project…, and select Installed -> Templates -> Visual C++ ->Windows Store -> Direct2D App (XAML) Visual C++ Project. Type in a project name in the Name field (e.g., SensorSampleApp) and click OK.
Once the project is created, the Direct2D framework is set up and ready to go. The main implementation file is SimpleTextRenderer.cpp. For this app, we changed it to SimpleGameRenderer.cpp and named the header file SimpleGameRenderer.h. You do not have to do so. However, if you do, be sure to update all header file references, reference classes, and constructors/destructors.
Step 2: Implementing the Game Logic
First, we will update the DirectXPage.xaml to get rid of the “Hello, XAML!” text. The game uses Direct2D to render the objects. XAML has great rendering capabilities such that it is possible to draw an ellipse on the screen as the hole in the middle, since its position is static anyway. However, one thing to remember about XAML is that it is an overlay, which means it will always render on top. In our case, since we want the ball to appear on top of the hole, this would not work for us.
We want to keep the SwapChainBackgroundPanel, but get rid of the TextBlock. We also remove the PointerMoved event trigger and simply leave PointerReleased. We use this to capture mouse clicks indicating a game reset.
Before:
<SwapChainBackgroundPanel x:Name="SwapChainPanel" PointerMoved="OnPointerMoved" PointerReleased="OnPointerReleased"> <TextBlock x:Name="SimpleTextBlock" HorizontalAlignment="Center" FontSize="42" Height="72" Text="Hello, XAML!" Margin="0,0,0,50"/> </SwapChainBackgroundPanel>
After:
<SwapChainBackgroundPanel x:Name="SwapChainPanel" PointerReleased="OnPointerReleased"> </SwapChainBackgroundPanel>
Moving on to the header file SimpleGameRenderer.h, we keep all of its public functions as is.
#include "DirectXBase.h" // This class renders simple text with a colored background. ref class SimpleGameRenderer sealed : public DirectXBase { public: SimpleGameRenderer(); // DirectXBase methods. virtual void CreateDeviceIndependentResources() override; virtual void CreateDeviceResources() override; virtual void CreateWindowSizeDependentResources() override; virtual void Render() override; // Method for updating time-dependent objects. void Update(float timeTotal, float timeDelta); // Method to change the ball position based on input events. void UpdateBallPosition(Windows::Foundation::Point deltaBallPosition); // Methods to adjust the window background color. void BackgroundColorNext(); void BackgroundColorPrevious(); // Methods to save and load state in response to suspend. void SaveInternalState(Windows::Foundation::Collections::IPropertySet^ state); void LoadInternalState(Windows::Foundation::Collections::IPropertySet^ state);
Under the private member variable section, we want to track the hole and ball positions and the ball’s velocity. Notice we also track the brushes as well as the radius. In our case they are static, but are included as reference for more complex game structures. To use the motion sensor, we track the accelerometer object itself, which is built into Windows objects under The Devices -> Sensors -> Accelerometer. The desiredReportInterval is used by the accelerometer to indicate the minimum report interval in milliseconds. This is also optional and only tracked here in case your application requires changes, such as when the application is in the background and you want to poll the accelerometer less frequently.
The backgroundColorIndex comes with the template and is the XAML overlay to change background color. In this sample we just leave it as is. The DWRITE variables were removed as the game will not be writing anything to the screen.
private: Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_holeBrush; Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_ballBrush; D2D_POINT_2F m_ballPosition; D2D_POINT_2F m_holePosition; Windows::Foundation::Point m_ballVelocity; Windows::Devices::Sensors::Accelerometer^ accelerometer; uint32 desiredReportInterval; bool m_renderNeeded; int m_backgroundColorIndex; float m_ballRadius; float m_holeRadius; };
Now to the main implementation file SimpleGameRenderer.cpp. To use the built-in Windows sensor objects, just use the Accelerometer namespace:
using namespace Windows::Devices::Sensors;
Leave the BackgroundColors function alone. As we mentioned earlier, that comes with the Visual Studio project template and is the XAML overlay to change background colors.
We will start with the Constructor and set the renderNeeded to true indicating the game is active. Set backgroundColorIndex to the first color in the set, as default. Set the initial velocity to (0, 0). Set the radius for the hole and ball to 30 and 25, respectively.
SimpleGameRenderer::SimpleGameRenderer() : m_renderNeeded(true), m_backgroundColorIndex(0), m_ballVelocity(0.f, 0.f), m_ballRadius(25.f), m_holeRadius(30.f) { }
Next, we will create the Device Independent resources and Device Dependent resources. Basically, device independent resources always reside on the CPU, whereas device dependent resources can be associated with a GPU when available, potentially increasing your application’s performance through hardware acceleration. Generally, the device independent resources include Direct2D Factory and Geometry, whereas device dependent resources include the Direct2D Brush, Layer, and Render Target. For more on the differences between device independent and dependent resources, please refer to this MSDN link.
Starting with creating the Device Independent Resources, we get the accelerometer object and set the report interval. As with the Windows Desktop version of the app, there are two ways to read sensor data: by setting a ReadingChanged event handler or with the GetCurrentReading function. In our case, we use the latter function as it is preferred to periodically poll sensor data and update UI at our own specified frame rate. You might think that setting reportInterval to 0 will disable sensor activity when it is not needed. Instead, it returns the sensor to its default report interval, which varies from system to system. It is good practice to explicitly establish a desired Report Interval as this informs the sensor driver to allocate resources to satisfy subsequent polling requests (see this MSDN link). For more information on Report Intervals, please refer to this MSDN link. Here we also initialize the ball and hole positions.
void SimpleGameRenderer::CreateDeviceIndependentResources() { DirectXBase::CreateDeviceIndependentResources(); //@ww: this gets the accelerometer object, and sets the report interval to the min report interval for the accelerometer object, but not to be more frequent than 16 ms. accelerometer = Accelerometer::GetDefault(); uint32 minReportInterval = accelerometer->MinimumReportInterval; desiredReportInterval = minReportInterval > 16 ? minReportInterval : 16; accelerometer->ReportInterval = desiredReportInterval; m_holePosition.x = 0.f; m_holePosition.y = 0.f; m_ballPosition.x = 300.f; m_ballPosition.y = 220.f; }
Moving on to creating Device Dependent Resources, we create the brushes to paint the hole and ball.
void SimpleGameRenderer::CreateDeviceResources() { DirectXBase::CreateDeviceResources(); //@ww: create the brushes to paint ball and hole DX::ThrowIfFailed( m_d2dContext->CreateSolidColorBrush( ColorF(ColorF::Black), &m_holeBrush ) ); DX::ThrowIfFailed( m_d2dContext->CreateSolidColorBrush( ColorF(ColorF::Red), &m_ballBrush ) ); }
If there were any Window Size Dependent resources, you should initialize them under the CreateWindowSizeDependentResources function. For simplicity’s sake, our application does not handle change in window size, which can happen when you snap two windows side by side with the Windows Modern UI, or when there is a change in the orientation. For more info on Windows size dependent resources, please refer to this MSDN link.
The SimpleGameRenderer Update function is called periodically to update game status by reading the values of the accelerometer sensor and applying them to the ball’s velocity. We first do a check for whether the ball is in the hole. If so, set renderNeeded to false and end the game. Otherwise, read the accelerometer data using the GetCurrentReading function on the accelerometer. This returns an AccelerometerReading object from which you can obtain the AccelerationX and AccelerationY data. Check whether the ball has hit a “wall” or not and update the velocity accordingly. Then apply the accelerometer reading to the velocity and update the ball’s position.
void SimpleGameRenderer::Update(float timeTotal, float timeDelta) { (void) timeTotal; // Unused parameter. (void) timeDelta; // Unused parameter. // @ww: updates the ball position if(abs(m_ballPosition.x - m_holePosition.x) < (m_holeRadius - m_ballRadius) && abs(m_ballPosition.y - m_holePosition.y) < (m_holeRadius - m_ballRadius)){ //detects if ball is in hole, if so, end game by setting renderNeeded to false m_renderNeeded = false; m_ballPosition.x = m_holePosition.x; m_ballPosition.y = m_holePosition.y; } else{ //detects if ball has hit a "wall", if so change velocity to opposite direction if(m_ballPosition.x <= (0-(m_windowBounds.Width/2-m_ballRadius))){ //hit west wall m_ballPosition.x = 0-(m_windowBounds.Width/2-m_ballRadius); m_ballVelocity.X = abs(m_ballVelocity.X)/1.2; } else if(m_ballPosition.x >= (m_windowBounds.Width/2-m_ballRadius)){ //hit east wall m_ballPosition.x = m_windowBounds.Width/2-m_ballRadius; m_ballVelocity.X = 0-abs(m_ballVelocity.X)/1.2; } if(m_ballPosition.y <= (0-(m_windowBounds.Height/2-m_ballRadius))){ //hit north wall m_ballPosition.y = 0-(m_windowBounds.Height/2-m_ballRadius); m_ballVelocity.Y = 0-abs(m_ballVelocity.Y)/1.2; } else if(m_ballPosition.y >= (m_windowBounds.Height/2-m_ballRadius)){ //hit south wall m_ballPosition.y = m_windowBounds.Height/2-m_ballRadius; m_ballVelocity.Y = abs(m_ballVelocity.Y)/1.2; } //@ww: Create new accelerometer reading object to get current reading AccelerometerReading^ reading = accelerometer->GetCurrentReading(); if (reading != nullptr) { //update ball velocity based on acceleromter reading m_ballVelocity.X += reading->AccelerationX; m_ballVelocity.Y += reading->AccelerationY; } //@ww: update ball position by applying velocity m_ballPosition.x += m_ballVelocity.X; m_ballPosition.y -= m_ballVelocity.Y; } // Add code to update time dependent objects here. }
The Render function is solely responsible for drawing the hole and ball based on their respective positions via DirectX*. For simplicity sake, we will not go into detail on the DirectX rendering function. For more info on the Direct2D API, please refer to this MSDN link.
The UpdateBallPosition function is called when the user touches the screen or clicks the mouse. This indicates a game reset, so all we do is take the coordinates passed in and set that to the ball’s position as well as update its velocity to (0, 0).
We leave BackgroundColorNext and BackgroundColorPrevious functions as is to handle the XAML overlay for changing background colors.
The SaveInternalState and LoadInternalStates are useful to stop rendering and polling accelerometer data when your application is in the background and to save the game’s state. In our case, setting the renderNeeded to false will essentially “pause” the game. There are, of course, even more power efficient ways of handling this, but that is another topic of discussion.
Step 3: Putting it all together
At this point you might be wondering: “where is my main function?” This section provides a quick overview of the Windows Store app and how it all works together. As well as a few final touches to finish up the game.
At the top most level, there is the App.xaml.cpp. Notice from the default generated Microsoft Visual Studio comments that this is the logical equivalent of main or WinMain. It makes a call to InitalizeComponent and adds a handler for Suspend.
Once the application is launched, the App OnLaunched function gets invoked, and here is where the DirectXPage gets created. It looks for a previous state and if there is one, loads it by calling the DirectXPage LoadInternalState handler. Then it places the page in the current window and ensures it is active.
Moving on to the DirectXPage.xaml.cpp implementation file. This also makes a call to initialize its components and create a new SimpleGameRenderer object: m_renderer, to utilize all of the game logic and implementations we put into it.
We leave everything as is in this constructor. Remove the OnPointerMoved function as we will not need to track mouse/touch movements for our game (of course, make sure to also remove it from the header file).
Implement the OnPointerReleased function to take the input detected and restart the game by calling the SimpleGameRenderer’s UpdateBallPosition function. Set renderNeeded to true so that the game will start rendering again.
void DirectXPage::OnPointerReleased(Object^ sender, PointerRoutedEventArgs^ args) { //@ww: on input detected, restart the game and place the ball where user clicked/touched auto currentPoint = args->GetCurrentPoint(nullptr); Windows::Foundation::Point delta( currentPoint->Position.X, currentPoint->Position.Y ); m_renderer->UpdateBallPosition(delta); m_renderNeeded=true; }
The other event handler of interest is the OnRendering function. As long as the game is active and renderNeeded is true, update the timer, call the SimpleGameRenderer’s Update function to get accelerometer readings and update game state. Finally, call SimpleGameRenderer’s Render and Present functions to draw the scene. Take out the line that sets renderNeeded to false. This was originally there because the project template was event driven and handled mouse clicks and movements to move text around the screen. In our case, we want the game to continuously render until the game is over.
void DirectXPage::OnRendering(Object^ sender, Object^ args) { if (m_renderNeeded) { m_timer->Update(); m_renderer->Update(m_timer->Total, m_timer->Delta); m_renderer->Render(); m_renderer->Present(); } }
Leave all other event handlers as is.
Conclusion
You will notice that with Windows Store Apps, you have a wealth of templates to choose from. Each template does a great job of setting up an encapsulated framework to let you focus more on your game/application logic without having to spend too much time setting up the environment for Direct2D, Sensor objects, and the likes. As in this example, the SimpleGameRenderer that hosts all of our game logic and implementations simply extends the DirectX object, which is already preconfigured with most of your Direct2D needs so you can hit the ground running. The Accelerometer object is there and ready for you to use simply by declaring to use its namespace. The only caveat is that Windows Store Apps are not backwards compatible to Windows 7. For more information on the differences between Windows Desktop and Windows Store applications, refer to this link.
Resources
Intel Software Network / Intel Developer Zone (ISN/IDZ)
Ultrabook™ and Tablet Windows* 8 Sensors Development Guide: http://software.intel.com/en-us/articles/ultrabook-and-tablet-windows-8-sensors-development-guide
Detecting Ultrabook Sensors: http://software.intel.com/en-us/blogs/2012/07/26/detecting-ultrabook-sensors
Microsoft Developer Network (MSDN)
Windows Software Development Kit (SDK) for Windows 8: http://msdn.microsoft.com/en-us/windows/desktop/hh852363.aspx
Visual Studio 2012: http://www.microsoft.com/visualstudio/eng/downloads
Sensor API: http://msdn.microsoft.com/en-us/library/windows/desktop/dd318953(v=vs.85).aspx
Retrieving Sensor Data Values: http://msdn.microsoft.com/en-us/library/windows/desktop/dd318962(v=vs.85).aspx
General Requirements for Application Development: http://msdn.microsoft.com/en-us/library/windows/desktop/dd318930(v=vs.85).aspx
About the Sensor API: http://msdn.microsoft.com/en-us/library/windows/desktop/dd318913(v=vs.85).aspx
Retrieving a Sensor Object: http://msdn.microsoft.com/en-us/library/windows/desktop/dd318960(v=vs.85).aspx
Sensor Categories, Types, and Data Fields: http://msdn.microsoft.com/en-us/library/windows/desktop/dd318969(v=vs.85).aspx
Direct2D: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370990(v=vs.85).aspx
Set Up My Visual C++ Project: http://msdn.microsoft.com/en-us/library/windows/desktop/ms753736(v=vs.85).aspx
Getting started with Windows Store apps: http://msdn.microsoft.com/en-us/library/windows/apps/br211386.aspx
Motion and device orientation (Windows Store apps): http://msdn.microsoft.com/en-us/library/windows/apps/jj155768.aspx
Resources Overview: http://msdn.microsoft.com/en-us/library/windows/desktop/dd756757(v=vs.85).aspx
Accelerometer.GetCurrentReading | getCurrentReading method: http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.sensors.accelerometer.getcurrentreading.aspx
Accelerometer.ReportInterval | reportInterval property: http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.sensors.accelerometer.reportinterval.aspx
Supporting screen orientation (DirectX and C++): http://msdn.microsoft.com/en-us/library/windows/apps/jj262112.aspx
Direct2D API Overview: http://msdn.microsoft.com/en-us/library/windows/desktop/dd317121(v=vs.85).aspx