您的位置:首页 > 编程语言

Windows游戏编程快速入门方法

2005-02-03 17:06 656 查看
Windows游戏编程快速入门方法[/b][/b]
[/b]
Easideao(简单思路)

序言:[/b][/b]

从2001年到2005年,在不知不觉中我已经渡过了4年的职业游戏开发生涯。在这4年里经常会有些网友向我询问编程的入门有没有捷径。每一次我都不知怎么回答才算合适,我也一直想表达一下我的思路和想法,但一直都没有能力把自己的见解在书面上表达出来,其实我认为编写程序并不是很难的事情。最关键的是你对他是否有兴趣,最难的是坚持学习。如果没有兴趣,即使你刚刚入了一点们如果不坚持下去,也是一事无成。[/b]
虽然毅力在学习的过程中有着不可置疑的位置,但是有个合适的方法和适合自己的方法还是很重要的。假如你的兴趣和毅力都过了关,我接下来将以一个游戏的代码编写过程写下来,我坚持写下来,你坚持读完,按照我讲述的步骤去做。我这里不会把所有细节都讲述出来,因为那是太庞大的任务,我的力量无法实现,我们下面的方法就是:我说怎么做,你就怎么做,先知道怎样做一些事情,当你能够按照我说的做出正确的结果说明你已经会了,如果有不懂得再去查看相关资料。[/b]
上面说的有些繁琐,我自己也不太愿意写下去了,我的文笔水平有限,请大家谅解。接下来最重要的就是跟着我做。如果你有什么意见或问题可以给我发E-mail : [/b]chinagdh@163.com[/b]。[/b][/b]

第一章[/b] Windows程序[/b]

打开[/b]Visual Studio 2003.net ,选择File -> Blank Solution。[/b]



在[/b]Name栏里输入 BattleCity 并按 ok 按钮, 按browse选择解决方案存放位置[/b]

3.在Solution Explorer 里在 Solution ‘BattleCity’上按右键。在下拉菜单中选择 Add -> New Project。[/b]

4.在Add New Project 对话框里选择 Visual C++ Projects -> Win32 -> Win32 Project,在Name栏里打入 Tank 并按回车[/b]

5.选择Application Settings 并在 Empty project 前面打钩,创建一个空的Win32 项目。[/b]

6.在Tank项目上按右键 选择Add -> New Folder 增加文件夹,并命名WinApp[/b]

7.在WinApp文件夹上按右键选择Add -> Add New Item[/b]

8.选择Visual C++ -> C++ File(.cpp) 在Name栏里输入 WinApp.cpp。 [/b]

9.反复7.8步 增加 WinApp.h AppEntry.cpp AppEntry.h[/b]

10.双击 WinApp.h 打开文件我们在WinApp.h头文件中加入以下代码[/b]

11.以同样的方法处理AppEntry.h , 这种方法保证头文件只被include一次,这是我喜欢用的一种方法也可以在第一行写#pragma once[/b]

12.打开AppEntry.h 加入代码 #include <windows.h>[/b]

13.打开WinApp.h 加入代码 [/b]#include[/b] "AppEntry.h"[/b]

14.打开WinApp.cpp 加入代码 [/b]#include[/b] "WinApp.h"[/b][/b]

15.定义主程序句柄和主窗口句柄[/b]

16.增加获得主程序句柄和主窗口句柄的全局函数[/b]
17. 为方便以后获得主程序句柄和主窗口句柄 在 WinApp.h 中声明[/b]
HINSTANCE GetAppHandle();
HWND GetMainWnd();

18.定义Windows程序主函数,这是一个Windows程序的入口函数,我们认为程序从此函数开始执行。[/b]

19.在Solution Explorer 中选择Tank项目,按右键选择Build 编译一下,看程序是否可以编译。[/b]

编译成功会在Output窗口中出现提示信息 Build:1 succeeded, 0 failed, 0 skipped 表示 成功一个 0 失败 0 跳过

20.设置Tank生成的路径,在项目Tank上按右键选择Properties[/b]

21.选择程序生成路径(Output File)为 ../RunTime/Tank.exe[/b]

22.选择运行路径(Working Directory) 为 ../RunTime 在编译一下[/b]
23.增加必要的几个函数 程序的初始化 结束 主循环 消息处理函数,代码如下[/b]
#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()函数里注册程序和创建主窗口[/b]

// 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() 的程序主循环[/b]

// 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()函数里加入推出程序消息处理[/b]

