Part2:Example

来源:互联网 发布:mac os iso光盘镜像 编辑:程序博客网 时间:2024/05/16 14:54

来自:http://www.codeproject.com/Articles/28608/TetroGL-An-OpenGL-Game-Tutorial-in-C-for-Win32-Pla

After all those explanations, it is time for a concrete example to see how we will use all those classes. The example will be quite simple and far from a complete game, but it shows the principles. The purpose is to have an animated character (a knight) that can be controlled through the direction keys. It moves in a simple scene: grass with some trees on it, in an isometric view. There is no collision detection yet, which means that the knight can move through the trees. Another thing that is not implemented is the order in which the sprites are drawn: the knight will always be drawn on top of the scene, no matter where he is, which is wrong in some situations (if he is behind a tree, the tree should be drawn on top of the knight). This is left as an exercise to the reader :).

All the code will be implemented in the CMainWindowclass. Let's first add some member variables in this class:

  // The image for the grass.  TImagePtr m_pGrassImg;  // Images for the trees  TImagePtr m_pTreesImg[16];  // The animated sprite of the knight  CAnimatedSprite* m_pKnightSprite;  // Which keys are currently pressed  bool m_KeysDown[4];  // The last direction of the knight  std::string m_strLastDir;

We first declare some TImagePtr which will hold several images that will be drawn (grass and trees). We then declare theCAnimatedSprite which will be used to draw the knight. We finally have an array of 4 booleans to store the current state of the direction keys and astring that contains the current direction of the knight. Those variables are initialized in the constructor of the main window class:

  // Load the grass image and set the color key.  m_pGrassImg = CImage::CreateImage("GrassIso.bmp");  m_pGrassImg->GetTexture()->SetColorKey(0,128,128);  // Load all the 'walk' animations for the knight.  m_pKnightSprite = new CAnimatedSprite;  CAnimFileLoader fileLoader1("KnightWalk.bmp", 8, 96, 96);  CTextureManager::GetInstance()->GetTexture("KnightWalk.bmp")    ->SetColorKey(111, 79, 51);  m_pKnightSprite->AddAnimation("WalkE",      fileLoader1.GetAnimation(0,7));  m_pKnightSprite->AddAnimation("WalkSE",      fileLoader1.GetAnimation(8,15));  m_pKnightSprite->AddAnimation("WalkS",      fileLoader1.GetAnimation(16,23));  m_pKnightSprite->AddAnimation("WalkSW",      fileLoader1.GetAnimation(24,31));  m_pKnightSprite->AddAnimation("WalkW",      fileLoader1.GetAnimation(32,39));  m_pKnightSprite->AddAnimation("WalkNW",      fileLoader1.GetAnimation(40,47));  m_pKnightSprite->AddAnimation("WalkN",      fileLoader1.GetAnimation(48,55));  m_pKnightSprite->AddAnimation("WalkNE",      fileLoader1.GetAnimation(56,63));  // Load all the 'pause' animations for the knight.  CAnimFileLoader fileLoader2("KnightPause.bmp", 8, 96, 96);  CTextureManager::GetInstance()->GetTexture("KnightPause.bmp")    ->SetColorKey(111, 79, 51);  m_pKnightSprite->AddAnimation("PauseE",      fileLoader2.GetAnimation(0,7));  m_pKnightSprite->AddAnimation("PauseSE",      fileLoader2.GetAnimation(8,15));  m_pKnightSprite->AddAnimation("PauseS",      fileLoader2.GetAnimation(16,23));  m_pKnightSprite->AddAnimation("PauseSW",      fileLoader2.GetAnimation(24,31));  m_pKnightSprite->AddAnimation("PauseW",      fileLoader2.GetAnimation(32,39));  m_pKnightSprite->AddAnimation("PauseNW",      fileLoader2.GetAnimation(40,47));  m_pKnightSprite->AddAnimation("PauseN",      fileLoader2.GetAnimation(48,55));  m_pKnightSprite->AddAnimation("PauseNE",      fileLoader2.GetAnimation(56,63));  m_pKnightSprite->PlayAnimation("PauseE");  for (int i=0; i<4; i++)    m_KeysDown[i] = false;  // Set the initial direction to the east.  m_strLastDir = "E";  m_pKnightSprite->SetPosition(350,250);

This looks like a lot of code but we need to load quite a bunch of animations for our knight: 2 animations (walk and pause) for each direction (8 different directions). We are using a new class here: theCAnimFileLoader class. It is a simple helper class to easily load an image list from a file. It takes the file name, the number of images per row, the width and the height of an image as parameters in the constructor and you can retrieve an image list later by simply specifying the start index and the stop index of images in the file (it returns aCImageList object). If you now look at the code, we first load the grass image and specify its color key, then we load all the 'walk' animations for our knight. Each animation name depends on the direction, e.g. for the 'walk' east direction, the animation name is "WalkE". This will be used later to play a specific animation. We then specify that the default animation is the "PauseE" animation.

Let's now look at how we handle the events when a key is pressed. This is done in theProcessEvent function:

