My CodeProject Entries
In late 2009, Ian Backlund and I released a game we were working on that showcased Windows Touch. You can download the sample associated with this project on the Code Gallery page for Windows Touch Game Programming in DirectX 10.
The following post is just a re-release of the documentation.
Game Overview
Gameplay is very simple: the player uses rotation gestures to rotate pieces of a puzzle and uses a pinch or zoom gesture to trigger pieces to insert into the stack. Keyboard input is supported using the keys on the numeric keypad. The challenge is to finish placing all the pieces before time runs out.
The following screen shows the game being played:
A rotate gesture spins the outer gear. Pinching or zooming when the gear is in place causes the outer gear to attach to the inner gear. If time runs out, the game presents a Game Over screen.
How It Works
The game has three key components:
- Rendering
- Gameplay
- Manipulations
The rendering portion of the game is powered by the Skinnable Mesh sample, which incorporates the DXUT utility and is accessed using the CogResourceLoader
. The gameplay is controlled through a custom interface, the Game Manager. Manipulations are driven using the Windows Touch Manipulation Processor.
The following diagram illustrates interaction for the various classes.
Rendering Details
The CogResourceLoader
class sets up the cogs, textures, camera, and lighting. Each type of cog (rusty, greasy, gold, and so on) has its own rendering and lighting settings that are handled through utility functions in that class.
Cog Meshes
The components of the cogs were all created in 3D Studio Max. Portions of the components are added or removed to create the shape geometry. Because the project targets DirectX 10, which handles meshes differently than DirectX 9, the cog components (meshes) must be converted before they can be rendered. This operation is handled by the D3DXMeshToD3DX10Mesh
when the game loads.
Cog Textures
Textures are created to dress up the meshes to create an interesting variety of shapes. Shaders are used to render the textures on the cogs.
Game Camera
The camera is controlled programmatically to zoom out as gears are successfully aligned and put together.
Lighting
A flat light is placed behind the camera to give the textures a shiny appearance.
Rendering Details
The CogResourceLoader
class sets up the cogs, textures, camera, and lighting. Each type of cog (rusty, greasy, gold, and so on) has its own rendering and lighting settings that are handled through utility functions in that class.
Cog Meshes
The components of the cogs were all created in 3D Studio Max. Portions of the components are added or removed to create the shape geometry. Because the project targets DirectX 10, which handles meshes differently than DirectX 9, the cog components (meshes) must be converted before they can be rendered. This operation is handled by the D3DXMeshToD3DX10Mesh
when the game loads.
Cog Textures
Textures are created to dress up the meshes to create an interesting variety of shapes. Shaders are used to render the textures on the cogs.
Game Camera
The camera is controlled programmatically to zoom out as gears are successfully aligned and put together.
Lighting
A flat light is placed behind the camera to give the textures a shiny appearance.
Gameplay Details
The GameManager
class orchestrates the game user interface, settings, sound, state, and mode.
User Interface and Menus
The user interface (UI) is handled through the GUIManager
class. Although the GameManager
sets up the object interfaces for DirectX, the GUIManager
class uses these interfaces to render the various buttons that the user sees when starting the game.
The GUIManager
is responsible for positioning and controlling game menu objects. The GUIManager
has an associated state (Main Menu, Loading, Solving a Stack, and so on) that is switched through based on the user actions. When the application is in a mode where there are buttons, the GUIManager
positions the buttons and handles the actions when the buttons are pressed. The GUIManager
also hides menu screens when the user plays the game.
The following code shows how the GUIManager
class renders the main menu of the game.
void GUIManager::EnableMainMenu()
{
m_GameState = MainMenu;
m_pDiffuseVariable->SetResource( m_pTextureMainMenuRV );
m_GameUI.GetButton(IDC_BEGINGAME)->SetEnabled(true);
m_GameUI.GetButton(IDC_BEGINGAME)->SetVisible(true);
m_GameUI.GetButton(IDC_TOGGLEFULLSCREEN)->SetEnabled(true);
m_GameUI.GetButton(IDC_TOGGLEFULLSCREEN)->SetVisible(true);
m_GameUI.GetButton(IDC_CREDITS)->SetEnabled(true);
m_GameUI.GetButton(IDC_CREDITS)->SetVisible(true);
m_GameUI.GetButton(IDC_PRACTICE)->SetEnabled(true);
m_GameUI.GetButton(IDC_PRACTICE)->SetVisible(true);
m_GameUI.GetButton(IDC_CHANGEDEVICE)->SetEnabled(true);
m_GameUI.GetButton(IDC_CHANGEDEVICE)->SetVisible(true);
m_GameUI.GetButton(IDC_MAINMENU)->SetEnabled(false);
m_GameUI.GetButton(IDC_MAINMENU)->SetVisible(false);
m_GameUI.GetButton(IDC_CONTINUEGAME)->SetEnabled(false);
m_GameUI.GetButton(IDC_CONTINUEGAME)->SetVisible(false);
}
Gameplay
The GameManager
controls rendering of the game elements during play. When the user starts a game level, the GameManager
sets up the stack of cogs and then positions the projection matrix, which controls what the user sees. The cog position is controlled by the GameManager
using the GameManager::SpinCog
method and GameManager::PushCog
method which interacts with the Cog
objects that are stored in a CogStack
class. The CogStack
class contains a stack of Cog
objects. The Cog
class controls rendering for a particular cog and stores the rotation and position state. When a cog is added to the stack for the user to play, the cog is initialized with a random rotation amount in the CogStack
class and random component pieces using the Cog::CreatePatternEx
method. The following code shows how the stack is initialized in the CogStack
class.
HRESULT CogStack::CreateCogStack
(int numberOfLevels, int difficulty, ID3D10Device* pd3dDevice,
CogResourceLoader* p_CogResourceLoader)
{
HRESULT hr = S_OK;
m_pd3dDevice = pd3dDevice;
m_CogResourceLoader = p_CogResourceLoader;
m_numOfLevels = numberOfLevels;
firstCog = new Cog();
firstCog->CreateCogPatternEx(); firstCog->LoadMesh( pd3dDevice, Rusty,
m_CogResourceLoader );
firstCog->setPosition(D3DXVECTOR3(0,1,0));
firstCog->setScale(D3DXVECTOR3(1,1,1));
firstCog->setCameraPosition
(D3DXVECTOR3(0,0,0));
float lastCameraY = 0;
float backdistance2 =0;
Cog* lastCog = firstCog;
float scale = 1;
float position = 1;
int practiceCogNumber = 0;
for(int i =1;i<numberOfLevels; i++)
{
Cog* tempCog = new Cog();
tempCog->CreateCogPatternEx();
tempCog->CreateCogPatternInt(lastCog->exteriorSpokes);
int cogTypeNumber = (rand()*5) / (RAND_MAX);
if(difficulty == 0) cogTypeNumber = practiceCogNumber++;
switch(cogTypeNumber)
{
case 0:
tempCog->LoadMesh( pd3dDevice, Normal, m_CogResourceLoader );
break;
case 1:
tempCog->LoadMesh( pd3dDevice, Gold, m_CogResourceLoader );
break;
case 2:
tempCog->LoadMesh( pd3dDevice, Rusty, m_CogResourceLoader );
break;
case 3:
tempCog->LoadMesh
( pd3dDevice, Greased, m_CogResourceLoader );
break;
case 4:
tempCog->LoadMesh
( pd3dDevice, Spring,
m_CogResourceLoader ); break;
}
position = m_positionFactor * position;
tempCog->setPosition(D3DXVECTOR3(0,position,0));
scale = m_scaleFactor * scale;
tempCog->setScale(D3DXVECTOR3(scale,scale,scale));
float amount = static_cast<float>(rand());
tempCog->changeRotation(D3DXVECTOR3(amount, 0, 0));
FLOAT nextCogPosition = m_positionFactor * position;
FLOAT lastCogPosition = lastCog->getPosition();
FLOAT thisCogCameraPosition = nextCogPosition - lastCogPosition + 1;
tempCog->setCameraPosition(D3DXVECTOR3(0,thisCogCameraPosition,0));
lastCog->nextCog = tempCog;
lastCog = tempCog;
float progress = (float)i/(float)numberOfLevels;
float progressOutOf100 = progress*100;
}
currentCog = firstCog->nextCog;
D3DXMATRIX mView;
D3DXMatrixIdentity( &mView );
D3DXVECTOR3 Eye( 0.0f, 0.0f, -2.25f );
D3DXVECTOR3 At( 0.0f, 0.0f, 0.0f );
D3DXVECTOR3 Up( 0.0f, 1.0f, 0.0f );
D3DXMatrixLookAtLH( &mView, ¤tCog->getCameraPosition(), &At, &Up );
m_CogResourceLoader->setViewMatrix(mView);
m_CameraPos = currentCog->getCameraPosition();
return hr;
}
The CogStack
class handles puzzle completion. The CogStack
tests a cog’s rotation when a cog is “pushed” using the GameManager
class. A cog is considered solved if the rotation value for the cog is between 5 degrees and 355 degrees (this can be reduced to increase difficulty). The following code shows the logic used to test whether a cog is positioned correctly in the CogStack
class.
bool CogStack::PushCog()
{
if(animatingStack)
return false;
double currentRotationInRadians = currentCog->getRotation();
int currentRotationInDegrees =
static_cast<int>(D3DXToDegree(currentRotationInRadians));
int rotation = currentRotationInDegrees % 360;
if(rotation < 5 || rotation > 355)
{
animatingStack = true;
currentCog->setRotation(D3DXVECTOR3(0.0,0.0,0.0));
FLOAT firstCogPos = firstCog->getPosition();
FLOAT currentCogPos = currentCog->getPosition();
m_distanceToTravel = currentCogPos - firstCogPos;
if(currentCog->nextCog)
m_cameraDistanceToTravel = currentCog->nextCog->
getCameraPosition().y - currentCog->getCameraPosition().y;
m_CurrentCogCount++;
droppingCog = currentCog;
currentCog = currentCog->nextCog;
return true;
}
return false;
}
When a cog is successfully pushed into the completed stack, the camera position is reset by setting the m_cameraDistanceToTravel
member of the CogStack
class and setting the CogStack
animation state, set in the animatingStack
member, to TRUE
.
When a level is completed, the CogStack
class is destroyed and then a new stack is created for the next level. For performance reasons, textures and geometry are cached.
Windows Touch Integration
Windows Touch integration is one of the simplest components of the game. The CManipulationEventSink
class implements the _ManipulationEventSInk
interface to enable control of the current cog using Windows Touch. The IntertManipUtil
class hooks the CManipulationEventSink
implementation to the game window and acts as an intermediary between Windows Touch and the game.
Hooking the Event Sink
The event sink is hooked when the InertManipUtil::Initialize
method is called from Main
, which happens when the application starts. RegisterTouchWindow
is called to the application window and the event sink is notified so that the ManipulationStarted
, ManipulationUpdate
, and ManipulationCompleted
events are registered. WM_TOUCH
messages are forwarded to the InertManipUtil
class so they can be passed to the manipulation processor. The following code shows how the WM_TOUCH
events are passed to the manipulation processor in the InertManipUtil
class.
void CALLBACK InertManipUtil::TouchMessageProc
(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){
UINT cInputs = LOWORD(wParam);
PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
if (NULL != pInputs)
{
if (GetTouchInputInfo((HTOUCHINPUT)lParam,
cInputs,
pInputs,
sizeof(TOUCHINPUT)))
{
for (UINT i=0; i<cInputs; i++){
if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN){
m_spIManipProc->ProcessDown(pInputs[i].dwID,
(FLOAT)pInputs[i].x, (FLOAT)pInputs[i].y);
}
if (pInputs[i].dwFlags & TOUCHEVENTF_MOVE){
m_spIManipProc->ProcessMove(pInputs[i].dwID,
(FLOAT)pInputs[i].x, (FLOAT)pInputs[i].y);
}
if (pInputs[i].dwFlags & TOUCHEVENTF_UP){
m_spIManipProc->ProcessUp(pInputs[i].dwID,
(FLOAT)pInputs[i].x, (FLOAT)pInputs[i].y);
}
}
} else {
}
}else {
}
if (!CloseTouchInputHandle((HTOUCHINPUT)lParam)) {
}
delete [] pInputs;
}
Spinning and Pushing Cogs
Cogs are spun by using rotation values from ManipulationDelta
and then triggering the GameManager::SpinCog
method through the InertManipUti
l’s reference to the GameManager
class. The following code shows how this is done in CManipulationEventSink
.
if (rotationDelta != 0){
if(true || m_fSpin){
m_pGameManager->SpinCog(rotationDelta);
}else{
}
lastDelta = rotationDelta;
}
if (expansionDelta > 200){
if (m_pGameManager->PushCog()){
m_pGameManager->AddScore(200);
}
}
if (expansionDelta < -200 ){
if (m_pGameManager->PushCog()){
m_pGameManager->AddScore(200);
}
}
Pinch and zoom amounts can be changed to alter the sensitivity of the game to gestures.
My name is Gus Class, I'm 29 years old, and I have been passionate about computers for my entire life. My first computer was an Apple IIgs and I begged my parents to get a PC (a 386!) around the time I had turned 7 or 8 which they begrudgingly caved in on. I came online for the first time in the early 90s through the Prodigy service which led me to the BBS scene and opened doors for learning the ins and outs of computing. I taught myself how to program in my teens, took vocational classes in high school on network administration through a magnet program, and earned a bachelor's degree in Computing and Software Systems through the University of Washington in 2003. In an effort to round out my education, I earned an MBA in 2008 while working full time at Microsoft.
I am currently a Content Project Manager at Microsoft where I have worked for the past 5 years. My work there has included writing sample code, creating compelling documentation to help developers adopt Windows features, managing sites, connecting with the developer audience through social media, mentoring and growing new hires and peers, developing tools to enhance productivity, and attending conferences as a Microsoft representative.
I enjoy a plethora of hobbies including rock climbing, DJing, electronic music production, recreational programming, creating web sites, playing and creating video games, and hanging out with social media mavens.