// 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程序设计》一书。

第二章[/b] 简化的程序接口[/b]

前一章中我们介绍了如何编写Windows程序,已经完成了一个简单的有一个主窗口的Windows程序,接下来我们钻心写我们的游戏程序了。为了以后的编写方便使各部分程序内容彼此独立,我们现在开始定义一个程序简单的程序接口,我们以后就不需要再顾虑太多的Windows程序相关的内容。
程序接口的实现我们用到了C++中最常用的一种功能虚拟函数(virtual function) 来实现接口功能。在这里你只需要知道在基类中定义的函数前面加了一个virtual关键字,以后派生类中都可以重写该函数内容。

1. [/b]打开[/b]AppEntry.h 输入下面程序[/b]

#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. [/b]打开[/b]WinApp.cpp 加入下面代码[/b]

// Windows消息循环和主循环
int AppMsgLoop();

// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );

// 获得简单程序接口实例,我们将在GameApp.cpp中定义[/b]
IAppEntry[/b] *AppEntryClass();[/b]

//////////////////////////////////////////////////////////////////////////

// Windows程序主函数
int WINAPI WinMain(
IN HINSTANCE hInstance,
IN HINSTANCE hPrevInstance,
IN LPSTR lpCmdLine,
IN int nShowCmd
)

3. [/b]在[/b]WinMain函数中加入下面代码[/b]
// 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;
}

// 初始化主程序[/b][/b]
if( !AppEntryClass()->Initialize( g_hTheApp, g_hMainWnd ) )[/b]
{[/b]
MessageBox( NULL, "Initialize AppEntry Failed", "Error", MB_OK );[/b]
return 0;[/b]
}[/b]

// 进入主消息循环
return AppMsgLoop();
}

4. [/b]在[/b]AppTerm() 函数里加入下面代码[/b]
// Windows程序结束
void AppTerm()
{
// 结束程序[/b]
AppEntryClass()->Terminal();[/b]
}
5. [/b]在[/b]AppMsgLoop()函数里加入下面代码[/b]
// 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();[/b]
Sleep(1);
}

goto _AppLoop;

_ExitApp:

AppTerm();

return (int)msg.wParam;
}

6. [/b]在[/b]AppWndProc()函数里加入下面代码[/b]
// Windows消息处理函数
LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage( 0 );
break;
}

// 调用简单程序接口的消息处理函数,如果返回不是FALSE 就直接完成消息处理[/b]
// 否则 继续调用Windows程序默认消息处理函数.[/b]
LRESULT lr = AppEntryClass()->WndProc( msg, wParam, lParam );[/b]
if( lr != FALSE )[/b]
return lr;[/b]

return DefWindowProc( hWnd, msg, wParam, lParam );
}
7. [/b]在[/b]Tank项目上建一个新目录GameApp并创建两个文件 AppGame.cpp AppGame.h[/b]

8. [/b]在[/b]AppGame.h里 加入下面代码[/b]
#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. [/b]打开[/b]AppGame.cpp 加入简单接口程序实例,和获得程序实例的函数[/b]
#include "AppGame.h"

// 定义主游戏程序实例
CAppGame theAppGame;

// 定义获得简单程序接口实例函数,这个在WinApp.cpp中用到,而且必须定义
// 这是Windows程序调用游戏程序的接口
IAppEntry *AppEntryClass()
{
return &theAppGame;
}

////////////////////////////////////////////////////////////////////////

