Windows游戏编程快速入门方法
Easideao(简单思路)
序言:
从2001年到2005年,在不知不觉中我已经渡过了4年的职业游戏开发生涯。在这4年里经常会有些网友向我询问编程的入门有没有捷径。每一次我都不知怎么回答才算合适,我也一直想表达一下我的思路和想法,但一直都没有能力把自己的见解在书面上表达出来,其实我认为编写程序并不是很难的事情。最关键的是你对他是否有兴趣,最难的是坚持学习。如果没有兴趣,即使你刚刚入了一点们如果不坚持下去,也是一事无成。
虽然毅力在学习的过程中有着不可置疑的位置,但是有个合适的方法和适合自己的方法还是很重要的。假如你的兴趣和毅力都过了关,我接下来将以一个游戏的代码编写过程写下来,我坚持写下来,你坚持读完,按照我讲述的步骤去做。我这里不会把所有细节都讲述出来,因为那是太庞大的任务,我的力量无法实现,我们下面的方法就是:我说怎么做,你就怎么做,先知道怎样做一些事情,当你能够按照我说的做出正确的结果说明你已经会了,如果有不懂得再去查看相关资料。
上面说的有些繁琐,我自己也不太愿意写下去了,我的文笔水平有限,请大家谅解。接下来最重要的就是跟着我做。如果你有什么意见或问题可以给我发E-mail : chinagdh@163.com。
第一章 Windows程序
- 打开Visual Studio 2003.net ,选择File -> Blank Solution。
- 在Name栏里输入 BattleCity 并按 ok 按钮, 按browse选择解决方案存放位置
3.在Solution Explorer 里在 Solution ‘BattleCity’上按右键。在下拉菜单中选择 Add -> New Project。
4.在Add New Project 对话框里选择 Visual C++ Projects -> Win32 -> Win32 Project,在Name栏里打入 Tank 并按回车
5.选择Application Settings 并在 Empty project 前面打钩,创建一个空的Win32 项目。
6.在Tank项目上按右键 选择Add -> New Folder 增加文件夹,并命名WinApp
7.在WinApp文件夹上按右键选择Add -> Add New Item
8.选择Visual C++ -> C++ File(.cpp) 在Name栏里输入 WinApp.cpp。
9.反复7.8步 增加 WinApp.h AppEntry.cpp AppEntry.h
10.双击 WinApp.h 打开文件我们在WinApp.h头文件中加入以下代码
11.以同样的方法处理AppEntry.h , 这种方法保证头文件只被include一次,这是我喜欢用的一种方法也可以在第一行写#pragma once
12.打开AppEntry.h 加入代码 #include <windows.h>
13.打开WinApp.h 加入代码 #include "AppEntry.h"
14.打开WinApp.cpp 加入代码 #include "WinApp.h"
15.定义主程序句柄和主窗口句柄
16.增加获得主程序句柄和主窗口句柄的全局函数
17. 为方便以后获得主程序句柄和主窗口句柄 在 WinApp.h 中声明
HINSTANCE GetAppHandle();
HWND GetMainWnd();
18.定义Windows程序主函数,这是一个Windows程序的入口函数,我们认为程序从此函数开始执行。
19.在Solution Explorer 中选择Tank项目,按右键选择Build 编译一下,看程序是否可以编译。
编译成功会在Output窗口中出现提示信息 Build:1 succeeded, 0 failed, 0 skipped 表示 成功一个 0 失败 0 跳过
20.设置Tank生成的路径,在项目Tank上按右键选择Properties
21.选择程序生成路径(Output File)为 ../RunTime/Tank.exe
22.选择运行路径(Working Directory) 为 ../RunTime 在编译一下
23.增加必要的几个函数 程序的初始化 结束 主循环 消息处理函数,代码如下
#include "WinApp.h"
// 定义主程序句柄
HINSTANCE g_hTheApp = NULL;
// 定义主窗口句柄
HWND g_hMainWnd = NULL;
//
// 获得主程序句柄
HINSTANCE GetAppHandle()
{
return g_hTheApp;
}
// 获得主窗口句柄
HWND GetMainWnd()
{
return g_hMainWnd;
}
//
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd );
// Windows程序结束
void AppTerm();
// Windows消息循环和主循环
int AppMsgLoop();
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );
//
// Windows程序主函数
int WINAPI WinMain(
IN HINSTANCE hInstance,
IN HINSTANCE hPrevInstance,
IN LPSTR lpCmdLine,
IN int nShowCmd
)
{
// 保存主程序句柄
g_hTheApp = hInstance;
// 初始化Window窗口和程序
g_hMainWnd = AppInit( g_hTheApp, nShowCmd );
if( g_hMainWnd == NULL )
{
MessageBox( NULL, "Can't Create Main Window", "Error", MB_OK );
return 0;
}
// 进入主消息循环
return AppMsgLoop();
}
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd )
{
return NULL;
}
// Windows程序结束
void AppTerm()
{
}
// Windows消息循环和主循环
int AppMsgLoop()
{
return 0;
}
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
return DefWindowProc( hWnd, msg, wParam, lParam );
}
24.在AppInit()函数里注册程序和创建主窗口
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd )
{
// 程序实例类名
static char szWindowsClassName[] = "TankBattleCity";
static int iWindowWidth = 800; // 窗口宽度
static int iWindowHeight = 600; // 窗口高度
// 注册窗口类
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
// 设置主窗口消息处理函数
wc.lpfnWndProc = AppWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( 0, IDI_APPLICATION );
wc.hCursor = LoadCursor( 0, IDC_ARROW );
wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
wc.lpszMenuName = 0;
wc.lpszClassName = szWindowsClassName;
if( !RegisterClass( &wc ) )
{
MessageBox( NULL, "RegisterClass - Failed", "Error", MB_OK );
return false;
}
// 创建主窗口
HWND hWnd = CreateWindow( szWindowsClassName
, szWindowsClassName
, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
, CW_USEDEFAULT
, CW_USEDEFAULT
, iWindowWidth
, iWindowHeight
, 0
, 0
, hInstance
, 0 );
if( NULL == hWnd )
{
MessageBox( NULL, "Create Main Window - Failed", "Error", MB_OK );
return NULL;
}
// 显示主窗口
ShowWindow( hWnd, SW_SHOW );
// 更新主窗口显示内容
UpdateWindow( hWnd );
return hWnd;
}
25.编写AppMsgLoop() 的程序主循环
// Windows消息循环和主循环
int AppMsgLoop()
{
MSG msg;
ZeroMemory( &msg, sizeof(MSG) );
_AppLoop:
// 检测是否有消息要处理
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
// 如果接收不到消息表示要退出程序
if( !GetMessage( &msg, NULL, 0, 0 ) )
{
// 转向结束程序
goto _ExitApp;
}
// 执行消息处理
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
// 如果没有任何消息要处理就调用主循环
Sleep(1);
}
goto _AppLoop;
_ExitApp:
AppTerm();
return (int)msg.wParam;
}
26.在AppWndProc()函数里加入推出程序消息处理
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
break;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
到现在我们已经可以编译并执行一个没有做任何事情的Windows程序,这里我不想和任何人讨论关于编码好坏的问题,例如在AppMsgLoop()函数中用了goto和标号。我个人觉得在这里不适宜讨论这个问题。我们重要的是知道如何让程序工作,有关于Windows编程的细节你可以查阅《Windows程序设计》一书。
第二章 简化的程序接口
前一章中我们介绍了如何编写Windows程序,已经完成了一个简单的有一个主窗口的Windows程序,接下来我们钻心写我们的游戏程序了。为了以后的编写方便使各部分程序内容彼此独立,我们现在开始定义一个程序简单的程序接口,我们以后就不需要再顾虑太多的Windows程序相关的内容。
程序接口的实现我们用到了C++中最常用的一种功能虚拟函数(virtual function) 来实现接口功能。在这里你只需要知道在基类中定义的函数前面加了一个virtual关键字,以后派生类中都可以重写该函数内容。
1. 打开AppEntry.h 输入下面程序
#ifndef __AppEntry_H__
#define __AppEntry_H__
#include <windows.h>
class IAppEntry
{
public:
// 初始化程序
virtual bool Initialize( HINSTANCE hInstance, HWND hWnd ) = 0;
// 结束程序
virtual void Terminal() = 0;
// 主处理函数
virtual void Process() = 0;
// 主渲染函数
virtual void Render() = 0;
// 主窗口消息处理函数
virtual LRESULT WndProc( UINT message, WPARAM wParam, LPARAM lParam ) = 0;
// 定义Window程序类名函数
virtual const char *WindowClassName() = 0;
// 定义Window窗口宽度
virtual const int WindowWidth() = 0;
// 定义Window窗口高度
virtual const int WindowHeight() = 0;
// 获得 主程序句柄
virtual HINSTANCE GetInstance() = 0;
// 获得 主窗口句柄
virtual HWND GetWnd() = 0;
};
#endif // __AppEntry_H__
这里每一个函数 后面都写了一个 = 0 表示该函数是纯虚函数, 再派生类中必须重写。这种方法是定义接口的常用方法。
2. 打开WinApp.cpp 加入下面代码
// Windows消息循环和主循环
int AppMsgLoop();
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );
// 获得简单程序接口实例,我们将在GameApp.cpp中定义
IAppEntry *AppEntryClass();
//
// Windows程序主函数
int WINAPI WinMain(
IN HINSTANCE hInstance,
IN HINSTANCE hPrevInstance,
IN LPSTR lpCmdLine,
IN int nShowCmd
)
3. 在WinMain函数中加入下面代码
// Windows程序主函数
int WINAPI WinMain(
IN HINSTANCE hInstance,
IN HINSTANCE hPrevInstance,
IN LPSTR lpCmdLine,
IN int nShowCmd
)
{
// 保存主程序句柄
g_hTheApp = hInstance;
// 初始化Window窗口和程序
g_hMainWnd = AppInit( g_hTheApp, nShowCmd );
if( g_hMainWnd == NULL )
{
MessageBox( NULL, "Can't Create Main Window", "Error", MB_OK );
return 0;
}
// 初始化主程序
if( !AppEntryClass()->Initialize( g_hTheApp, g_hMainWnd ) )
{
MessageBox( NULL, "Initialize AppEntry Failed", "Error", MB_OK );
return 0;
}
// 进入主消息循环
return AppMsgLoop();
}
4. 在AppTerm() 函数里加入下面代码
// Windows程序结束
void AppTerm()
{
// 结束程序
AppEntryClass()->Terminal();
}
5. 在AppMsgLoop()函数里加入下面代码
// Windows消息循环和主循环
int AppMsgLoop()
{
MSG msg;
ZeroMemory( &msg, sizeof(MSG) );
_AppLoop:
// 检测是否有消息要处理
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
// 如果接收不到消息表示要退出程序
if( !GetMessage( &msg, NULL, 0, 0 ) )
{
// 转向结束程序
goto _ExitApp;
}
// 执行消息处理
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
// 如果没有任何消息要处理就调用主循环
AppEntryClass()->Process();
Sleep(1);
}
goto _AppLoop;
_ExitApp:
AppTerm();
return (int)msg.wParam;
}
6. 在AppWndProc()函数里加入下面代码
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
break;
}
// 调用简单程序接口的消息处理函数,如果返回不是FALSE 就直接完成消息处理
// 否则 继续调用Windows程序默认消息处理函数.
LRESULT lr = AppEntryClass()->WndProc( msg, wParam, lParam );
if( lr != FALSE )
return lr;
return DefWindowProc( hWnd, msg, wParam, lParam );
}
7. 在Tank项目上建一个新目录GameApp并创建两个文件 AppGame.cpp AppGame.h
8. 在AppGame.h里 加入下面代码
#ifndef __AppGame_H__
#define __AppGame_H__
#include "WinApp.h"
class CAppGame : public IAppEntry
{
public:
CAppGame();
virtual ~CAppGame();
// 初始化程序
virtual bool Initialize( HINSTANCE hInstance, HWND hWnd );
// 结束程序
virtual void Terminal();
// 主处理函数
virtual void Process();
// 主渲染函数
virtual void Render();
// 主窗口消息处理函数
virtual LRESULT WndProc( UINT message, WPARAM wParam, LPARAM lParam );
// 定义Window程序类名函数
virtual const char *WindowClassName();
// 定义Window窗口宽度
virtual const int WindowWidth();
// 定义Window窗口高度
virtual const int WindowHeight();
// 获得 主程序句柄
virtual HINSTANCE GetInstance();
// 获得 主窗口句柄
virtual HWND GetWnd();
protected:
// 保存主程序句柄
HINSTANCE m_hInstance;
// 保存主窗口句柄
HWND m_hWnd;
};
#endif // __AppGame_H__
9. 打开AppGame.cpp 加入简单接口程序实例,和获得程序实例的函数
#include "AppGame.h"
// 定义主游戏程序实例
CAppGame theAppGame;
// 定义获得简单程序接口实例函数,这个在WinApp.cpp中用到,而且必须定义
// 这是Windows程序调用游戏程序的接口
IAppEntry *AppEntryClass()
{
return &theAppGame;
}
10.加入下面代码从写简单程序接口的纯虚函数内容
//
CAppGame::CAppGame()
{
m_hInstance = NULL;
m_hWnd = NULL;
}
CAppGame::~CAppGame()
{
}
// 初始化程序
bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )
{
m_hInstance = hInstance;
m_hWnd = hWnd;
return true;
}
// 结束程序
void CAppGame::Terminal()
{
}
// 主处理函数
void CAppGame::Process()
{
}
// 主渲染函数
void CAppGame::Render()
{
}
// 主窗口消息处理函数
LRESULT CAppGame::WndProc( UINT message, WPARAM wParam, LPARAM lParam )
{
return FALSE;
}
// 定义Window程序类名函数
const char *CAppGame::WindowClassName()
{
return "TankBattleCity";
}
// 定义Window窗口宽度
const int CAppGame::WindowWidth()
{
return 800;
}
// 定义Window窗口高度
const int CAppGame::WindowHeight()
{
return 600;
}
// 获得 主程序句柄
HINSTANCE CAppGame::GetInstance()
{
return m_hInstance;
}
// 获得 主窗口句柄
HWND CAppGame::GetWnd()
{
return m_hWnd;
}
11.打开WinApp.cpp, 并在 AppInit()函数里做如下修改
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd )
{
// 程序实例类名
const char *szWindowsClassName = AppEntryClass()->WindowClassName();
static int iWindowWidth = AppEntryClass()->WindowWidth(); // 窗口宽度
static int iWindowHeight = AppEntryClass()->WindowHeight(); // 窗口高度
// 注册窗口类
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
12.介绍一点小工具。在Tank项目上加入一个Utility目录 加入FormatString.h 和FormatString.cpp 并加入下面代码
#ifndef __FormatString_H__
#define __FromatString_H__
#include <assert.h>
// 格式串垫片函数模版
template <const int iBufLen> inline const char *FormatString( const char *szFormat, ... )
{
static char szOutStr[iBufLen];
szOutStr[0] = 0;
va_list vl;
va_start( vl, szFormat );
vsprintf(szOutStr, szFormat, vl);
va_end(vl);
assert( strlen(szOutStr) < iBufLen && "Critical Damage" );
return szOutStr;
}
// 我给出一个常用的缓冲大小为1024字节的宏
#define FSTR FormatString<1024>
// 用法:
// OutputDebugString( FSTR( "Format Out %s", "ok" ) );
#endif // __FormatString_H__
我们可以在任何需要输出字符串格式化时使用它 ,这种方法很方便。这个不能用来嵌套调用,也不能用于复杂线程调用,static char szOutStr[iBufLen]; 这是个唯一的内存地址,其他函数调用改变了他的内容就会出错。也就是说用这个工具组合出来的字符串要立即调用。
现在我们项目文件如下图:
第三章 显示图片和基本绘图
这一章我将介绍如何用WindowsGDI显示图片到程序主窗口上,用GDI显示只是为了简单方便,来完成我们的任务,如果要更高效的方法可以使用 DirectX 3D 或 DirectDraw接口。
1. 首先我们准备好下面这张图片我把它放在运行目录RunTime里
取名为ImageC.bmp,接下来就是如何把这张图片显示出来。
2.在Tank项目里加入Render目录,并增加文件 GDIGraphicsDevice.cpp GDIGraphicsDevice.h GDISurface.cpp GDISurface.h GDITextRender.cpp GDITextRender.h
3.打开GDIGraphicsDevice.h 加入下面代码
#ifndef __GDIGraphicsDevice_H__
#define __GDIGraphicsDevice_H__
#include <Windows.h>
// 绘图设备,这里指最后能够显示在屏幕上的设备,这是虚拟的,
// 我们把它叫做图形设备。
class CGDIGraphicsDevice
{
public:
CGDIGraphicsDevice();
virtual ~CGDIGraphicsDevice();
// 创建设备, 让设备操作对象指向 某个窗口
bool Create( HWND hWnd, int iWidth, int iHeight );
// 释放设备
void Release();
// 更新显示,使主表面内容显示到屏幕上
void UpdateFrame( HDC hdc );
// 获得操作指向的窗口
HWND GetWnd();
protected:
// 调整窗口大小
void AdjuestWindowSize( int iWidth, int iHeight );
protected:
// 操作指向的窗口句柄
HWND m_hWnd;
};
#endif // __GDIGraphicsDevice_H__
4.打开GDIGraphicsDevice.cpp 加入下面代码
#include "GDIGraphicsDevice.h"
CGDIGraphicsDevice::CGDIGraphicsDevice()
{
m_hWnd = NULL;
}
CGDIGraphicsDevice::~CGDIGraphicsDevice()
{
}
bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )
{
return true;
}
void CGDIGraphicsDevice::Release()
{
}
void CGDIGraphicsDevice::UpdateFrame( HDC hdc )
{
}
HWND CGDIGraphicsDevice::GetWnd()
{
return m_hWnd;
}
//
// 调整窗口大小 使绘图区域等于 iWidth iHeight 指示的大小
void CGDIGraphicsDevice::AdjuestWindowSize( int iWidth, int iHeight )
{
}
5.实现调整窗口大小函数
// 调整窗口大小 使绘图区域等于 iWidth iHeight 指示的大小
void CGDIGraphicsDevice::AdjuestWindowSize( int iWidth, int iHeight )
{
// 定义窗口在屏幕上的位置 和 窗口显示区位置
RECT rcScreen,rcClient;
// 获得窗口在屏幕上的位置 和 窗口显示区位置
GetWindowRect( m_hWnd, &rcScreen );
GetClientRect( m_hWnd, &rcClient );
// 计算两个区域的宽和高
int rScreenWidth = rcScreen.right - rcScreen.left + 1;
int rScreenHeight = rcScreen.bottom - rcScreen.top + 1;
int rClientWidth = rcClient.right - rcClient.left + 1;
int rClientHeight = rcClient.bottom - rcClient.top + 1;
// 计算两个区域的大小差
int rW = rScreenWidth - rClientWidth;
int rH = rScreenHeight - rClientHeight;
// 计算出新窗口大小
rcScreen.right = iWidth + rW;
rcScreen.bottom = iHeight + rH;
// 设置窗口大小
SetWindowPos( m_hWnd,NULL,rcScreen.left, rcScreen.top, rcScreen.right,rcScreen.bottom,SWP_FRAMECHANGED );
// 显示出Windows
ShowWindow( m_hWnd, SW_SHOW );
// 更新窗口
UpdateWindow( m_hWnd );
}
6.实现创建设备函数
// 创建设备, 让设备操作对象指向 某个窗口
bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )
{
// 保存窗口句柄
m_hWnd = hWnd;
// 调整显示区大小
AdjuestWindowSize( iWidth, iHeight );
return true;
}
7.打开GDISurface.h 输入下面代码
#ifndef __GDISurface_H__
#define __GDISurface_H__
#include <windows.h>
class CGDIGraphicsDevice;
// 表面类 用来存放一个图片,并用来显示图片
class CGDISurface
{
public:
// 位块传送方式
enum SurfaceBltMode
{
BLT_BLOCK, // 复制方式, 完全显示在目标上
BLT_ALPHATEST, // 透明色检测方式, 如果遇到 RGB( 0, 0, 255 ) 图片里的纯蓝色就跳过
BLT_ALPHABLEND, // 未支持
};
CGDISurface();
virtual ~CGDISurface();
// 创建一个位图
bool Create( CGDIGraphicsDevice *pDevice, int iWidth, int iHeight );
// 从文件读取一个位图
bool LoadBmp( CGDIGraphicsDevice *pDevice, const char *szFileName );
// 释放位图
void Release();
// 获得宽
int GetWidth();
// 获得高
int GetHeight();
// 获得位图设备句柄
HDC GetDC();
// 获得透明通道位图设备句柄
HDC GetMaskDC();
// 清除表面为 某种颜色
void Clear( COLORREF c = RGB( 0, 0, 0 ) );
// 画店
void SetPixel( int x, int y, COLORREF c );
// 画线
void Line( int x1, int y1, int x2, int y2, COLORREF c );
// 画矩形
void Rect( int x1, int y1, int x2, int y2, COLORREF c );
// 位图传送
void Blt( CGDISurface *pSurface, int x, int y, int rx, int ry, int w, int h, SurfaceBltMode sbmMode );
protected:
// 表面宽度
int m_iWidth;
// 表面高度
int m_iHeight;
// 位图句柄
HBITMAP m_hBitmap;
// 位图透明通道图句柄
HBITMAP m_hMaskBitmap;
// 保存两种位图句柄
HBITMAP m_hOldBitmap;
HBITMAP m_hOldMaskBitmap;
// 两个用来显示的设备句柄
// 位图设备句柄
HDC m_hDC;
// 透明通道位图设备句柄
HDC m_hMaskDC;
};
#endif // __GDISurface_H__
8.打开GDISurface.cpp 输入下面代码
#include "GDISurface.h"
#include "GDIGraphicsDevice.h"
CGDISurface::CGDISurface()
{
m_iWidth = 0;
m_iHeight = 0;
m_hBitmap = NULL;
m_hMaskBitmap = NULL;
m_hOldBitmap = NULL;
m_hOldMaskBitmap = NULL;
m_hDC = NULL;
m_hMaskDC = NULL;
}
CGDISurface::~CGDISurface()
{
}
// 创建一个位图
bool CGDISurface::Create( CGDIGraphicsDevice *pDevice, int iWidth, int iHeight )
{
return true;
}
// 从文件读取一个位图
bool CGDISurface::LoadBmp( CGDIGraphicsDevice *pDevice, const char *szFileName )
{
return true;
}
// 释放位图
void CGDISurface::Release()
{
}
// 获得宽
int CGDISurface::GetWidth()
{
return m_iWidth;
}
// 获得高
int CGDISurface::GetHeight()
{
return m_iHeight;
}
// 获得位图设备句柄
HDC CGDISurface::GetDC()
{
return m_hDC;
}
// 获得透明通道位图设备句柄
HDC CGDISurface::GetMaskDC()
{
return m_hMaskDC;
}
// 清除表面为 某种颜色
void CGDISurface::Clear( COLORREF c )
{
}
// 画店
void CGDISurface::SetPixel( int x, int y, COLORREF c )
{
}
// 画线
void CGDISurface::Line( int x1, int y1, int x2, int y2, COLORREF c )
{
}
// 画矩形
void CGDISurface::Rect( int x1, int y1, int x2, int y2, COLORREF c )
{
}
// 位图传送
void CGDISurface::Blt( CGDISurface *pSurface, int x, int y, int rx, int ry, int w, int h, BltMode iMode )
{
}
10. 实现创建位图
// 创建一个位图
bool CGDISurface::Create( CGDIGraphicsDevice *pDevice, int iWidth, int iHeight )
{
// 获得主窗口绘图设备句柄
HWND hWnd = pDevice->GetWnd();
HDC hdcWindow = ::GetDC( hWnd );
// 创建图形设备句柄
m_hDC = ::CreateCompatibleDC( hdcWindow );
m_hMaskDC = ::CreateCompatibleDC( hdcWindow );
// 创建位图和透明位图
m_hBitmap = ::CreateCompatibleBitmap( hdcWindow, iWidth, iHeight );
m_hMaskBitmap = ::CreateBitmap( iWidth, iHeight, 1, 1, NULL );
// 关联设备和位图句柄
m_hOldBitmap = (HBITMAP)::SelectObject( m_hDC, m_hBitmap );
m_hOldMaskBitmap = (HBITMAP)::SelectObject( m_hMaskDC, m_hMaskBitmap );
// 制作透明通道位图
::SetBkColor( m_hDC, RGB(0,0,255) );
::BitBlt( m_hMaskDC, 0, 0, iWidth, iHeight, m_hDC, 0, 0, SRCCOPY );
::SetBkColor( m_hDC, RGB(0,0,0) );
::SetTextColor( m_hDC, RGB(255,255,255) );
::BitBlt( m_hDC, 0, 0, iWidth, iHeight, m_hMaskDC, 0, 0, SRCAND );
// 释放主窗口绘图句柄
::ReleaseDC( hWnd, hdcWindow );
// 保存表面大小
m_iWidth = iWidth;
m_iHeight = iHeight;
return true;
}
11. 实现从文件读取位图, 方法和创建位图类似,只是原位图从文件用LoadImage读入。
// 从文件读取一个位图
bool CGDISurface::LoadBmp( CGDIGraphicsDevice *pDevice, const char *szFileName )
{
// 读取位图文件信息,确定位图大小
FILE *fp = fopen( szFileName, "rb" );
if( NULL == fp )
{
OutputDebugString( FSTR( "Open bmp file [%s] failed(%s:%d)", szFileName, __FILE__, __LINE__ ) );
return false;
}
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
fread( &bmfh, sizeof(BITMAPFILEHEADER), 1, fp );
fread( &bmih, sizeof(BITMAPINFOHEADER), 1, fp );
fclose(fp);
// 如果不是位图则返回失败
if( bmfh.bfType != 0x4D42 )
{
OutputDebugString( FSTR( "the bmp file [%s] type is failed(%s:%d)", szFileName, __FILE__, __LINE__ ) );
return false;
}
HBITMAP hBmp = (HBITMAP)::LoadImage(NULL, szFileName, IMAGE_BITMAP, bmih.biWidth, bmih.biHeight, LR_LOADFROMFILE | LR_CREATEDIBSECTION );
m_iWidth = bmih.biWidth;
m_iHeight = bmih.biHeight;
HWND hWnd = pDevice->GetWnd();
HDC hdcWindow = ::GetDC( hWnd );
m_hBitmap = ::CreateCompatibleBitmap( hdcWindow, m_iWidth, m_iHeight );
m_hMaskBitmap = ::CreateBitmap( m_iWidth, m_iHeight, 1, 1, NULL );
HDC hTempDC = ::CreateCompatibleDC( hdcWindow );
m_hDC = ::CreateCompatibleDC( hdcWindow );
m_hMaskDC = ::CreateCompatibleDC( hdcWindow );
HBITMAP hOldBitmap = (HBITMAP)::SelectObject( hTempDC, hBmp );
m_hOldBitmap = (HBITMAP)::SelectObject( m_hDC, m_hBitmap );
m_hOldMaskBitmap = (HBITMAP)::SelectObject( m_hMaskDC, m_hMaskBitmap );
::BitBlt( m_hDC, 0, 0, m_iWidth, m_iHeight, hTempDC, 0, 0, SRCCOPY );
::SetBkColor( m_hDC, RGB(0,0,255) );
::BitBlt( m_hMaskDC, 0, 0, m_iWidth, m_iHeight, m_hDC, 0, 0, SRCCOPY );
::SetBkColor( m_hDC, RGB(0,0,0) );
::SetTextColor( m_hDC, RGB(255,255,255) );
::BitBlt( m_hDC, 0, 0, m_iWidth, m_iHeight, m_hMaskDC, 0, 0, SRCAND );
::SelectObject( hTempDC, hOldBitmap );
DeleteDC( hTempDC );
DeleteObject( hBmp );
::ReleaseDC( hWnd, hdcWindow );
return true;
}
12. 实现释放位图
// 释放位图
void CGDISurface::Release()
{
::SelectObject( m_hDC, m_hOldBitmap );
::SelectObject( m_hMaskDC, m_hOldMaskBitmap );
::DeleteDC( m_hMaskDC );
::DeleteDC( m_hDC );
::DeleteObject( m_hBitmap );
::DeleteObject( m_hMaskBitmap );
}
13. 实现4个基本绘图操作
// 清除表面为 某种颜色
void CGDISurface::Clear( COLORREF c )
{
// 创建填充画笔
HBRUSH hBrush = CreateSolidBrush( c );
// 填充整个表面
RECT rect = { 0, 0, m_iWidth, m_iHeight };
FillRect( m_hDC, &rect, hBrush );
// 释放画笔
DeleteObject( hBrush );
}
// 画店
void CGDISurface::SetPixel( int x, int y, COLORREF c )
{
::SetPixel( m_hDC, x, y, c );
}
// 画线
void CGDISurface::Line( int x1, int y1, int x2, int y2, COLORREF c )
{
HPEN hPen = CreatePen(PS_SOLID, 1, c );
HPEN hOldPen = SelectPen( m_hDC, hPen );
MoveToEx( m_hDC, x1, y1, (LPPOINT) NULL );
LineTo( m_hDC, x2, y2 );
SelectPen( m_hDC, hOldPen );
DeletePen( hPen );
}
// 画矩形
void CGDISurface::Rect( int x1, int y1, int x2, int y2, COLORREF c )
{
// 画4条线 矩形边框
Line( x1, y1, x2, y1, c );
Line( x1, y2, x2, y2, c );
Line( x1, y1, x1, y2, c );
Line( x2, y1, x2, y2, c );
}
14. 实现位图传送功能
// 位图传送
void CGDISurface::Blt( CGDISurface *pSurface, int x, int y, int rx, int ry, int w, int h, BltMode iMode )
{
HDC hdcDst = pSurface->GetDC();
switch( iMode )
{
case BLT_BLOCK:
// 基本复制方式传送位图
BitBlt( hdcDst, x, y, w, h, m_hDC, rx, ry, SRCCOPY );
break;
case BLT_ALPHATEST:
// 带透明色方式传送位图
SetBkColor( hdcDst, RGB(255,255,255) );
SetTextColor( hdcDst, RGB(0,0,0) );
BitBlt( hdcDst, x, y, w, h, m_hMaskDC, rx, ry, SRCAND );
BitBlt( hdcDst, x, y, w, h, m_hDC, rx, ry, SRCPAINT );
break;
case BLT_ALPHABLEND:
break;
}
}
15. 打开GDIGraphicsDevice.h 加入下面代码
#ifndef __GDIGraphicsDevice_H__
#define __GDIGraphicsDevice_H__
#include <Windows.h>
class CGDISurface;
// 绘图设备,这里指最后能够显示在屏幕上的设备,这是虚拟的,
// 我们把它叫做图形设备。
class CGDIGraphicsDevice
{
public:
CGDIGraphicsDevice();
virtual ~CGDIGraphicsDevice();
// 创建设备, 让设备操作对象指向 某个窗口
bool Create( HWND hWnd, int iWidth, int iHeight );
// 释放设备
void Release();
// 更新显示,使主表面内容显示到屏幕上
void UpdateFrame( HDC hdc );
// 获得操作指向的窗口
HWND GetWnd();
// 获得主表面
CGDISurface *GetMainSurface();
protected:
// 调整窗口大小
void AdjuestWindowSize( int iWidth, int iHeight );
protected:
// 操作指向的窗口句柄
HWND m_hWnd;
// 住缓冲表面
CGDISurface *m_pMainSurface;
};
#endif // __GDIGraphicsDevice_H__
16. 打开GDIGraphicsDevice.cpp 修改和增加下面代码
#include "GDIGraphicsDevice.h"
#include "GDISurface.h"
CGDIGraphicsDevice::CGDIGraphicsDevice()
{
m_hWnd = NULL;
m_pMainSurface = NULL;
}
CGDIGraphicsDevice::~CGDIGraphicsDevice()
{
}
// 创建设备, 让设备操作对象指向 某个窗口
bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )
{
// 保存窗口句柄
m_hWnd = hWnd;
// 调整显示区大小
AdjuestWindowSize( iWidth, iHeight );
m_pMainSurface = new CGDISurface();
return true;
}
// 释放设备
void CGDIGraphicsDevice::Release()
{
delete m_pMainSurface;
m_pMainSurface = NULL;
}
17. 增加获取主表面函数
// 获得主表面
CGDISurface *CGDIGraphicsDevice::GetMainSurface()
{
return m_pMainSurface;
}
18. 增加创建主表面
// 创建设备, 让设备操作对象指向 某个窗口
bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )
{
// 保存窗口句柄
m_hWnd = hWnd;
// 调整显示区大小
AdjuestWindowSize( iWidth, iHeight );
m_pMainSurface = new CGDISurface();
if( !m_pMainSurface->Create( this, iWidth, iHeight ) )
{
OutputDebugString( FSTR( "GraphicsDevice Create Main Surface Failed" ) );
return false;
}
return true;
}
// 释放设备
void CGDIGraphicsDevice::Release()
{
m_pMainSurface->Release();
delete m_pMainSurface;
m_pMainSurface = NULL;
}
19. 增加更新显示函数功能
// 更新显示,使主表面内容显示到屏幕上
void CGDIGraphicsDevice::UpdateFrame( HDC hdc )
{
// 如果主表面没有创建成功就返回
if( NULL == m_pMainSurface )
return;
HDC hdcSrc = m_pMainSurface->GetDC();
int iWidth = m_pMainSurface->GetWidth();
int iHeight = m_pMainSurface->GetHeight();
// 更新显示到屏幕,将由 WM_PAINT 调用
BitBlt( hdc, 0, 0, iWidth, iHeight, hdcSrc, 0, 0, SRCCOPY );
}
20. 为了方便我们在项目Tank上加入一个全局数据定义文件,大家可以在此定义非常公用或重要的全局变量。并加入两个文件 ShareData.h ShareData.cpp
21. 打开ShareData.h 加入下面代码
#ifndef __ShareData_H__
#define __ShareData_H__
#include "GDIGraphicsDevice.h"
#include "GDISurface.h"
#include "GDITextRender.h"
extern CGDIGraphicsDevice *theGraphicsDevice;
#endif // __ShareData_H__
22. 打开ShareData.cpp 加入下面代码
#include "ShareData.h"
CGDIGraphicsDevice *theGraphicsDevice = NULL;
23. 打开AppGame.cpp里加入下面代码
#include "AppGame.h"
#include "ShareData.h"
CGDISurface theSurface;
// 定义主游戏程序实例
CAppGame theAppGame;
24. 加入下面代码,创建
// 初始化程序
bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )
{
m_hInstance = hInstance;
m_hWnd = hWnd;
// 创建图形设备
theGraphicsDevice = new CGDIGraphicsDevice();
if( !theGraphicsDevice->Create( m_hWnd, WindowWidth(), WindowHeight() ) )
{
OutputDebugString( "Create GraphicsDevice Failed" );
return false;
}
// 读取位图
if( !theSurface.LoadBmp( theGraphicsDevice, "ImageC.bmp" ) )
{
OutputDebugString( "Load bitmap Failed" );
return false;
}
return true;
}
// 结束程序
void CAppGame::Terminal()
{
// 释放位图
theSurface.Release();
// 释放图形设备
theGraphicsDevice->Release();
delete theGraphicsDevice;
}
25. 修改下面代码来显示读入的图片
// 主处理函数
void CAppGame::Process()
{
Render();
}
// 主渲染函数
void CAppGame::Render()
{
// 显示位图
theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_BLOCK );
}
26. 在消息处理函数里加入更新显示代码
// 主窗口消息处理函数
LRESULT CAppGame::WndProc( UINT message, WPARAM wParam, LPARAM lParam )
{
HDC hdc;
PAINTSTRUCT ps;
switch( message )
{
case WM_PAINT:
{
hdc = BeginPaint( m_hWnd, &ps );
if( theGraphicsDevice )
{
// 调用图形设备更新显示
theGraphicsDevice->UpdateFrame( hdc );
}
EndPaint( m_hWnd, &ps );
}
return TRUE;
case WM_ERASEBKGND:
return TRUE;
}
return FALSE;
}
27. 在CAppGame::Render()里加入更新显示通知
// 主渲染函数
void CAppGame::Render()
{
// 显示位图
theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_BLOCK );
// 通知Window程序更新显示窗口
RECT rcWindow;
GetClientRect( m_hWnd, &rcWindow );
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );
}
28. 运行程序可以看到如下效果
29.在CAppGame::Render() 里加入下面代码
// 主渲染函数
void CAppGame::Render()
{
// 清除主表面
theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );
// 显示位图
theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_BLOCK );
// 通知Window程序更新显示窗口
RECT rcWindow;
GetClientRect( m_hWnd, &rcWindow );
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );
}
大家可以看到下面效果
30.将CAppGame::Render() 修改为
void CAppGame::Render()
{
// 清除主表面
theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );
// 显示位图
theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );
// 通知Window程序更新显示窗口
RECT rcWindow;
GetClientRect( m_hWnd, &rcWindow );
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );
}
可以看到如下效果
蓝色已经没有了,绘制方式改为透明方式。
31.我们将theSurface 改为 theImageC 并且放到ShareData.cpp 中定义 以便以后使用方便
// 初始化程序
bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )
{
m_hInstance = hInstance;
m_hWnd = hWnd;
// 创建图形设备
theGraphicsDevice = new CGDIGraphicsDevice();
if( !theGraphicsDevice->Create( m_hWnd, WindowWidth(), WindowHeight() ) )
{
OutputDebugString( "Create GraphicsDevice Failed" );
return false;
}
// 读取位图
theImageC = new CGDISurface();
if( !theImageC->LoadBmp( theGraphicsDevice, "ImageC.bmp" ) )
{
OutputDebugString( "Load bitmap Failed" );
return false;
}
return true;
}
// 结束程序
void CAppGame::Terminal()
{
// 释放位图
theImageC->Release();
delete theImageC;
theImageC = NULL;
// 释放图形设备
theGraphicsDevice->Release();
delete theGraphicsDevice;
theGraphicsDevice = NULL;
}
// 主处理函数
void CAppGame::Process()
{
Render();
}
// 主渲染函数
void CAppGame::Render()
{
// 清除主表面
theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );
// 显示位图
theImageC->Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );
// 通知Window程序更新显示窗口
RECT rcWindow;
GetClientRect( m_hWnd, &rcWindow );
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );
}
第四章 文字显示 计时器 刷新率
在编写程序时有些很重要的信息要显示出来,帮助调试程序,其中显示文字处于非常重要的位置。计时器我们将用到系统函数timeGetTime(),它是用来获得系统从开始到现在所经历的毫秒数,记录一个开始时间,然后不断判断现在的时间和开始时间的间隔,以达到控制某一段代码以一定的频率执行。刷新率是表示在一秒钟之内更新显示了多少次,最后我们把它显示在屏幕上。
1. 打开GDITextRender.h 输入下面代码
#ifndef __GDITextRender_H__
#define __GDITextRender_H__
#include <windows.h>
class CGDISurface;
// 文字显示类
class CGDITextRender
{
public:
CGDITextRender();
virtual ~CGDITextRender();
// 创建字体
bool Create( PLOGFONT pFont );
// 释放
void Release();
// 向表面输出文字
void TextOut( CGDISurface *pSurface, int x, int y, const char *szString, COLORREF c, int len = 0 );
protected:
// 字体句柄
HFONT m_hFont;
};
#endif // __GDITextRender_H__
2. 打开GDITextRender.cpp 输入下面代码
#include "GDITextRender.h"
CGDITextRender::CGDITextRender()
{
m_hFont = NULL;
}
CGDITextRender::~CGDITextRender()
{
}
// 创建字体
bool CGDITextRender::Create( PLOGFONT pFont )
{
return true;
}
// 释放
void CGDITextRender::Release()
{
}
// 向表面输出文字
void CGDITextRender::TextOut( CGDISurface *pSurface, int x, int y, const char *szString, COLORREF c, int len )
{
}
3. 实现创建字体功能
// 创建字体
bool CGDITextRender::Create( PLOGFONT pFont )
{
// 创建 pFont 指定的字体
m_hFont = CreateFont(
pFont->lfHeight,
pFont->lfWidth,
pFont->lfEscapement,
pFont->lfOrientation,
pFont->lfWeight,
pFont->lfItalic,
pFont->lfUnderline,
pFont->lfStrikeOut,
pFont->lfCharSet,
pFont->lfOutPrecision,
pFont->lfClipPrecision,
pFont->lfQuality,
pFont->lfPitchAndFamily,
pFont->lfFaceName );
if( m_hFont == NULL )
{
OutputDebugString( FSTR( "Create Font Failed: %s %d/n", pFont->lfFaceName, pFont->lfHeight ) );
return false;
}
return true;
}
4.实现释放字体
// 释放
void CGDITextRender::Release()
{
// 如果字体已经创建
if( m_hFont )
{
// 删除字体句柄
DeleteObject( m_hFont );
m_hFont = NULL;
}
}
5.修改文字输出函数,加入输出文字功能
#include "GDITextRender.h"
#include "GDISurface.h"
#include "FormatString.h"
#include <windowsx.h>
// 向表面输出文字
void CGDITextRender::TextOut( CGDISurface *pSurface, int x, int y, const char *szString, COLORREF c, int len )
{
// 如果指定的字符串长度是0
if( len == 0 )
{
// 自动获取字符串长度
len = (int)strlen( szString );
}
// 获得表面设备句柄
HDC hdc = pSurface->GetDC();
// 选择字体
HFONT hOldFont = SelectFont( hdc, m_hFont );
// 显示文字
SetBkMode( hdc, TRANSPARENT );
SetBkColor( hdc, RGB(0,0,0) );
SetTextColor( hdc, c );
::TextOut( hdc, x, y, szString, len );
// 恢复字体
SelectFont( hdc, hOldFont );
}
6.打开ShareData.h 加入下面代码
#ifndef __ShareData_H__
#define __ShareData_H__
#include "GDIGraphicsDevice.h"
#include "GDISurface.h"
#include "GDITextRender.h"
extern CGDIGraphicsDevice *theGraphicsDevice;
extern CGDISurface *theImageC;
extern CGDITextRender *theTextRender;
#endif // __ShareData_H__
7.打开ShareData.cpp 加入下面代码
#include "ShareData.h"
CGDIGraphicsDevice *theGraphicsDevice = NULL;
CGDISurface *theImageC = NULL;
CGDITextRender *theTextRender = NULL;
8.打开AppGame.cpp 加入 初始化文字输出功能 和释放
// 初始化程序
bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )
{
m_hInstance = hInstance;
m_hWnd = hWnd;
// 创建图形设备
theGraphicsDevice = new CGDIGraphicsDevice();
if( !theGraphicsDevice->Create( m_hWnd, WindowWidth(), WindowHeight() ) )
{
OutputDebugString( "Create GraphicsDevice Failed" );
return false;
}
// 读取位图
theImageC = new CGDISurface();
if( !theImageC->LoadBmp( theGraphicsDevice, "ImageC.bmp" ) )
{
OutputDebugString( "Load bitmap Failed" );
return false;
}
// 初始化文字输出功能
LOGFONT FontInfo = { 16, 0, 0, 0,
FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
VARIABLE_PITCH,
"宋体" };
theTextRender = new CGDITextRender();
if( !theTextRender->Create( &FontInfo ) )
{
OutputDebugString( "Create theTextRender Failed/n" );
return false;
}
return true;
}
// 结束程序
void CAppGame::Terminal()
{
// 释放文字输出功能
theTextRender->Release();
delete theTextRender;
theTextRender = NULL;
// 释放位图
theImageC->Release();
delete theImageC;
theImageC = NULL;
// 释放图形设备
theGraphicsDevice->Release();
delete theGraphicsDevice;
theGraphicsDevice = NULL;
}
9.在CAppGame::Render()函数里做如下修改来测试文字输出功能
// 主渲染函数
void CAppGame::Render()
{
CGDISurface *pScreen = theGraphicsDevice->GetMainSurface();
// 清除主表面
pScreen->Clear( RGB( 125,0, 23 ) );
// 显示位图
theImageC->Blt( pScreen, 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );
// 显示文字
theTextRender->TextOut( pScreen, 0, 0, "测试文字ABC", RGB( 255, 255, 255 ) );
// 通知Window程序更新显示窗口
RECT rcWindow;
GetClientRect( m_hWnd, &rcWindow );
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );
}
看到如下结果
10.在Tank项目Utility目录里加入Fps.cpp Fps.h
11.打开Fps.h 输入下面代码
#ifndef __Fps_H__
#define __Fps_H__
#include <WTypes.h>
// fps类
class CFps
{
public:
CFps();
virtual ~CFps();
// 统计, 没掉一次 计数器加1 并判断 是否该计算FPS
void Count();
// 获得FPS
int GetFps();
protected:
// 记录刷新率
int m_iFps;
// 统计次数
int m_iCount;
// 上一次记录FPS的时间
DWORD m_dwPrevRecordFpsTime;
// 计算FPS时间间隔
DWORD m_dwRecordFpsInterval;
};
#endif // __Fps_H__
12.打开Fps.cpp 输入下面代码
#include "Fps.h"
#include <mmsystem.h>
#pragma comment( lib, "winmm.lib" )
CFps::CFps()
{
m_iCount = 0;
m_iFps = 0;
m_dwPrevRecordFpsTime = 0;
m_dwRecordFpsInterval = 1000; // 1秒钟记录一次
}
CFps::~CFps()
{
}
void CFps::Count()
{
// 没调用一次就加1
m_iCount++;
// 如果时间已经过去1000毫秒
if( timeGetTime() - m_dwPrevRecordFpsTime >= m_dwRecordFpsInterval )
{
// 记录1秒钟调用次数
m_iFps = m_iCount;
// 复位计数器
m_iCount = 0;
// 复位开始计时时间
m_dwPrevRecordFpsTime = timeGetTime();
}
}
int CFps::GetFps()
{
return m_iFps;
}
13.在ShareData.h 和 ShareData.cpp 中分别加入下面代码
ShareData.h :
#ifndef __ShareData_H__
#define __ShareData_H__
#include "GDIGraphicsDevice.h"
#include "GDISurface.h"
#include "GDITextRender.h"
#include "Fps.h"
extern CGDIGraphicsDevice *theGraphicsDevice;
extern CGDISurface *theImageC;
extern CGDITextRender *theTextRender;
extern CFps *theRenderFps;
#endif // __ShareData_H__
ShareData.cpp :
#include "ShareData.h"
CGDIGraphicsDevice *theGraphicsDevice = NULL;
CGDISurface *theImageC = NULL;
CGDITextRender *theTextRender = NULL;
CFps *theRenderFps = NULL;
14.在AppGame.cpp 中 CAppGame::Initialize() 和 CAppGame::Terminal()中加入下面代码
theTextRender = new CGDITextRender();
if( !theTextRender->Create( &FontInfo ) )
{
OutputDebugString( "Create theTextRender Failed/n" );
return false;
}
// 创建渲染刷新率统计器
theRenderFps = new CFps();
return true;
}
// 结束程序
void CAppGame::Terminal()
{
// 释放渲染刷新率统计器
delete theRenderFps;
// 释放文字输出功能
theTextRender->Release();
15.在 CAppGame::Render() 函数里加入 刷新率统计,并显示在屏幕上,并在
AppGame.cpp 最上加入 #include “FormatString.h”
// 主渲染函数
void CAppGame::Render()
{
CGDISurface *pScreen = theGraphicsDevice->GetMainSurface();
// 清除主表面
pScreen->Clear( RGB( 127,127, 127 ) );
// 显示位图
//theImageC->Blt( pScreen, 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );
// 显示文字
theTextRender->TextOut( pScreen, 0, 0, FSTR( "FPS = %2d", theRenderFps->GetFps() ), RGB( 255, 255, 255 ) );
// 通知Window程序更新显示窗口
RECT rcWindow;
GetClientRect( m_hWnd, &rcWindow );
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );
theRenderFps->Count();
}
运行程序看到如下效果
16.为了给CPU更多的时间,我们不需要程序运行很快,我们如何限制渲染速度,打开AppGame.h 加入下面代码, 再运行可以看到 Fps已经是30了
protected:
// 保存主程序句柄
HINSTANCE m_hInstance;
// 保存主窗口句柄
HWND m_hWnd;
// 最后一次渲染的时间
DWORD m_dwRenderLastTime;
};
17.在主渲染函数中加入
// 主渲染函数
void CAppGame::Render()
{
if( timeGetTime() - m_dwRenderLastTime < 33 )
{
return;
}
m_dwRenderLastTime = timeGetTime();
第五章 精灵动画
在游戏中跑来跑去的小人,小狗,Tank 等叫做精灵,每个精灵都会有一张图画,如果会做动作的会有几张图画交替显示,从而看起来像是在跑。如图 28x28大小的一个图块是精灵动画的一张图片,后面的那个是第二张,我们把这每一张图片叫做帧, 我们说Tank的上方向行走是2帧动画。
在我们的项目中,我们定义帧的方法是 记录一个图快的位置和大小
如图 X,Y,W,H 来表示一个帧。
1.在TANK项目中加入一个目录Animation,并增加6个文件 Frame.h
Frame.cpp Animation.h Animation.cpp SpriteManager.h SpriteManager.cpp
2.在Frame.h 和Frame.cpp 增加帧类代码
Frame.h :
#ifndef __Frame_H__
#define __Frame_H__
#include "GDISurface.h"
// 动画帧类
class CFrame
{
public:
// 在ImageC上的位置和大小
int m_iX;
int m_iY;
int m_iW;
int m_iH;
// 渲染到表面
void Render( CGDISurface *pSurface, int iX, int iY, CGDISurface::BltMode iMode );
};
#endif // __Frame_H__
Frame.cpp :
#include "Frame.h"
#include "ShareData.h"
// 渲染到表面
void CFrame::Render( CGDISurface *pSurface, int iX, int iY, CGDISurface::BltMode iMode )
{
theImageC->Blt( pSurface, iX, iY, m_iX, m_iY, m_iW, m_iH, iMode );
}
3.打开Animation.h 输入下面代码
#ifndef __Animation_H__
#define __Animation_H__
#include "Frame.h"
#include <vector>
// 动画类
class CAnimation
{
public:
CAnimation();
virtual ~CAnimation();
// 加入帧
void Push( CFrame& f );
// 获得总帧数
int GetMaxFrame();
// 获得某帧数据
CFrame *GetFrame( int iFrame );
protected:
// 帧数据
std::vector<CFrame> m_vectorFrame;
};
#endif // __Animation_H__
4. 打开Animation.cpp 输入下面代码
#include "Animation.h"
CAnimation::CAnimation()
{
m_vectorFrame.clear();
}
CAnimation::~CAnimation()
{
m_vectorFrame.clear();
}
// 加入帧
void CAnimation::Push( CFrame& f )
{
m_vectorFrame.push_back( f );
}
// 获得总帧数
int CAnimation::GetMaxFrame()
{
return (int)m_vectorFrame.size();
}
// 获得某帧数据
CFrame *CAnimation::GetFrame( int iFrame )
{
if( iFrame < 0 && iFrame >= (int)m_vectorFrame.size() )
return NULL;
return &m_vectorFrame[iFrame];
}
5. 打开SpriteManager.h 输入下面代码
#ifndef __SpriteManager_H__
#define __SpriteManager_H__
#include "Animation.h"
class CSpriteManager
{
public:
CSpriteManager();
virtual ~CSpriteManager();
// 创建动画数据集
bool Create();
// 释放动画数据集
void Release();
// 获得动画数据
CAnimation *GetAnimation( int index );
// 获得动画总数
int GetAnimationNumber();
protected:
// 存储动画数据
std::vector< CAnimation > m_vectorAnimation;
};
#endif // __SpriteManager_H__
6.打开SpriteManager.cpp 输入下面代码
#include "SpriteManager.h"
CSpriteManager::CSpriteManager()
{
}
CSpriteManager::~CSpriteManager()
{
}
// 创建动画数据集
bool CSpriteManager::Create()
{
return true;
}
// 释放动画数据集
void CSpriteManager::Release()
{
m_vectorAnimation.clear();
}
// 获得动画数据
CAnimation *CSpriteManager::GetAnimation( int index )
{
if( index < 0 || index >= (int)m_vectorAnimation.size() )
return NULL;
// 返回index所指的动画
return &m_vectorAnimation[index];
}
// 获得动画总数
int CSpriteManager::GetAnimationNumber()
{
return (int)m_vectorAnimation.size();
}
7.在CSpriteManager::Create()里加入第一个动画,动画也可能只有1帧。下面是加入前两个帧给0号动画。同样的方法,可以在此函数里加入所有动画。
// 创建动画数据集
bool CSpriteManager::Create()
{
CAnimation *pAni;
CFrame f;
// 第一个动画
pAni = new CAnimation();
// 第一帧 (0,0,28,28)
f.m_iX = 0;
f.m_iY = 0;
f.m_iW = 28;
f.m_iH = 28;
pAni->Push( f );
// 第二帧 (28,0,28,28)
f.m_iX = 28;
f.m_iY = 0;
f.m_iW = 28;
f.m_iH = 28;
pAni->Push( f );
// Ani ID = 0 两帧动画加入动画库
m_vectorAnimation.push_back( *pAni );
delete pAni;
return true;
}
8.在ShareData.h ShareData.cpp 里分别加入下面代码
ShareData.h:
#ifndef __ShareData_H__
#define __ShareData_H__
#include "GDIGraphicsDevice.h"
#include "GDISurface.h"
#include "GDITextRender.h"
#include "Fps.h"
#include "SpriteManager.h"
extern CGDIGraphicsDevice *theGraphicsDevice;
extern CGDISurface *theImageC;
extern CGDITextRender *theTextRender;
extern CFps *theRenderFps;
extern CSpriteManager *theSpriteManager;
#endif // __ShareData_H__
ShareData.cpp:
#include "ShareData.h"
CGDIGraphicsDevice *theGraphicsDevice = NULL;
CGDISurface *theImageC = NULL;
CGDITextRender *theTextRender = NULL;
CFps *theRenderFps = NULL;
CSpriteManager *theSpriteManager = NULL;
9.在AppGame.cpp里加入下面初始化和释放动画管理器代码
theTextRender = new CGDITextRender();
if( !theTextRender->Create( &FontInfo ) )
{
OutputDebugString( "Create theTextRender Failed/n" );
return false;
}
// 创建渲染刷新率统计器
theRenderFps = new CFps();
// 创建动画管理器
theSpriteManager = new CSpriteManager();
if( !theSpriteManager->Create() )
{
OutputDebugString( "Create Sprite Manager Failed/n" );
return false;
}
return true;
}
// 结束程序
void CAppGame::Terminal()
{
// 释放动画管理器
theSpriteManager->Release();
delete theSpriteManager;
theSpriteManager = NULL;
10.在 CAppGame::Render()里作如下修改,显示一个动画的一帧。
// 清除主表面
pScreen->Clear( RGB( 127,127, 127 ) );
// 显示位图
//theImageC->Blt( pScreen, 30, 30, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );
// 显示一个动画的一帧
CAnimation *pAni = theSpriteManager->GetAnimation( 0 );
CFrame *pF = pAni->GetFrame( 0 );
pF->Render( pScreen, 100, 100, CGDISurface::BLT_ALPHATEST );
// 显示文字
theTextRender->TextOut( pScreen, 0, 0, FSTR( "FPS = %2d", theRenderFps->GetFps() ), RGB( 255, 255, 255 ) );
大家可以看到显示效果是,一个Tank显示在屏幕上。
10.接下来的任务就是就是把这些动画编排好,以便以后使用。根据下面的图我们可以看出,这组图是很有规律,这是每一个TANK的4个方向上的动画,我写了一个循环按照规则创建动画在CSpriteManager.cpp Create()函数里加入初始化代码
// 创建动画数据集
bool CSpriteManager::Create()
{
CAnimation *pAni;
CFrame f;
// 所有TANK图片都是28x28
f.m_iW = 28;
f.m_iH = 28;
// 两组动画,上面是玩家TANK,下面是地方TANK。
for( int k=0; k<2; k++ )
{
// 每一大组内有8种TANK的动画
for( int l=0; l<8; l++ )
{
// 每种Tank有4个方向上的动画
for( int j=0; j<4; j++ )
{
pAni = new CAnimation();
// 每个方向上的动画有2帧
for( int i=0; i<2; i++ )
{
f.m_iX = l*28*2 + i*28;
f.m_iY = k*28*4 + j*28;
pAni->Push( f );
}
m_vectorAnimation.push_back( *pAni );
delete pAni;
}
}
}
// 动画定位公式是: 动画ID = 坦克型号 * 4 + 方向
return true;
}
11.在CAppGame::Render()里加入并修改代码,运行程序可以看到TANK行走动画,每2秒钟随机变化一个TANK
// 显示位图
//theImageC->Blt( pScreen, 30, 30, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );
// 当前帧
static int iCurFrame = 0;
// 动画计时开始时间
static DWORD dwAniStartTime = timeGetTime();
// 当前动画ID
static int iCurAni = 0;
// 最后一次改变动画的时间
static DWORD dwAniChangeLastTime = timeGetTime();
if( timeGetTime() - dwAniChangeLastTime > 2000 )
{
iCurAni = rand()%64;
dwAniChangeLastTime = timeGetTime();
}
if( timeGetTime() - dwAniStartTime > 33 )
{
iCurFrame++;
if( iCurFrame >= 2 )
iCurFrame = 0;
dwAniStartTime = timeGetTime();
}
// 显示一个动画的一帧
CAnimation *pAni = theSpriteManager->GetAnimation( iCurAni );
CFrame *pF = pAni->GetFrame( iCurFrame );
pF->Render( pScreen, 100, 100, CGDISurface::BLT_ALPHATEST );
第六章 场景