应用前面学到的子画面知识,开发一个小鸡过马路游戏。
接上文 游戏编程入门(8):管理子画面
Henway 游戏的玩法
Henway 游戏的主角是一只小鸡,它急需从一条繁忙的高速公路的一边到达另一边。Henway 的游戏方向是水平的,这意味着小鸡要从屏幕的左边到右边。
Henway 游戏,小鸡的数量是有限的3只。当所有小鸡都死掉时,游戏就结束了。当小鸡成功的穿越了高速公路或者玩家失去了一只小鸡,都会计入积分系统中,最后显示玩家所得的分数。
Henway 游戏,以键盘的上下左右键,控制小鸡。
设计Henway 游戏
Henway 游戏有5个子画面:一个小鸡子画面和4辆汽车子画面,有7个位图图像:1个背景高速公路图像,1个小鸡图像,4个汽车图像,很小的鸡头图像。
很小的鸡头图像显示在右下角,提示玩家还剩下多少只小鸡。在高速公路上损失小鸡时,鸡头就会消失,直到没有剩下任何鸡头位置,这时游戏就结束了。
现在,读者已经对游戏中涉及的图像对象有了一定的了解,下面考虑游戏必须维护的其他数据。首先,很显然需要记录剩余多少个小鸡,还需要维护一个连续的积分,在每次小鸡安全穿过高速公路时都加分,还需要一个布尔变量,用来记录游戏是否结束了。
还有最后一个变量,如果读者没有真正开发这个程序并彻底测试它,那么很难预见到这个变量。我是指一个输入延迟变量,它有助于改变键盘的输入响应,以便改进游戏的可玩性。
如果在每一个游戏循环里都直接响应键盘,那么小鸡在游戏屏幕上的移动就会太快了,这就产生了太多的游戏周期,过多的注意了输入设备。为了稍微放慢输入,可以使用一个延迟变量,每三个游戏循环才检查一次键盘输入。确定合适的延迟是一个不断尝试的过程,因此可以随意修改它并查看最合适的延迟。
总结一下,Henway 游戏的设计使我们了解到游戏必须管理以下信息:
- 剩下的小鸡数量
- 得分
- 确定游戏是否结束的布尔变量
- 输入延迟变量
开发Henway 游戏
注意:若出现编译错误,请在项目设置->连接->对象/库模块中 加入 msimg32.lib winmm.lib
Henway 目录结构与效果图
Henway 目录结构:
Henway 效果图:
Henway 游戏源代码
Henway.h 源代码
#pragma once
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include <windows.h>
#include "Resource.h"
#include "GameEngine.h"
#include "Bitmap.h"
#include "Sprite.h"
//-----------------------------------------------------------------
// 全局变量
//-----------------------------------------------------------------
HINSTANCE g_hInstance; //程序实例句柄
GameEngine* g_pGame; //游戏引擎指针
HDC g_hOffscreenDC; //屏幕外设备
HBITMAP g_hOffscreenBitmap; //屏幕外缓冲区的位图
Bitmap* g_pHighwayBitmap; //背景高速公路位图
Bitmap* g_pChickenBitmap; //小鸡位图
Bitmap* g_pCarBitmaps[4]; //4个汽车位图
Bitmap* g_pChickenHeadBitmap;//鸡头位图
Sprite* g_pChickenSprite; //小鸡子画面
int g_iInputDelay; //输入延迟
int g_iNumLives; //剩下的小鸡
int g_iScore; //得分
BOOL g_bGameOver; //游戏是否结束
//-----------------------------------------------------------------
// 根据用户输入事件移动小鸡子画面
//-----------------------------------------------------------------
void MoveChicken(int iXDistance, int iYDistance);
Henway.cpp 源代码
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include "Henway.h"
//-----------------------------------------------------------------
// 游戏事件函数
//-----------------------------------------------------------------
// 初始化游戏
BOOL GameInitialize(HINSTANCE hInstance)
{
// Create the game engine
g_pGame = new GameEngine(hInstance, TEXT("Henway"),
TEXT("Henway"), IDI_HENWAY, IDI_HENWAY_SM, 465, 400);
if (g_pGame == NULL)
return FALSE;
// Set the frame rate
g_pGame->SetFrameRate(30);
// Store the instance handle
g_hInstance = hInstance;
return TRUE;
}
// 开始游戏
void GameStart(HWND hWindow)
{
// 生成随机数种子
srand(GetTickCount());
// 创建屏幕外的设备环境,然后使用它创建一个屏幕外位图
g_hOffscreenDC = CreateCompatibleDC(GetDC(hWindow));
g_hOffscreenBitmap = CreateCompatibleBitmap(GetDC(hWindow),
g_pGame->GetWidth(), g_pGame->GetHeight());
SelectObject(g_hOffscreenDC, g_hOffscreenBitmap);
// 创建并加载位图
HDC hDC = GetDC(hWindow);
g_pHighwayBitmap = new Bitmap(hDC, IDB_HIGHWAY, g_hInstance);
g_pChickenBitmap = new Bitmap(hDC, IDB_CHICKEN, g_hInstance);
g_pCarBitmaps[0] = new Bitmap(hDC, IDB_CAR1, g_hInstance);
g_pCarBitmaps[1] = new Bitmap(hDC, IDB_CAR2, g_hInstance);
g_pCarBitmaps[2] = new Bitmap(hDC, IDB_CAR3, g_hInstance);
g_pCarBitmaps[3] = new Bitmap(hDC, IDB_CAR4, g_hInstance);
g_pChickenHeadBitmap = new Bitmap(hDC, IDB_CHICKENHEAD, g_hInstance);
// 创建小鸡和汽车的子画面(汽车的z顺序比小鸡高,这样小鸡被汽车压的时候,小鸡显示在车下面)
Sprite* pSprite;
RECT rcBounds = { 0, 0, 465, 400 };
g_pChickenSprite = new Sprite(g_pChickenBitmap, rcBounds, BA_STOP);
g_pChickenSprite->SetPosition(4, 175);
g_pChickenSprite->SetVelocity(0, 0);
g_pChickenSprite->SetZOrder(1);
g_pGame->AddSprite(g_pChickenSprite);
pSprite = new Sprite(g_pCarBitmaps[0], rcBounds, BA_WRAP);
pSprite->SetPosition(70, 0);
pSprite->SetVelocity(0, 7);
pSprite->SetZOrder(2);
g_pGame->AddSprite(pSprite);
pSprite = new Sprite(g_pCarBitmaps[1], rcBounds, BA_WRAP);
pSprite->SetPosition(160, 0);
pSprite->SetVelocity(0, 3);
pSprite->SetZOrder(2);
g_pGame->AddSprite(pSprite);
pSprite = new Sprite(g_pCarBitmaps[2], rcBounds, BA_WRAP);
pSprite->SetPosition(239, 400);
pSprite->SetVelocity(0, -5);
pSprite->SetZOrder(2);
g_pGame->AddSprite(pSprite);
pSprite = new Sprite(g_pCarBitmaps[3], rcBounds, BA_WRAP);
pSprite->SetPosition(329, 400);
pSprite->SetVelocity(0, -10);
pSprite->SetZOrder(2);
g_pGame->AddSprite(pSprite);
// 初始化其余全局变量
g_iInputDelay = 0;
g_iNumLives = 3;
g_iScore = 0;
g_bGameOver = FALSE;
}
// 结束游戏
void GameEnd()
{
// 清除屏幕外设备环境和位图
DeleteObject(g_hOffscreenBitmap);
DeleteDC(g_hOffscreenDC);
// 清除位图
delete g_pHighwayBitmap;
delete g_pChickenBitmap;
for (int i = 0; i < 4; i++)
delete g_pCarBitmaps[i];
delete g_pChickenHeadBitmap;
// 清除子画面
g_pGame->CleanupSprites();
// 清除游戏引擎
delete g_pGame;
}
void GameActivate(HWND hWindow)
{
}
void GameDeactivate(HWND hWindow)
{
}
// 绘制游戏
void GamePaint(HDC hDC)
{
// 绘制高速公路背景
g_pHighwayBitmap->Draw(hDC, 0, 0);
// 绘制游戏子画面
g_pGame->DrawSprites(hDC);
// 绘制剩余小鸡生命的数量
for (int i = 0; i < g_iNumLives; i++)
g_pChickenHeadBitmap->Draw(hDC,
406 + (g_pChickenHeadBitmap->GetWidth() * i), 382, TRUE);
}
// 游戏循环
void GameCycle()
{
if (!g_bGameOver)
{
// 更新子画面
g_pGame->UpdateSprites();
// 获得用来绘制游戏的设备环境
HWND hWindow = g_pGame->GetWindow();
HDC hDC = GetDC(hWindow);
// 在屏幕外设备环境上绘制游戏
GamePaint(g_hOffscreenDC);
// 将屏幕外位图位块传送到游戏屏幕
BitBlt(hDC, 0, 0, g_pGame->GetWidth(), g_pGame->GetHeight(),
g_hOffscreenDC, 0, 0, SRCCOPY);
// 清理
ReleaseDC(hWindow, hDC);
}
}
// 键盘响应事件
void HandleKeys()
{
if (!g_bGameOver && (++g_iInputDelay > 2))
{
// 根据按键移动小鸡
if (GetAsyncKeyState(VK_LEFT) < 0)
MoveChicken(-20, 0);
else if (GetAsyncKeyState(VK_RIGHT) < 0)
MoveChicken(20, 0);
if (GetAsyncKeyState(VK_UP) < 0)
MoveChicken(0, -20);
else if (GetAsyncKeyState(VK_DOWN) < 0)
MoveChicken(0, 20);
// 重置输入延迟
g_iInputDelay = 0;
}
}
// 按下鼠标
void MouseButtonDown(int x, int y, BOOL bLeft)
{
// 开始一个新游戏
if (g_bGameOver)
{
g_iNumLives = 3;
g_iScore = 0;
g_bGameOver = FALSE;
}
}
void MouseButtonUp(int x, int y, BOOL bLeft)
{
}
void MouseMove(int x, int y)
{
}
// 碰撞检测
BOOL SpriteCollision(Sprite* pSpriteHitter, Sprite* pSpriteHittee)
{
// 查看是否撞上了小鸡
if (pSpriteHittee == g_pChickenSprite)
{
// 将小鸡移回开始位置,给人一种新小鸡开始的错觉
g_pChickenSprite->SetPosition(4, 175);
// 查看游戏是否结束
if (--g_iNumLives > 0)
MessageBox(g_pGame->GetWindow(), TEXT("Ouch!"), TEXT("Henway"), MB_OK);
else
{
// 显示游戏结束消息
TCHAR szText[64];
wsprintf(szText, "Game Over! You scored %d points.", g_iScore);
MessageBox(g_pGame->GetWindow(), szText, TEXT("Henway"), MB_OK);
g_bGameOver = TRUE;
}
return FALSE;
}
return TRUE;
}
//-----------------------------------------------------------------
// 移动小鸡子画面
//-----------------------------------------------------------------
void MoveChicken(int iXDistance, int iYDistance)
{
// 将小鸡移动到新位置
g_pChickenSprite->OffsetPosition(iXDistance, iYDistance);
// 查看小鸡是否成功通过
if (g_pChickenSprite->GetPosition().left > 400)
{
// 将小鸡移回开始位置并增加得分
g_pChickenSprite->SetPosition(4, 175);
g_iScore += 150;
MessageBox(g_pGame->GetWindow(), TEXT("You made it!"), TEXT("Henway"),
MB_OK);
}
}