10.加入下面代码从写简单程序接口的纯虚函数内容[/b]
//////////////////////////////////////////////////////////////////////////

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()函数里做如下修改[/b]
// Windows程序初始化
HWND AppInit( HINSTANCE hInstance, int nShowCmd )
{
// 程序实例类名[/b]
const char *szWindowsClassName = AppEntryClass()->WindowClassName();[/b]
[/b]
static int iWindowWidth = AppEntryClass()->WindowWidth(); // 窗口宽度[/b]
static int iWindowHeight = AppEntryClass()->WindowHeight(); // 窗口高度[/b]

// 注册窗口类
WNDCLASS wc;

wc.style = CS_HREDRAW | CS_VREDRAW;

12.介绍一点小工具。在Tank项目上加入一个Utility目录 加入FormatString.h 和FormatString.cpp 并加入下面代码[/b]

#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]; 这是个唯一的内存地址,其他函数调用改变了他的内容就会出错。也就是说用这个工具组合出来的字符串要立即调用[/b]。

现在我们项目文件如下图:

第三章[/b] 显示图片和基本绘图[/b]

这一章我将介绍如何用WindowsGDI显示图片到程序主窗口上,用GDI显示只是为了简单方便,来完成我们的任务,如果要更高效的方法可以使用 DirectX 3D 或 DirectDraw接口。

1. [/b]首先我们准备好下面这张图片我把它放在运行目录[/b]RunTime里[/b]
取名为[/b]ImageC.bmp,接下来就是如何把这张图片显示出来。[/b]

2.在Tank项目里加入Render目录,并增加文件 GDIGraphicsDevice.cpp GDIGraphicsDevice.h GDISurface.cpp GDISurface.h GDITextRender.cpp GDITextRender.h[/b]

3.打开GDIGraphicsDevice.h 加入下面代码[/b]
#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[/b].打开GDIGraphicsDevice.cpp 加入下面代码[/b]
#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.实现调整窗口大小函数[/b]
// 调整窗口大小 使绘图区域等于 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.实现创建设备函数[/b]
// 创建设备, 让设备操作对象指向 某个窗口
bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )
{
// 保存窗口句柄
m_hWnd = hWnd;
// 调整显示区大小
AdjuestWindowSize( iWidth, iHeight );

return true;
}

7.打开GDISurface.h 输入下面代码[/b]
#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 输入下面代码[/b]
#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. [/b]实现创建位图[/b][/b]
// 创建一个位图
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. [/b]实现从文件读取位图, 方法和创建位图类似,只是原位图从文件用[/b]LoadImage读入。[/b]
// 从文件读取一个位图
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. [/b]实现释放位图[/b][/b]
// 释放位图
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. [/b]实现[/b]4个基本绘图操作[/b]
// 清除表面为 某种颜色
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. [/b]实现位图传送功能[/b][/b]
// 位图传送
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. [/b]打开[/b]GDIGraphicsDevice.h 加入下面代码[/b]
#ifndef __GDIGraphicsDevice_H__
#define __GDIGraphicsDevice_H__

#include <Windows.h>

class[/b] CGDISurface;[/b]

// 绘图设备,这里指最后能够显示在屏幕上的设备,这是虚拟的,
// 我们把它叫做图形设备。
class CGDIGraphicsDevice
{
public:

CGDIGraphicsDevice();
virtual ~CGDIGraphicsDevice();

// 创建设备, 让设备操作对象指向 某个窗口
bool Create( HWND hWnd, int iWidth, int iHeight );
// 释放设备
void Release();
// 更新显示,使主表面内容显示到屏幕上
void UpdateFrame( HDC hdc );

// 获得操作指向的窗口
HWND GetWnd();

// 获得主表面[/b]
CGDISurface *GetMainSurface();[/b]

protected:

// 调整窗口大小
void AdjuestWindowSize( int iWidth, int iHeight );

protected:

// 操作指向的窗口句柄
HWND m_hWnd;

// 住缓冲表面[/b]
CGDISurface *m_pMainSurface;[/b]

};

#endif // __GDIGraphicsDevice_H__

16. [/b]打开[/b]GDIGraphicsDevice.cpp 修改和增加下面代码[/b]
#include "GDIGraphicsDevice.h"
#include[/b] "GDISurface.h"[/b]

CGDIGraphicsDevice::CGDIGraphicsDevice()
{
m_hWnd = NULL;
m_pMainSurface = NULL;[/b]
}

CGDIGraphicsDevice::~CGDIGraphicsDevice()
{

}

// 创建设备, 让设备操作对象指向 某个窗口
bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )
{
// 保存窗口句柄
m_hWnd = hWnd;
// 调整显示区大小
AdjuestWindowSize( iWidth, iHeight );

m_pMainSurface = new CGDISurface();[/b]

return true;
}

// 释放设备
void CGDIGraphicsDevice::Release()
{
delete m_pMainSurface;[/b]
m_pMainSurface = NULL;[/b]
}

17. [/b]增加获取主表面函数[/b][/b]
// 获得主表面
CGDISurface *CGDIGraphicsDevice::GetMainSurface()
{
return m_pMainSurface;
}

18. [/b]增加创建主表面[/b][/b]
// 创建设备, 让设备操作对象指向 某个窗口
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 ) )[/b]
{[/b]
OutputDebugString( FSTR( "GraphicsDevice Create Main Surface Failed" ) );[/b]
return false;[/b]
}[/b]

