游戏编程入门5使用键盘和鼠标控制游戏
游戏编程入门(5):使用键盘和鼠标控制游戏
接上文
本文内容包括:
- 如何有效地检测和响应键盘输入
- 如何处理鼠标输入
- 如何开发带有动画图形对象的程序,并且可以使用键盘和鼠标来控制动画图形对象
用户输入设备
输入设备是允许用户与一个游戏进行交互的物理硬件。
所有输入设备都执行相同的操作: 将用户提供的信息转换为一种计算机可以理解的格式 。输入设备在用户与游戏之间建立联系。
有三种主要的输入设备类型:
- 键盘
- 鼠标
- 游戏杆
键盘输入
我们知道,在Win32 API 中大量使用消息来提交有关各种事件的通知,如创建窗口、破坏窗口、激活窗口、使用窗口等,这个相同的信息传递系统也用来传递在键盘上按键的通知。
Win32 API 定义了名为 WM_KEYDOWN 和 WM_KEYUP 的消息,只需要按下或释放一个键,它们就会通知你。
但是,标准的Windows消息传递系统 传输键盘消息的速度慢的令人难以忍受,而游戏对快速响应的控制要求很高。
跟踪鼠标
在移动鼠标的时候,将会引发一系列事件,这些事件与键盘所引发的那些事件非常相似。
实际上,Win32 API 包括了一系列用来传送鼠标事件的鼠标消息,与键盘消息传递键盘事件的方式相似。
在前面,我们了解到 Win32键盘消息不适合为游戏提供有效输入的任务。而鼠标消息并不属于这种情况,通过消息处理鼠标事件的Win32方法对游戏很适用。
下面是用来 向Windows程序通报鼠标事件的鼠标消息:
- WM_MOUSEMOVE:任何鼠标移动
- WM_LBUTTONDOWN:按下鼠标左键
- WM_LBUTTONUP:释放鼠标左键
- WM_RBUTTONDOWN:按下鼠标右键
- WM_RBUTTONUP:释放鼠标右键
- WM_MBUTTONDOWN:按下鼠标中键
- WM_MBUTTONUP:释放鼠标中键
实现鼠标拖动功能 :单击鼠标的一个按钮,再按下一个按钮,之后释放这个按钮。通过记录 按下和释放鼠标按钮的时间并查看这段时间内的鼠标移动,就可以实现鼠标拖动功能。
在前面讲游戏引擎的时候,我们定义了一个HandleEvent( )方法,方法的原型如下:
LRESULT GameEngine::HandleEvent(HWND hWindow,UINT msg,WPARAM wParam,LPARAM lParam);
wParam 和 lParam参数是随着每一个Windows消息一起发送的,它们包含了消息专用的信息。
鼠标指针的位置是鼠标的一个重要性质, 对于鼠标消息来说,lParam包含了鼠标指针的XY位置(包含其在低位和高位字节中)。
下面这个例子,从WM_MOUSEMOVE 消息处理程序的 lParam 参数中提取鼠标位置:
case WM_MOUSEMOVE:
WORD x=LOWORD(lParam);
WORD y=HIWORD(lParam);
return 0;
而 鼠标消息的wParam 参数包含有关鼠标按钮状态的信息以及一些键盘信息 。更具体的说,wParam 使我们知道三个按钮(鼠标左键,中键,右键)中是否有一个处于被按下的状态,是否按下了键盘上的Shift键或Ctrl键。
下面是在处理鼠标消息是,用来解释 wParam 参数值的一些常量:
- MK_LBUTTON:按下了鼠标左键
- MK_RBUTTON:按下了鼠标右键
- MK_MBUTTON:按下了鼠标中键
- MK_SHIFT:按下了Shift键
- MK_CONTROL:按下了Ctrl键
可以通过检查这些鼠标常量,以便确定在鼠标移动的过程中是否按下了一个按钮或键。
实际上,这些常量也可以在wParam 参数中将它们组合在一起, 要想检查单个标志的存在性,必须使用按位AND 运算符(&)来检查标志是否存在。
下面是检查wParam 以查看是否按下鼠标右键的一个例子:
if(wParam & HK_RBUTTON)
//按下了鼠标右键
向 游戏引擎 添加输入支持
因为我们已经开发了一个游戏引擎来完成与游戏管理有关的各种任务,所以将用户输入处理结合到游戏引擎中是很有意义的。处理用户输入的某个方面是游戏所特有的,因此必须在每个单独游戏的代码中进行处理。不过,键盘处理和鼠标处理存在一些通用的地方,可以将它们结合到游戏引擎中,从而简化特定游戏代码所需要完成的工作。
添加键盘支持
在前面我们已经了解到使用消息来处理键盘的标准Windows方法对于游戏来说是不够的(因为太慢了)。
处理键盘输入的一种更好的方法是反复检查键盘的状态,查看是否按下了特定的键,然后做出相应的反应。
使用这个策略, 键盘输入处理的很多工作就转移给了游戏代码,这意味着游戏引擎主要只负责调用一个键盘处理函数,使游戏有机会相应按键。
下面是这个函数的原型:
void HandleKeys();
HandleKeys( ) 函数必须作为游戏代码的一部分提供,因此它不包括在游戏引擎中。如果不希望游戏支持键盘输入,那么只需要使HandleKeys( ) 函数保持为空白即可。
当然,游戏引擎必须确定以足够快的速度调用HandleKeys( ) 函数,从而使游戏能够立即响应。
这是在游戏引擎代码(GameEngine.cpp)中的WinMain( )函数中实现的 。下面是对这个函数所作的修改:
if(iTickCount > iTickTrigger)
{
iTickTrigger = iTickCount + GameEngine::GetEngine()->GetFrameDelay();
HandleKeys();
GameCycle();
}
对WinMain( ) 代码的唯一一处改动是对HandleKeys( )函数的新调用。注意,这个调用刚好在GameCycle( ) 函数之前发生,这表示游戏在每个周期之前都会获得相应键盘输入的机会。
不要忘了,处理键盘输入的具体细节是在各个特定的游戏中实现的,也就是在创建自己的HandleKeys( )函数时。
添加鼠标支持
要想支持鼠标输入,游戏必须支持以下3个函数, 它们由游戏引擎在接受到鼠标事件时调用。
鼠标处理函数如下 :
void MouseButtonDown(int x,int y,BOOL bLeft); //按下鼠标
void MouseButtonUp(int x,int y,BOOL bLeft); //释放鼠标
boid MouseMove(int x,int y); //鼠标移动
要想将鼠标消息与这些鼠标处理函数联系起来,游戏引擎必须检查适当的鼠标消息并作出响应的响应。
下面这段代码包括了GameEngine::HandleEvent( )方法的一部分新内容,它们负责 处理传递到主游戏窗口的鼠标消息 。
CASE WM_LBUTTONDOWN:
//处理按下鼠标左键的事件
MouseButtonDown(LOWORD(lParam), HIWORD(lParam),TRUE);
return 0;
CASE WM_LBUTTONUP:
//处理释放鼠标左键的事件
MouseButtonUp(LOWORD(lParam), HIWORD(lParam),TRUE);
return 0;
CASE WM_RBUTTONDOWN:
//处理按下鼠标右键的事件
MouseButtonDown(LOWORD(lParam), HIWORD(lParam),FALSE);
return 0;
CASE WM_RBUTTONDOWN:
//处理释放鼠标右键的事件
MouseButtonUp(LOWORD(lParam), HIWORD(lParam),FALSE);
return 0;
CASE WM_MOUSEMOVE:
//处理按下鼠标移动的事件
MouseMove(LOWORD(lParam), HIWORD(lParam));
return 0;
鼠标按钮函数的最后一个参数是一个布尔值,它标识了事件中是否涉及鼠标左键(TRUE)或鼠标右键(FALSE)
修改 Bitmap 类 使 位图透明
从技术上讲,这个修改与输入没有任何关系。
位图透明,可以使位图不是总显示为方块图形对象(虽然位图都是方块图形对象,但我们不一定必须按照这种方式来绘制)。
透明的意思是可以将一种颜色指定为透明色,然后使用这种颜色来表示一个位图的透明部分。在绘制位图时,不会绘制透明色的像素,背景将会透过它显示出来。
从创建图形的角度来看,创建带有透明位图的方法是选择一种图形中没有使用的颜色,例如深紫色,然后使用深紫色来填充位图中需要显示为透明的区域。
游戏开发群体在透明色的使用上有一些争论。过去,紫色(RGB:255,0, 255)是表示透明的标准颜色。现在,大多数商业3D游戏都使用纯黑色(RGB:0,0, 0)、纯蓝(RGB:0, 0, 255)或中度灰色(RGB:128, 128, 128)作为透明色。
本系列所有的例子,都使用紫色作为透明色,但是只要在某个特定游戏的图形中保持一致,就可以任意选择使用没有使用的颜色。
在游戏引擎中,实现位图透明的诀窍是扩展现有的 Bitmap::Draw( ) 方法 ,使之支持透明。这通过添加两个新的参数实现。
- bTran:布尔值,表示是否将位图绘制为透明的。FALSE:没有使用透明
- crTransColor:位图的透明色
对Draw( ) 的唯一一个重大更改是检查透明参数 bTran ,如果这个参数为TRUE,则使用 Win32 的 TransparentBlt( ) 函数绘制带有透明的位图 。否则,就像往常一样使用 BitBlt( ) 函数绘制不带透明的位图。
TransparentBlt( ) 函数,需要包括一个名为msimg32.lib的库,记得在工程”->”设置”->在”Project Setting”中,”对象/库模块”中,增加Msimg32.lib。
开发 UFO 示例
本文将着重讨论一个名为 UFO 的实例,虽然从技术上讲,这个程序不是一个游戏,但它是到目前为止读者所看到的最接近于游戏的程序。
它包括一个可以使用键盘或(和)鼠标控制的飞碟,可以使飞碟在一个位图背景图像上飞行。
本程序,在每一个游戏周期都重新绘制位图,因此通过改变位图的位置并不断重新绘制,就创建了UFO移动的效果。
UFO目录结构与效果图
UFO目录结构 :
UFO效果图:
UFO 源代码
Resource.h
//-----------------------------------------------------------------
// UFO Resource Identifiers
// C++ Header - Resource.h
//-----------------------------------------------------------------
//-----------------------------------------------------------------
// Icons Range : 1000 - 1999
//-----------------------------------------------------------------
#define IDI_UFO 1000
#define IDI_UFO_SM 1001
//-----------------------------------------------------------------
// Bitmaps Range : 2000 - 2999
//-----------------------------------------------------------------
#define IDB_BACKGROUND 2000
#define IDB_SAUCER 2001
UFO.h
#pragma once
//-----------------------------------------------------------------
// 包含文件
//-----------------------------------------------------------------
#include <windows.h>
#include "Resource.h"
#include "GameEngine.h"
#include "Bitmap.h"
//-----------------------------------------------------------------
// 全局变量
//-----------------------------------------------------------------
HINSTANCE g_hInstance; //程序句柄
GameEngine* g_pGame; //游戏引擎指针
const int g_iMAXSPEED = 8; //飞碟的最大速度即1个游戏周期内在x或y方向上移动的像素
Bitmap* g_pBackground; //夜晚天空的背景
Bitmap* g_pSaucer; //飞碟图像
int g_iSaucerX, g_iSaucerY; //飞碟的x,y位置
int g_iSpeedX, g_iSpeedY; //飞碟的xy速度(即每周期移动的像素)负值即为反方向
Bitmap.h
#pragma once
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include <windows.h>
//-----------------------------------------------------------------
// Bitmap 类
//-----------------------------------------------------------------
class Bitmap
{
protected:
// 成员变量
HBITMAP m_hBitmap; //位图句柄
int m_iWidth, m_iHeight;//位图的宽和高
// 帮助器方法,用来释放与位图有关的内存并清除位图句柄
void Free();
public:
// 构造函数和析构函数 3个构造函数分别对应一种创建位图的不同方法
Bitmap();
//从一个文件中创建位图
Bitmap(HDC hDC, LPTSTR szFileName);
//从一个资源中创建位图
Bitmap(HDC hDC, UINT uiResID, HINSTANCE hInstance);
//创建纯色的空白位图
Bitmap(HDC hDC, int iWidth, int iHeight, COLORREF crColor = RGB(0, 0, 0));
virtual ~Bitmap();
// 常规方法 create()用来处理加载位图数据并将其创建为一个GDI 对象,3个Create分别对应3个构造函数
BOOL Create(HDC hDC, LPTSTR szFileName);
BOOL Create(HDC hDC, UINT uiResID, HINSTANCE hInstance);
BOOL Create(HDC hDC, int iWidth, int iHeight, COLORREF crColor);
//提供将位图绘制到设备环境上的方法 bTrans=FALSE,不将位图绘制成透明
void Draw(HDC hDC, int x, int y, BOOL bTrans = FALSE,
COLORREF crTransColor = RGB(255, 0, 255));
int GetWidth()
{
return m_iWidth;
};
int GetHeight()
{
return m_iHeight;
};
};
GameEngine.h
#pragma once /*该头文件仅编译一次(因为同一头文件会在许多源文件中多次引用。如
果没有指定编译一次,则编译时出现重定义错误。*/
//-----------------------------------------------------------------
// 包含的头文件
//-----------------------------------------------------------------
#include <windows.h>
//-----------------------------------------------------------------
// Windows函数声明
//-----------------------------------------------------------------
/*WinMain函数应初始化应用程序,显示主窗口,进入一个消息接收一发送循环,
这个循环是应用程序执行的其余部分的顶级控制结构。*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow);
//窗口过程,指向一个应用程序定义的窗口过程的指针。
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
//-----------------------------------------------------------------
// 游戏事件函数声明
//-----------------------------------------------------------------
BOOL GameInitialize(HINSTANCE hInstance);//初始化游戏
void GameStart(HWND hWindow); //启动游戏
void GameEnd(); //结束游戏
void GameActivate(HWND hWindow); //激活游戏
void GameDeactivate(HWND hWindow); //停用游戏
void GamePaint(HDC hDC); //绘制游戏
void GameCycle(); //循环游戏
//键盘与鼠标处理函数
void HandleKeys(); //键盘处理函数
void MouseButtonDown(int x, int y, BOOL bLeft);//按下鼠标
void MouseButtonUp(int x, int y, BOOL bLeft); //释放鼠标
void MouseMove(int x, int y); //鼠标移动
//-----------------------------------------------------------------
// GameEngine 类
//-----------------------------------------------------------------
class GameEngine
{
protected:
//成员变量
static GameEngine* m_pGameEngine; //指向自身的静态指针,用于游戏程序的外部访问
HINSTANCE m_hInstance; //应用程序实例
HWND m_hWindow; //主窗口句柄
TCHAR m_szWindowClass[32]; //窗口类的名称
TCHAR m_szTitle[32]; //主游戏窗口的名称
WORD m_wIcon, m_wSmallIcon; //游戏的两个程序图标的数字ID
int m_iWidth, m_iHeight; //游戏屏幕的宽度和高度
int m_iFrameDelay; //游戏周期之间的间隔,单位是ms
BOOL m_bSleep; //表示游戏是否在休眠
public:
//构造函数和析构函数
//游戏引擎构造函数使用默认的屏幕大小(640*480)创建游戏,这是实际的游戏区
GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass, LPTSTR szTitle,
WORD wIcon, WORD wSmallIcon, int iWidth = 640, int iHeight = 480);
virtual ~GameEngine();
//常规方法
//在引擎外部使用这个静态方法访问指向引擎的静态指针
static GameEngine* GetEngine()
{
return m_pGameEngine;
};
//创建引擎后,初始化游戏程序
BOOL Initialize(int iCmdShow);
//处理引擎内的标准Windows事件
LRESULT HandleEvent(HWND hWindow, UINT msg, WPARAM wParam,
LPARAM lParam);
void ErrorQuit(LPTSTR szErrorMsg);
//访问方法
HINSTANCE GetInstance()
{
return m_hInstance;
};
HWND GetWindow()
{
return m_hWindow;
};
void SetWindow(HWND hWindow)
{
m_hWindow = hWindow;
};
LPTSTR GetTitle()
{
return m_szTitle;
};
WORD GetIcon()
{
return m_wIcon;
};
WORD GetSmallIcon()
{
return m_wSmallIcon;
};
int GetWidth()
{
return m_iWidth;
};
int GetHeight()
{
return m_iHeight;
};
int GetFrameDelay()
{
return m_iFrameDelay;
};
//指定帧速率,当值为30时会使游戏以30帧/秒的速率运行
void SetFrameRate(int iFrameRate)
{
m_iFrameDelay = 1000 /iFrameRate;
};
BOOL GetSleep()
{
return m_bSleep;
};
void SetSleep(BOOL bSleep)
{
m_bSleep = bSleep;
};
};
UFO.cpp
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include "UFO.h"
//-----------------------------------------------------------------
// 游戏事件函数
//-----------------------------------------------------------------
// 初始化游戏
BOOL GameInitialize(HINSTANCE hInstance)
{
// 创建游戏引擎
g_pGame = new GameEngine(hInstance, TEXT("UFO"),
TEXT("UFO"), IDI_UFO, IDI_UFO_SM, 500, 400);
if (g_pGame == NULL)
return FALSE;
// 设置帧率
g_pGame->SetFrameRate(30);
// 存储程序句柄
g_hInstance = hInstance;
return TRUE;
}
// 开始游戏
void GameStart(HWND hWindow)
{
// 创建并加载背景和飞碟位图
HDC hDC = GetDC(hWindow);
g_pBackground = new Bitmap(hDC, IDB_BACKGROUND, g_hInstance);
g_pSaucer = new Bitmap(hDC, IDB_SAUCER, g_hInstance);
// 设置初始的飞碟位置和速度(飞碟最初稳定停放在屏幕中央)
g_iSaucerX = 250 - (g_pSaucer->GetWidth() / 2);
g_iSaucerY = 200 - (g_pSaucer->GetHeight() / 2);
g_iSpeedX = 0; // 将飞碟的速度设置为0,它就不会动
g_iSpeedY = 0;
}
// 游戏结束
void GameEnd()
{
// Cleanup the background and saucer bitmaps
delete g_pBackground;
delete g_pSaucer;
// Cleanup the game engine
delete g_pGame;
}
void GameActivate(HWND hWindow)
{
}
void GameDeactivate(HWND hWindow)
{
}
// 绘制游戏
void GamePaint(HDC hDC)
{
// 绘制背景和飞碟位图
// 背景是在游戏屏幕的原点绘制的
g_pBackground->Draw(hDC, 0, 0);
// 飞碟则是再度昂前位置绘制的,使用默认的透明色(紫色)来绘制带透明的飞碟
g_pSaucer->Draw(hDC, g_iSaucerX, g_iSaucerY, TRUE);
}
// 游戏循环
void GameCycle()
{
// 更新飞碟的位置 min(),max()用来确定飞碟停留在屏幕的范围内
g_iSaucerX = min(500 - g_pSaucer->GetWidth(), max(0, g_iSaucerX + g_iSpeedX));
g_iSaucerY = min(320, max(0, g_iSaucerY + g_iSpeedY));
// 强制重新绘制,以便重新绘制飞碟,达到飞碟移动的效果
InvalidateRect(g_pGame->GetWindow(), NULL, FALSE);
}
// 键盘处理函数
void HandleKeys()
{
// 响应方向键按键事件,更改飞碟的速度 GetAsyncKeyState()获得键盘上任何键的状态
if (GetAsyncKeyState(VK_LEFT) < 0)
g_iSpeedX = max(-g_iMAXSPEED, --g_iSpeedX);
else if (GetAsyncKeyState(VK_RIGHT) < 0)
g_iSpeedX = min(g_iMAXSPEED, ++g_iSpeedX);
if (GetAsyncKeyState(VK_UP) < 0)
g_iSpeedY = max(-g_iMAXSPEED, --g_iSpeedY);
else if (GetAsyncKeyState(VK_DOWN) < 0)
g_iSpeedY = min(g_iMAXSPEED, ++g_iSpeedY);
}
// 释放鼠标
void MouseButtonDown(int x, int y, BOOL bLeft)
{
// 左键
if (bLeft)
{
// 将飞碟设置为当前的鼠标位置
g_iSaucerX = x - (g_pSaucer->GetWidth() / 2);
g_iSaucerY = y - (g_pSaucer->GetHeight() / 2);
}
else // 右键
{
// 将飞碟的速度设置为0
g_iSpeedX = 0;
g_iSpeedY = 0;
}
}
// 按下鼠标
void MouseButtonUp(int x, int y, BOOL bLeft)
{
}
// 移动鼠标
void MouseMove(int x, int y)
{
}
Bitmap.cpp
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include "Bitmap.h"
//-----------------------------------------------------------------
// Bitmap 的构造函数和析构函数
//-----------------------------------------------------------------
Bitmap::Bitmap()
: m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
}
// 从一个文件中创建位图
Bitmap::Bitmap(HDC hDC, LPTSTR szFileName)
: m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
Create(hDC, szFileName);
}
// 从一个资源中创建位图
Bitmap::Bitmap(HDC hDC, UINT uiResID, HINSTANCE hInstance)
: m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
Create(hDC, uiResID, hInstance);
}
// 创建纯色的空白位图
Bitmap::Bitmap(HDC hDC, int iWidth, int iHeight, COLORREF crColor)
: m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
Create(hDC, iWidth, iHeight, crColor);
}
Bitmap::~Bitmap()
{
Free();
}
//-----------------------------------------------------------------
// Bitmap 帮助器方法,用来释放与位图有关的内存并清除位图句柄
//-----------------------------------------------------------------
void Bitmap::Free()
{
// 若位图句柄有效(即存在)
if (m_hBitmap != NULL)
{
//删除GDI 位图图像并清除句柄
DeleteObject(m_hBitmap);
m_hBitmap = NULL;
}
}
//-----------------------------------------------------------------
// Bitmap 常规方法,3个Create()和Draw()
//-----------------------------------------------------------------
BOOL Bitmap::Create(HDC hDC, LPTSTR szFileName)
{
// 清空以前的任何位图信息(使用于对不同的位图重复使用同一个Bitmap对象的情况)
Free();
// 打开文件
HANDLE hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//检查得到的文件句柄以确保顺利打开文件
if (hFile == INVALID_HANDLE_VALUE)
return FALSE;
// 从文件中读 位图的 文件头(文件头包含位图文件本身的信息)
BITMAPFILEHEADER bmfHeader;
DWORD dwBytesRead;
BOOL bOK = ReadFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER),
&dwBytesRead, NULL);
// 进行检查,确保正确读取
if ((!bOK) || (dwBytesRead != sizeof(BITMAPFILEHEADER)) ||
(bmfHeader.bfType != 0x4D42))
{
CloseHandle(hFile);
return FALSE;
}
BITMAPINFO* pBitmapInfo = (new BITMAPINFO);
if (pBitmapInfo != NULL)
{
// 从文件中读 位图的 信息头部
bOK = ReadFile(hFile, pBitmapInfo, sizeof(BITMAPINFOHEADER),
&dwBytesRead, NULL);
if ((!bOK) || (dwBytesRead != sizeof(BITMAPINFOHEADER)))
{
CloseHandle(hFile);
Free();
return FALSE;
}
// 存储位图的宽度和高度
m_iWidth = (int)pBitmapInfo->bmiHeader.biWidth;
m_iHeight = (int)pBitmapInfo->bmiHeader.biHeight;
/* 计算biSizeImage填充回去,是增加的代码
(因为无压缩BMP文件的pBitmapInfo->bmiHeader.biSizeImage 里面的值不一定是图像的真实大小,
可能是0或者随意的值。所以需要重新计算
*/
pBitmapInfo->bmiHeader.biSizeImage =
m_iHeight*m_iWidth*((int)pBitmapInfo->bmiHeader.biBitCount)/8;
// 复制图像数据,调用CreateDIBSection()以从原始位图数据中获得一个GDI 位图对象的句柄
PBYTE pBitmapBits;
m_hBitmap = CreateDIBSection(hDC, pBitmapInfo, DIB_RGB_COLORS,
(PVOID*)&pBitmapBits, NULL, 0);
if ((m_hBitmap != NULL) && (pBitmapBits != NULL))
{
SetFilePointer(hFile, bmfHeader.bfOffBits, NULL, FILE_BEGIN);
bOK = ReadFile(hFile, pBitmapBits, pBitmapInfo->bmiHeader.biSizeImage,
&dwBytesRead, NULL);
if (bOK)
return TRUE;
}
}
// 读取数据的过程中发生错误时释放位图内存
Free();
return FALSE;
}
//从一个资源中加载位图并将其创建为一个GDI 对象
BOOL Bitmap::Create(HDC hDC, UINT uiResID, HINSTANCE hInstance)
{
// Free any previous DIB info
Free();
// 找到位图资源
HRSRC hResInfo = FindResource(hInstance, MAKEINTRESOURCE(uiResID), RT_BITMAP);
if (hResInfo == NULL)
return FALSE;
// 将位图资源加载到内存中
HGLOBAL hMemBitmap = LoadResource(hInstance, hResInfo);
if (hMemBitmap == NULL)
return FALSE;
// 锁定资源,以便访问其原始数据
PBYTE pBitmapImage = (BYTE*)LockResource(hMemBitmap);
if (pBitmapImage == NULL)
{
FreeResource(hMemBitmap);
return FALSE;
}
// 存储位图的宽度和高度
BITMAPINFO* pBitmapInfo = (BITMAPINFO*)pBitmapImage;
m_iWidth = (int)pBitmapInfo->bmiHeader.biWidth;
m_iHeight = (int)pBitmapInfo->bmiHeader.biHeight;
/* 计算biSizeImage填充回去,是增加的代码
(因为无压缩BMP文件的pBitmapInfo->bmiHeader.biSizeImage 里面的值不一定是图像的真实大小,
可能是0或者随意的值。所以需要重新计算
*/
pBitmapInfo->bmiHeader.biSizeImage =
m_iHeight*m_iWidth*((int)pBitmapInfo->bmiHeader.biBitCount)/8;
// 复制图像数据,并以此为基础使用CreateDIBSection获得一个位图句柄
PBYTE pBitmapBits;
m_hBitmap = CreateDIBSection(hDC, pBitmapInfo, DIB_RGB_COLORS,
(PVOID*)&pBitmapBits, NULL, 0);
if ((m_hBitmap != NULL) && (pBitmapBits != NULL))
{
const PBYTE pTempBits = pBitmapImage + pBitmapInfo->bmiHeader.biSize +
pBitmapInfo->bmiHeader.biClrUsed * sizeof(RGBQUAD);
CopyMemory(pBitmapBits, pTempBits, pBitmapInfo->bmiHeader.biSizeImage);
// 解锁并释放位图资源
UnlockResource(hMemBitmap);
FreeResource(hMemBitmap);
return TRUE;
}
// 在发生错误时执行一些清理工作
UnlockResource(hMemBitmap);
FreeResource(hMemBitmap);
Free();
return FALSE;
}
//创建纯色的空白位图并将其创建为一个GDI 对象
BOOL Bitmap::Create(HDC hDC, int iWidth, int iHeight, COLORREF crColor)
{
// 创建纯色的位图
m_hBitmap = CreateCompatibleBitmap(hDC, iWidth, iHeight);
if (m_hBitmap == NULL)
return FALSE;
// 设置宽度和高度
m_iWidth = iWidth;
m_iHeight = iHeight;
// 创建一个兼容的设备环境用以包含要绘制的位图
HDC hMemDC = CreateCompatibleDC(hDC);
// 创建一个指定颜色的纯白画刷用以填充位图
HBRUSH hBrush = CreateSolidBrush(crColor);
// 将位图选入设备环境
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, m_hBitmap);
// 用纯色画刷填充位图
RECT rcBitmap = { 0, 0, m_iWidth, m_iHeight };
FillRect(hMemDC, &rcBitmap, hBrush);
// 清理图形对象
SelectObject(hMemDC, hOldBitmap);
DeleteDC(hMemDC);
DeleteObject(hBrush);
return TRUE;
}
//绘制位图
void Bitmap::Draw(HDC hDC, int x, int y, BOOL bTrans, COLORREF crTransColor)
{
// 确保位图句柄有效
if (m_hBitmap != NULL)
{
// 创建一个兼容的设备环境来临时存储位图
HDC hMemDC = CreateCompatibleDC(hDC);
// 将位图选入设备环境中
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, m_hBitmap);
//
if (bTrans)
TransparentBlt(hDC, x, y, GetWidth(), GetHeight(), hMemDC, 0, 0,
GetWidth(), GetHeight(), crTransColor);
else
BitBlt(hDC, x, y, GetWidth(), GetHeight(), hMemDC, 0, 0, SRCCOPY);
// 清理临时设备环境
SelectObject(hMemDC, hOldBitmap);
DeleteDC(hMemDC);
}
}
GameEngine.cpp
//-----------------------------------------------------------------
// 包含的头文件
//-----------------------------------------------------------------
#include "GameEngine.h"
//-----------------------------------------------------------------
// 初始化静态的游戏指针
//-----------------------------------------------------------------
GameEngine *GameEngine::m_pGameEngine = NULL;
//-----------------------------------------------------------------
// Windows函数
//-----------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
MSG msg;
static int iTickTrigger = 0;
int iTickCount;
if (GameInitialize(hInstance)) //通过调用GameInitialize()初始化游戏
{
// 初始化游戏引擎
if (!GameEngine::GetEngine()->Initialize(iCmdShow))
return FALSE;
// 进入主消息循环
while (TRUE)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// 处理消息
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else // 这段代码创建游戏的计时机制
{
// 确保游戏引擎没有休眠
if (!GameEngine::GetEngine()->GetSleep())
{
// 检查滴答计数,查看是否过了一个游戏周期
iTickCount = GetTickCount();
if (iTickCount > iTickTrigger)
{
iTickTrigger = iTickCount +
GameEngine::GetEngine()->GetFrameDelay();
HandleKeys(); //键盘处理函数
GameCycle();
}
}
}
}
return (int)msg.wParam;
}
// 结束游戏
GameEnd();
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
// 将所有Windows消息传递给游戏引擎
return GameEngine::GetEngine()->HandleEvent(hWindow, msg, wParam, lParam);
}
//-----------------------------------------------------------------
// GameEngine的构造函数和析构函数
//-----------------------------------------------------------------
GameEngine::GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass,
LPTSTR szTitle, WORD wIcon, WORD wSmallIcon, int iWidth, int iHeight)
{
// 设置游戏引擎的成员变量
m_pGameEngine = this;
m_hInstance = hInstance;
m_hWindow = NULL;
if (lstrlen(szWindowClass) > 0)
lstrcpy(m_szWindowClass, szWindowClass);
if (lstrlen(szTitle) > 0)
lstrcpy(m_szTitle, szTitle);
m_wIcon = wIcon;
m_wSmallIcon = wSmallIcon;
m_iWidth = iWidth;
m_iHeight = iHeight;
m_iFrameDelay = 50; // 默认为20帧每秒(50ms/1000=20帧/秒)
m_bSleep = TRUE;
}
GameEngine::~GameEngine()
{
}
//-----------------------------------------------------------------
// Game Engine 常规方法
//-----------------------------------------------------------------
//Initialize方法处理一些通常在WinMain()中执行的杂乱方法
BOOL GameEngine::Initialize(int iCmdShow)
{
WNDCLASSEX wndclass;
// 创建主窗口的窗口类
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = m_hInstance;
wndclass.hIcon = LoadIcon(m_hInstance,
MAKEINTRESOURCE(GetIcon()));
wndclass.hIconSm = LoadIcon(m_hInstance,
MAKEINTRESOURCE(GetSmallIcon()));
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = m_szWindowClass;
// 注册窗口类
if (!RegisterClassEx(&wndclass))
return FALSE;
/* 根据游戏大小计算窗口大小和位置 (这段代码允许确定实际游戏屏幕的确切大小,
这个大小与整个应用程序窗口的大小不同) */
int iWindowWidth = m_iWidth + GetSystemMetrics(SM_CXFIXEDFRAME) * 2,
iWindowHeight = m_iHeight + GetSystemMetrics(SM_CYFIXEDFRAME) * 2 +
GetSystemMetrics(SM_CYCAPTION);
if (wndclass.lpszMenuName != NULL)
iWindowHeight += GetSystemMetrics(SM_CYMENU);
int iXWindowPos = (GetSystemMetrics(SM_CXSCREEN) - iWindowWidth) / 2,
iYWindowPos = (GetSystemMetrics(SM_CYSCREEN) - iWindowHeight) / 2;
// 创建窗口
m_hWindow = CreateWindow(m_szWindowClass, m_szTitle, WS_POPUPWINDOW |
WS_CAPTION | WS_MINIMIZEBOX, iXWindowPos, iYWindowPos, iWindowWidth,
iWindowHeight, NULL, NULL, m_hInstance, NULL);
if (!m_hWindow)
return FALSE;
// 显示和更新窗口
ShowWindow(m_hWindow, iCmdShow);
UpdateWindow(m_hWindow);
return TRUE;
}
// 接受并处理通常在WinProc()中处理的消息
LRESULT GameEngine::HandleEvent(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
// 将Windows消息传递给游戏引擎成员函数
switch (msg)
{
case WM_CREATE:
// 设置游戏窗口并开始游戏
SetWindow(hWindow);
GameStart(hWindow); //响应WM_CREATE消息,开始游戏
return 0;
case WM_SETFOCUS:
// 激活游戏并更新休眠状态
GameActivate(hWindow);
SetSleep(FALSE);
return 0;
case WM_KILLFOCUS:
// 停用游戏并更新休眠状态
GameDeactivate(hWindow);
SetSleep(TRUE);
return 0;
case WM_PAINT:
HDC hDC;
PAINTSTRUCT ps;
hDC = BeginPaint(hWindow, &ps);
// 绘制游戏
GamePaint(hDC);
EndPaint(hWindow, &ps);
return 0;
case WM_LBUTTONDOWN:
// 处理按下鼠标左键的事件
MouseButtonDown(LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_LBUTTONUP:
// 处理释放鼠标左键的事件
MouseButtonUp(LOWORD(lParam), HIWORD(lParam), TRUE);
return 0;
case WM_RBUTTONDOWN:
// 处理按下鼠标右键的事件
MouseButtonDown(LOWORD(lParam), HIWORD(lParam), FALSE);
return 0;
case WM_RBUTTONUP:
// 处理按下鼠标左键的事件
MouseButtonUp(LOWORD(lParam), HIWORD(lParam), FALSE);
return 0;
case WM_MOUSEMOVE:
// 处理鼠标移动的事件
MouseMove(LOWORD(lParam), HIWORD(lParam));
return 0;
case WM_DESTROY:
// 结束游戏并退出应用程序
GameEnd();
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWindow, msg, wParam, lParam);
}
void GameEngine::ErrorQuit(LPTSTR szErrorMsg)
{
MessageBox(GetWindow(), szErrorMsg, TEXT("Critical Error"), MB_OK | MB_ICONERROR);
PostQuitMessage(0);
}