void CMainWindow::ProcessEvent(UINT Message, WPARAM wParam, LPARAM lParam){  switch (Message)  {  // Quit when we close the main window  case WM_CLOSE :    PostQuitMessage(0);    break;  case WM_SIZE:    OnSize(LOWORD(lParam),HIWORD(lParam));    break;  case WM_KEYDOWN :    switch (wParam)    {    case VK_UP:      m_KeysDown[0] = true;      break;    case VK_DOWN:      m_KeysDown[1] = true;      break;    case VK_LEFT:      m_KeysDown[2] = true;      break;    case VK_RIGHT:      m_KeysDown[3] = true;      break;    }    UpdateAnimation();    break;  case WM_KEYUP :    switch (wParam)    {    case VK_UP:      m_KeysDown[0] = false;      break;    case VK_DOWN:      m_KeysDown[1] = false;      break;    case VK_LEFT:      m_KeysDown[2] = false;      break;    case VK_RIGHT:      m_KeysDown[3] = false;      break;    }    UpdateAnimation();    break;  }}

As you can see, we handle the WM_KEYDOWN and theWM_KEYUP messages, which correspond to a key pressed and a key released respectively. When such message is sent, theWPARAM contains the code of the key which is pressed or released. We simply then set or reset the flag in our array to specify the state of the corresponding key (so, the first element in the array corresponds to the up key, the second to the down key, ...). We then call the UpdateAnimation function:

void CMainWindow::UpdateAnimation(){  // First check if at least one key is pressed  bool keyPressed = false;  for (int i=0; i<4; i++)  {    if (m_KeysDown[i])    {      keyPressed = true;      break;    }  }  string strAnim;  if (!keyPressed)    strAnim = "Pause" + m_strLastDir;  if (keyPressed)  {    string vertDir;    string horizDir;    if (m_KeysDown[0])      vertDir = "N";    else if (m_KeysDown[1])      vertDir = "S";    if (m_KeysDown[2])      horizDir = "W";    else if (m_KeysDown[3])      horizDir = "E";    m_strLastDir = vertDir + horizDir;    strAnim = "Walk" + m_strLastDir;  }  m_pKnightSprite->PlayAnimation(strAnim);}
We first check if at least one key is pressed. If that's not the case, we specify that the animation that should be played is "Pause" + the name of the last knight direction. If at least one key is pressed, we check which ones are pressed and we build the last direction string. Let's now look at theDraw function:
void CMainWindow::Draw(){  // Clear the buffer  glClear(GL_COLOR_BUFFER_BIT);  // Draw the grass  int xPos=0, yPos=0;  for (int i=0; i<8; i++)  {    for (int j=0; j<6; j++)    {      xPos = i * 256/2 - 128;      if (i%2)        yPos = (j * 128) - 128/2;      else        yPos = (j * 128);      m_pGrassImg->BlitImage(xPos, yPos);    }  }  // Draw some trees  m_pTreesImg[0]->BlitImage(15,25);  m_pTreesImg[1]->BlitImage(695,55);  m_pTreesImg[2]->BlitImage(15,25);  m_pTreesImg[3]->BlitImage(300,400);  m_pTreesImg[4]->BlitImage(125,75);  m_pTreesImg[5]->BlitImage(350,250);  m_pTreesImg[6]->BlitImage(400,350);  m_pTreesImg[7]->BlitImage(350,105);  m_pTreesImg[8]->BlitImage(530,76);  m_pTreesImg[9]->BlitImage(125,450);  m_pTreesImg[10]->BlitImage(425,390);  m_pTreesImg[11]->BlitImage(25,125);  m_pTreesImg[12]->BlitImage(550,365);  m_pTreesImg[13]->BlitImage(680,250);  m_pTreesImg[14]->BlitImage(245,325);  m_pTreesImg[15]->BlitImage(300,245);  // Draw the knight  m_pKnightSprite->DrawSprite();  // Move to the next frame of the animation  m_pKnightSprite->NextFrame();  // Swap the buffers  SwapBuffers(m_hDeviceContext);}
We first draw the grass: if you open the GrassIso.bmp file, you can see that this is a losange, and not a rectangle. That shape is typically used for isometric games to give an impression of 3D. After the grass is drawn, we draw some trees at some predefined positions on the screen. As you can see, manipulating the object contained in the smart pointer is completely transparent (it is as if it were manipulating the object directly). We finally draw the knight sprite and switch to the next frame in the animation. Moving the knight sprite is done in the Update function:
void CMainWindow::Update(DWORD dwCurrentTime){  int xOffset = 0;  int yOffset = 0;  if (m_KeysDown[0])    yOffset -= 5;  if (m_KeysDown[1])    yOffset += 5;  if (m_KeysDown[2])    xOffset -= 5;  if (m_KeysDown[3])    xOffset += 5;  m_pKnightSprite->OffsetPosition(xOffset, yOffset);}

If one of the keys is pressed, we move the sprite by a certain offset. As the time is passed to the function, we could also calculate the offset to apply to the sprite depending on the time elapsed. So, you are now ready to test the example and move your knight on the screen. Of course, the scene should probably be loaded from a file that is generated from a specific editor, but that falls outside the scope of this article.


原创粉丝点击