return true;
}

// 释放设备
void CGDIGraphicsDevice::Release()
{
m_pMainSurface->Release();[/b]
delete m_pMainSurface;
m_pMainSurface = NULL;
}

19. [/b]增加更新显示函数功能[/b][/b]
// 更新显示,使主表面内容显示到屏幕上
void CGDIGraphicsDevice::UpdateFrame( HDC hdc )
{
// 如果主表面没有创建成功就返回[/b]
if( NULL == m_pMainSurface )[/b]
return;[/b]
[/b]
HDC hdcSrc = m_pMainSurface->GetDC();[/b]
int iWidth = m_pMainSurface->GetWidth();[/b]
int iHeight = m_pMainSurface->GetHeight();[/b]
[/b]
// 更新显示到屏幕,将由 WM_PAINT 调用[/b]
BitBlt( hdc, 0, 0, iWidth, iHeight, hdcSrc, 0, 0, SRCCOPY );[/b]
}

20. [/b]为了方便我们在项目[/b]Tank上加入一个全局数据定义文件,大家可以在此定义非常公用或重要的全局变量。并加入两个文件 ShareData.h ShareData.cpp[/b]

21. [/b]打开[/b]ShareData.h 加入下面代码[/b]
#ifndef __ShareData_H__
#define __ShareData_H__

#include "GDIGraphicsDevice.h"
#include "GDISurface.h"
#include "GDITextRender.h"

extern CGDIGraphicsDevice *theGraphicsDevice;

#endif // __ShareData_H__
22. [/b]打开[/b]ShareData.cpp 加入下面代码[/b]
#include "ShareData.h"

CGDIGraphicsDevice *theGraphicsDevice = NULL;

23. [/b]打开[/b]AppGame.cpp里加入下面代码[/b]
#include "AppGame.h"
#include[/b] "ShareData.h"[/b]
[/b]
CGDISurface[/b] theSurface;[/b]

// 定义主游戏程序实例
CAppGame theAppGame;

24. [/b]加入下面代码,创建[/b][/b]
// 初始化程序
bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )
{
m_hInstance = hInstance;
m_hWnd = hWnd;

// 创建图形设备[/b]
theGraphicsDevice = new CGDIGraphicsDevice();[/b]
if( !theGraphicsDevice->Create( m_hWnd, WindowWidth(), WindowHeight() ) )[/b]
{[/b]
OutputDebugString( "Create GraphicsDevice Failed" );[/b]
return false;[/b]
}[/b]
[/b]
// 读取位图[/b]
if( !theSurface.LoadBmp( theGraphicsDevice, "ImageC.bmp" ) )[/b]
{[/b]
OutputDebugString( "Load bitmap Failed" );[/b]
return false;[/b]
}[/b]

return true;
}

// 结束程序
void CAppGame::Terminal()
{
// 释放位图[/b]
theSurface.Release();[/b]
[/b]
// 释放图形设备[/b]
theGraphicsDevice->Release();[/b]
delete theGraphicsDevice;[/b]
}
25. [/b]修改下面代码来显示读入的图片[/b]
// 主处理函数
void CAppGame::Process()
{
Render();[/b]
}

// 主渲染函数
void CAppGame::Render()
{
// 显示位图[/b]
theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_BLOCK );[/b]
}
26. [/b]在消息处理函数里加入更新显示代码[/b][/b]
// 主窗口消息处理函数
LRESULT CAppGame::WndProc( UINT message, WPARAM wParam, LPARAM lParam )
{
HDC hdc;[/b]
PAINTSTRUCT ps;[/b]
[/b]
switch( message )[/b]
{[/b]
case WM_PAINT:[/b]
{[/b]
hdc = BeginPaint( m_hWnd, &ps );[/b]
if( theGraphicsDevice )[/b]
{[/b]
// 调用图形设备更新显示[/b]
theGraphicsDevice->UpdateFrame( hdc );[/b]
}[/b]
EndPaint( m_hWnd, &ps );[/b]
}[/b]
return TRUE;[/b]
case WM_ERASEBKGND:[/b]
return TRUE;[/b]
}[/b]

return FALSE;
}
[/b]
27. [/b]在[/b]CAppGame::Render()里加入更新显示通知[/b]
// 主渲染函数
void CAppGame::Render()
{
// 显示位图
theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_BLOCK );

