Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Windows Touch Game Programming in DirectX 10

, 23 Feb 2011 Ms-PL
Rate this:
Please Sign up or sign in to vote.
Windows Touch Game Programming in DirectX 10

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;
   
      //Set the background texture
      m_pDiffuseVariable->SetResource( m_pTextureMainMenuRV );

      //Turn on main menu buttons
      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;


      //Set the convenience pointer
      m_pd3dDevice = pd3dDevice;

      m_CogResourceLoader = p_CogResourceLoader;

      m_numOfLevels = numberOfLevels;

      firstCog = new Cog();
      firstCog->CreateCogPatternEx();//Counterintuitively, 
      //the patterns must be set up before calling loadMesh
      firstCog->LoadMesh( pd3dDevice, Rusty, 
      m_CogResourceLoader );//device, type, resouceLoader

      firstCog->setPosition(D3DXVECTOR3(0,1,0));
      firstCog->setScale(D3DXVECTOR3(1,1,1));
      firstCog->setCameraPosition
      (D3DXVECTOR3(0,0,0));//The camera is not used for the first cog

      float lastCameraY = 0;
      float backdistance2 =0;

      Cog* lastCog = firstCog;

      float scale = 1;
      float position = 1;

      int practiceCogNumber = 0;

      //Create a new cog for each level.
      for(int i =1;i<numberOfLevels; i++)
      {
            Cog* tempCog = new Cog();

            //Create the exterior cog pattern
            tempCog->CreateCogPatternEx();

            //Create the interior pattern for this cog
            tempCog->CreateCogPatternInt(lastCog->exteriorSpokes);

            //Pick the type of cog randomly
            int cogTypeNumber = (rand()*5) / (RAND_MAX);//this could be tweeked 
		//to have weirder cog types appear more frequently in later levels

            if(difficulty == 0)//If the game is in practice mode
                  cogTypeNumber = practiceCogNumber++;//One of each cog type

            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 );//Spring cog type = Glow cog 
                        break;
            }

            //Set the initial position of the cog
            position = m_positionFactor * position;
            tempCog->setPosition(D3DXVECTOR3(0,position,0));

            //Set the scale of the cog
            scale = m_scaleFactor * scale;
            tempCog->setScale(D3DXVECTOR3(scale,scale,scale));

            //Randomly rotate the cog
            float amount = static_cast<float>(rand());
            tempCog->changeRotation(D3DXVECTOR3(amount, 0, 0));//Amount first

            //Determine the position the camera should be in when 
            //the player is turning this cog

            FLOAT nextCogPosition = m_positionFactor * position;
            FLOAT lastCogPosition = lastCog->getPosition();
            FLOAT thisCogCameraPosition = nextCogPosition - lastCogPosition + 1;

            tempCog->setCameraPosition(D3DXVECTOR3(0,thisCogCameraPosition,0));

            //Set up the linking pointer for the last cog
            lastCog->nextCog = tempCog;
            lastCog = tempCog;

            float progress = (float)i/(float)numberOfLevels;
            float progressOutOf100 = progress*100;
      }

      currentCog = firstCog->nextCog;

    // Initialize the view matrix
    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, &currentCog->getCameraPosition(), &At, &Up );

      m_CogResourceLoader->setViewMatrix(mView);

      //This variable tracks the camera position matrix
      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 already animating a cog push, return false
      if(animatingStack)
            return false;

      //Get the current rotation, measured in radians
      double currentRotationInRadians = currentCog->getRotation();

      //Convert radians to degrees 
      int currentRotationInDegrees = 
	static_cast<int>(D3DXToDegree(currentRotationInRadians));

      //Get the remainder of 360 degrees. 
      //This is the amount that the cog is rotated from 0 degrees
      int rotation = currentRotationInDegrees % 360;

      //Determine whether the cogs line up
      if(rotation < 5 || rotation > 355)
      {
            //The cogs are lined up: lock out user input and 
            //begin an animation to drop the stack

            //The commented out code below indicates where inertia could be used
            //To use this, initialize inertia in createcogstack();
            /*
            switch(currentCog->m_CogType)
            {
                  case Normal:
                g_cInertManip.setInertiaSettings(g_cInertManip.standard);
                break;
            case Gold:
                g_cInertManip.setInertiaSettings(g_cInertManip.standard);
                break;
            case Rusty:
                g_cInertManip.setInertiaSettings(g_cInertManip.rust);
                break;
            case Greased:
                g_cInertManip.setInertiaSettings(g_cInertManip.grease);
                break;
                  case Spring:
                g_cInertManip.setInertiaSettings(g_cInertManip.goo);
                break;
            }
            */

            animatingStack = true;
           
            //Rotate the cog to zero
            currentCog->setRotation(D3DXVECTOR3(0.0,0.0,0.0));

            //Get and store the distance the cog needs to travel 
            FLOAT firstCogPos = firstCog->getPosition();
            FLOAT currentCogPos = currentCog->getPosition();
            m_distanceToTravel = currentCogPos - firstCogPos;

            //Get and store the distance the camera needs to travel
            if(currentCog->nextCog)
                  m_cameraDistanceToTravel = currentCog->nextCog->
		getCameraPosition().y - currentCog->getCameraPosition().y;

            //Increment the score
            m_CurrentCogCount++;

            //Set the dropping cog to the current cog for animation
            droppingCog = currentCog;
            //Set the current cog to the next cog so that it can move while it drops
            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 {
          //GetLastError() and error handling
        }
      }else {
        //Error handling, presumably out of memory
      }
      if (!CloseTouchInputHandle((HTOUCHINPUT)lParam)) {
        //Error handling
      }
      delete [] pInputs;
}

Spinning and Pushing Cogs

Cogs are spun by using rotation values from ManipulationDelta and then triggering the GameManager::SpinCog method through the InertManipUtil’s reference to the GameManager class. The following code shows how this is done in CManipulationEventSink.

    if (rotationDelta != 0){
        if(true || m_fSpin){
            //Spin the cog
            m_pGameManager->SpinCog(rotationDelta);                          
        }else{        
          
        }

        lastDelta = rotationDelta;
    }

    //Zoom Gesture
    if (expansionDelta > 200){
        if (m_pGameManager->PushCog()){
            m_pGameManager->AddScore(200);
        }
    }    
    //Pinch Gesture - Enable pinch gear insertion
    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.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

gclass
Web Developer Microsoft
United States United States
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.
Follow on   Twitter

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 23 Feb 2011
Article Copyright 2011 by gclass
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid