来自: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 CMainWindow
class. 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 the
CAnimatedSprite
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 a
string
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: the CAnimFileLoader
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 a CImageList
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 the ProcessEvent
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 the
WM_KEYUP
messages, which correspond to a key pressed and a key released respectively. When such message is sent, the
WPARAM
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 the
Draw
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.