// 通知Window程序更新显示窗口[/b]
RECT rcWindow;[/b]
GetClientRect( m_hWnd, &rcWindow );[/b]
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );[/b]
}
28. [/b]运行程序可以看到如下效果[/b][/b]

29[/b].在CAppGame::Render() [/b]里加入下面代码[/b]
// 主渲染函数
void CAppGame::Render()
{
// 清除主表面[/b]
theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );[/b]

// 显示位图
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[/b].将CAppGame::Render() [/b]修改为[/b]
void CAppGame::Render()
{
// 清除主表面
theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );

// 显示位图
theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST[/b] );

// 通知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;
}

// 读取位图[/b]
theImageC = new CGDISurface();[/b]
if( !theImageC->LoadBmp( theGraphicsDevice, "ImageC.bmp" ) )[/b]
{[/b]
OutputDebugString( "Load bitmap Failed" );[/b]
return false;[/b]
}[/b]

return true;
}

// 结束程序
void CAppGame::Terminal()
{
// 释放位图
theImageC->Release();[/b]
delete theImageC;[/b]
theImageC = NULL;[/b]

// 释放图形设备
theGraphicsDevice->Release();
delete theGraphicsDevice;
theGraphicsDevice = NULL;
}

// 主处理函数
void CAppGame::Process()
{
Render();
}

// 主渲染函数
void CAppGame::Render()
{
// 清除主表面
theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );

// 显示位图
theImageC->[/b]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 );
}

第四章[/b] 文字显示[/b] [/b]计时器[/b] [/b]刷新率[/b]

在编写程序时有些很重要的信息要显示出来,帮助调试程序,其中显示文字处于非常重要的位置。计时器我们将用到系统函数timeGetTime(),它是用来获得系统从开始到现在所经历的毫秒数,记录一个开始时间,然后不断判断现在的时间和开始时间的间隔,以达到控制某一段代码以一定的频率执行。刷新率是表示在一秒钟之内更新显示了多少次,最后我们把它显示在屏幕上。

1. [/b]打开GDITextRender.h [/b]输入下面代码[/b]
#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. [/b]打开GDITextRender.cpp [/b]输入下面代码[/b]
#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. [/b]实现创建字体功能[/b][/b]
// 创建字体
bool CGDITextRender::Create( PLOGFONT pFont )
{
// 创建 pFont 指定的字体[/b]
m_hFont = CreateFont([/b]
pFont->lfHeight,[/b]
pFont->lfWidth,[/b]
pFont->lfEscapement,[/b]
pFont->lfOrientation, [/b]
pFont->lfWeight,[/b]
pFont->lfItalic,[/b]
pFont->lfUnderline,[/b]
pFont->lfStrikeOut,[/b]
pFont->lfCharSet,[/b]
pFont->lfOutPrecision,[/b]
pFont->lfClipPrecision,[/b]
pFont->lfQuality,[/b]
pFont->lfPitchAndFamily,[/b]
pFont->lfFaceName );[/b]
[/b]
if( m_hFont == NULL )[/b]
{[/b]
OutputDebugString( FSTR( "Create Font Failed: %s %d/n", pFont->lfFaceName, pFont->lfHeight ) );[/b]
return false;[/b]
}[/b]

return true;
}

4[/b].实现释放字体[/b]
// 释放
void CGDITextRender::Release()
{
// 如果字体已经创建[/b]
if( m_hFont )[/b]
{[/b]
// 删除字体句柄[/b]
DeleteObject( m_hFont );[/b]
m_hFont = NULL;[/b]
}[/b]
}
[/b]
5[/b].修改文字输出函数,加入输出文字功能[/b]

#include "GDITextRender.h"
#include[/b] "GDISurface.h"[/b]
#include "FormatString.h"
#include[/b] <windowsx.h>[/b]

// 向表面输出文字
void CGDITextRender::TextOut( CGDISurface *pSurface, int x, int y, const char *szString, COLORREF c, int len )
{
// 如果指定的字符串长度是0[/b]
if( len == 0 )[/b]
{[/b]
// 自动获取字符串长度[/b]
len = (int)strlen( szString );[/b]
}[/b]
[/b]
// 获得表面设备句柄[/b]
HDC hdc = pSurface->GetDC();[/b]
[/b]
// 选择字体[/b]
HFONT hOldFont = SelectFont( hdc, m_hFont );[/b]
[/b]
// 显示文字[/b]
SetBkMode( hdc, TRANSPARENT );[/b]
SetBkColor( hdc, RGB(0,0,0) );[/b]
SetTextColor( hdc, c );[/b]
::TextOut( hdc, x, y, szString, len );[/b]
[/b]
// 恢复字体[/b]
SelectFont( hdc, hOldFont );[/b]
}

6[/b].打开ShareData.h [/b]加入下面代码[/b]
#ifndef __ShareData_H__
#define __ShareData_H__

#include "GDIGraphicsDevice.h"
#include "GDISurface.h"
#include "GDITextRender.h"

extern CGDIGraphicsDevice *theGraphicsDevice;

extern CGDISurface *theImageC;

extern[/b] CGDITextRender *theTextRender;[/b]

#endif // __ShareData_H__

7[/b].打开ShareData.cpp [/b]加入下面代码[/b]

#include "ShareData.h"

CGDIGraphicsDevice *theGraphicsDevice = NULL;

CGDISurface *theImageC = NULL;

CGDITextRender[/b] *theTextRender = NULL;[/b]

8[/b].打开AppGame.cpp [/b]加入 [/b]初始化文字输出功能 [/b]和释放[/b]
// 初始化程序
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;
}

// 初始化文字输出功能[/b]
LOGFONT FontInfo = { 16, 0, 0, 0, [/b]
FW_NORMAL, FALSE, FALSE, FALSE, [/b]
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, [/b]
CLIP_DEFAULT_PRECIS, [/b]
DEFAULT_QUALITY, [/b]
VARIABLE_PITCH, [/b]
"宋体" };[/b]
[/b]
theTextRender = new CGDITextRender();[/b]
if( !theTextRender->Create( &FontInfo ) )[/b]
{[/b]
OutputDebugString( "Create theTextRender Failed/n" );[/b]
return false;[/b]
}[/b]

return true;
}

// 结束程序
void CAppGame::Terminal()
{
// 释放文字输出功能[/b]
theTextRender->Release();[/b]
delete theTextRender;[/b]
theTextRender = NULL;[/b]

// 释放位图
theImageC->Release();
delete theImageC;
theImageC = NULL;

// 释放图形设备
theGraphicsDevice->Release();
delete theGraphicsDevice;
theGraphicsDevice = NULL;
}

9[/b].在CAppGame::Render()[/b]函数里做如下修改来测试文字输出功能[/b]
// 主渲染函数
void CAppGame::Render()
{
CGDISurface *pScreen = theGraphicsDevice->GetMainSurface();[/b]

// 清除主表面
pScreen[/b]->Clear( RGB( 125,0, 23 ) );

// 显示位图
theImageC->Blt( pScreen,[/b] 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );

// 显示文字[/b]
theTextRender->TextOut( pScreen, 0, 0, "测试文字ABC", RGB( 255, 255, 255 ) );[/b]

// 通知Window程序更新显示窗口
RECT rcWindow;
GetClientRect( m_hWnd, &rcWindow );
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );
}
看到如下结果

10[/b].在Tank[/b]项目Utility[/b]目录里加入Fps.cpp Fps.h[/b]

11[/b].打开Fps.h [/b]输入下面代码[/b]
#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[/b].打开Fps.cpp [/b]输入下面代码[/b]
#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[/b] "Fps.h"[/b]

extern CGDIGraphicsDevice *theGraphicsDevice;

extern CGDISurface *theImageC;

extern CGDITextRender *theTextRender;

extern[/b] CFps *theRenderFps;[/b]

#endif // __ShareData_H__

ShareData.cpp :
#include "ShareData.h"

CGDIGraphicsDevice *theGraphicsDevice = NULL;

CGDISurface *theImageC = NULL;

CGDITextRender *theTextRender = NULL;

CFps[/b] *theRenderFps = NULL;[/b]

14[/b].在AppGame.cpp [/b]中 CAppGame::Initialize() [/b]和 CAppGame::Terminal()[/b]中加入下面代码[/b]

theTextRender = new CGDITextRender();
if( !theTextRender->Create( &FontInfo ) )
{
OutputDebugString( "Create theTextRender Failed/n" );
return false;
}

// 创建渲染刷新率统计器[/b]
theRenderFps = new CFps();[/b]

return true;
}

// 结束程序
void CAppGame::Terminal()
{
// 释放渲染刷新率统计器[/b]
delete theRenderFps;[/b]

// 释放文字输出功能
theTextRender->Release();

15[/b].在 CAppGame::Render() [/b]函数里加入 [/b]刷新率统计,并显示在屏幕上,[/b]并在[/b]
AppGame.cpp [/b]最上加入 #include “FormatString.h”[/b]
// 主渲染函数
void CAppGame::Render()
{
CGDISurface *pScreen = theGraphicsDevice->GetMainSurface();

// 清除主表面
pScreen->Clear( RGB( 127,127, 127 ) );

// 显示位图[/b]
//theImageC->Blt( pScreen, 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );[/b]

// 显示文字[/b]
theTextRender->TextOut( pScreen, 0, 0, FSTR( "FPS = %2d", theRenderFps->GetFps() ), RGB( 255, 255, 255 ) );[/b]

// 通知Window程序更新显示窗口
RECT rcWindow;
GetClientRect( m_hWnd, &rcWindow );
RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );
[/b]
theRenderFps->Count();[/b]
}
运行程序看到如下效果

16[/b].为了给CPU[/b]更多的时间,我们不需要程序运行很快,我们如何限制渲染速度,打开AppGame.h [/b]加入下面代码, [/b]再运行可以看到 Fps[/b]已经是30[/b]了[/b]

protected:

// 保存主程序句柄
HINSTANCE m_hInstance;
// 保存主窗口句柄
HWND m_hWnd;

// 最后一次渲染的时间[/b]
DWORD m_dwRenderLastTime;[/b]
};
17.在主渲染函数中加入
// 主渲染函数
void CAppGame::Render()
{
if( timeGetTime() - m_dwRenderLastTime < 33 )[/b]
{[/b]
return;[/b]
}[/b]
m_dwRenderLastTime = timeGetTime();[/b]
[/b]
第五章[/b] 精灵动画[/b]

在游戏中跑来跑去的小人,小狗,Tank 等叫做精灵,每个精灵都会有一张图画,如果会做动作的会有几张图画交替显示,从而看起来像是在跑。如图 28x28大小的一个图块是精灵动画的一张图片,后面的那个是第二张,我们把这每一张图片叫做帧, 我们说Tank的上方向行走是2帧动画。

在我们的项目中,我们定义帧的方法是 记录一个图快的位置和大小
如图 X,Y,W,H 来表示一个帧。

1[/b].在TANK[/b]项目中加入一个目录Animation[/b],并增加6[/b]个文件 Frame.h [/b]
Frame.cpp Animation.h Animation.cpp SpriteManager.h SpriteManager.cpp[/b]

2[/b].在Frame.h [/b]和Frame.cpp [/b]增加帧类代码[/b]

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[/b].打开Animation.h [/b]输入下面代码[/b]
#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. [/b]打开Animation.cpp [/b]输入下面代码[/b]
#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. [/b]打开SpriteManager.h [/b]输入下面代码[/b]
#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[/b].打开SpriteManager.cpp [/b]输入下面代码[/b]
#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[/b].在CSpriteManager::Create()[/b]里加入第一个动画,[/b]动画也可能只有1[/b]帧。下面是加入前两个帧给0[/b]号动画。同样的方法,可以在此函数里加入所有动画。[/b]

// 创建动画数据集
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[/b].在ShareData.h ShareData.cpp [/b]里分别加入下面代码[/b]
[/b]
ShareData.h:[/b]
#ifndef __ShareData_H__
#define __ShareData_H__

#include "GDIGraphicsDevice.h"
#include "GDISurface.h"
#include "GDITextRender.h"
#include "Fps.h"
#include[/b] "SpriteManager.h"[/b]

extern CGDIGraphicsDevice *theGraphicsDevice;

extern CGDISurface *theImageC;

extern CGDITextRender *theTextRender;

extern CFps *theRenderFps;

extern[/b] CSpriteManager *theSpriteManager;[/b]

#endif // __ShareData_H__

ShareData.cpp:

#include "ShareData.h"

CGDIGraphicsDevice *theGraphicsDevice = NULL;

CGDISurface *theImageC = NULL;

CGDITextRender *theTextRender = NULL;

CFps *theRenderFps = NULL;

CSpriteManager[/b] *theSpriteManager = NULL;[/b]
9.在AppGame.cpp里加入下面初始化和释放动画管理器代码[/b]

theTextRender = new CGDITextRender();
if( !theTextRender->Create( &FontInfo ) )
{
OutputDebugString( "Create theTextRender Failed/n" );
return false;
}

// 创建渲染刷新率统计器
theRenderFps = new CFps();

// 创建动画管理器[/b]
theSpriteManager = new CSpriteManager();[/b]
if( !theSpriteManager->Create() )[/b]
{[/b]
OutputDebugString( "Create Sprite Manager Failed/n" );[/b]
return false;[/b]
}[/b]

return true;
}

// 结束程序
void CAppGame::Terminal()
{
// 释放动画管理器[/b]
theSpriteManager->Release();[/b]
delete theSpriteManager;[/b]
theSpriteManager = NULL;[/b]

10[/b].在 CAppGame::Render()[/b]里作如下修改,显示一个动画的一帧。[/b]
// 清除主表面
pScreen->Clear( RGB( 127,127, 127 ) );
[/b]
// 显示位图[/b]
//theImageC->Blt( pScreen, 30, 30, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );[/b]
[/b]
// 显示一个动画的一帧[/b]
CAnimation *pAni = theSpriteManager->GetAnimation( 0 );[/b]
CFrame *pF = pAni->GetFrame( 0 );[/b]
pF->Render( pScreen, 100, 100, CGDISurface::BLT_ALPHATEST );[/b]

// 显示文字
theTextRender->TextOut( pScreen, 0, 0, FSTR( "FPS = %2d", theRenderFps->GetFps() ), RGB( 255, 255, 255 ) );

大家可以看到显示效果是,一个Tank显示在屏幕上。

10[/b].接下来的任务就是就是把这些动画编排好,以便以后使用。根据下面的图我们可以看出,这组图是很有规律,这是每一个TANK[/b]的4[/b]个方向上的动画,我写了一个循环按照规则创建动画在CSpriteManager.cpp Create()[/b]函数里加入初始化代码[/b]

// 创建动画数据集
bool CSpriteManager::Create()
{
CAnimation *pAni;
CFrame f;

// 所有TANK图片都是28x28[/b]
f.m_iW = 28;[/b]
f.m_iH = 28;[/b]
[/b]
// 两组动画,上面是玩家TANK,下面是地方TANK。[/b]
for( int k=0; k<2; k++ )[/b]
{[/b]
// 每一大组内有8种TANK的动画[/b]
for( int l=0; l<8; l++ )[/b]
{[/b]
// 每种Tank有4个方向上的动画[/b]
for( int j=0; j<4; j++ )[/b]
{[/b]
pAni = new CAnimation();[/b]
[/b]
// 每个方向上的动画有2帧[/b]
for( int i=0; i<2; i++ )[/b]
{[/b]
f.m_iX = l*28*2 + i*28;[/b]
f.m_iY = k*28*4 + j*28;[/b]
pAni->Push( f );[/b]
}[/b]
[/b]
m_vectorAnimation.push_back( *pAni );[/b]
delete pAni;[/b]
}[/b]
}[/b]
}[/b]
[/b]
// 动画定位公式是: 动画ID = 坦克型号 * 4 + 方向[/b]

return true;
}
11[/b].在CAppGame::Render()[/b]里加入并修改代码,运行程序可以看到TANK[/b]行走动画,每2[/b]秒钟随机变化一个TANK[/b]
// 显示位图
//theImageC->Blt( pScreen, 30, 30, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );

// 当前帧[/b]
static int iCurFrame = 0;[/b]
// 动画计时开始时间[/b]
static DWORD dwAniStartTime = timeGetTime();[/b]
[/b]
// 当前动画ID[/b]
static int iCurAni = 0;[/b]
// 最后一次改变动画的时间[/b]
static DWORD dwAniChangeLastTime = timeGetTime();[/b]
[/b]
if( timeGetTime() - dwAniChangeLastTime > 2000 )[/b]
{[/b]
iCurAni = rand()%64;[/b]
dwAniChangeLastTime = timeGetTime();[/b]
}[/b]
if( timeGetTime() - dwAniStartTime > 33 )[/b]
{[/b]
iCurFrame++;[/b]
if( iCurFrame >= 2 )[/b]
iCurFrame = 0;[/b]
dwAniStartTime = timeGetTime();[/b]
}[/b]

// 显示一个动画的一帧
CAnimation *pAni = theSpriteManager->GetAnimation( iCurAni[/b] );
CFrame *pF = pAni->GetFrame( iCurFrame[/b] );
pF->Render( pScreen, 100, 100, CGDISurface::BLT_ALPHATEST );

第六章[/b] 场景[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: