Win32游戏编程

来源:互联网 发布:js获取今天星期几 编辑:程序博客网 时间:2024/05/21 19:35

   

 

Visual C++作为一个功能非常强大的可视化应用程序开发工具,是计算机界公认的最优秀的应用开发工具之一。本文介绍的就是在Visual C++.NET下开发Win32游戏软件的流程。通过游戏编程熟练掌握了Visual C++这个应用开发工具。

本文共四章:

第一章 Win32游戏编程

1.1 简单的Win32程序

1.2 WinMain()函数

1.3 注册窗口类

1.4 创建窗口

1.5 Windows消息

1.5.1 消息循环

1.5.2 消息处理函数

1.5.3 结束消息循环

第二章 DirectX编程基础

2.1 DirectX组成部分

2.2 简单的DirectX程序

第三章 游戏开发环境

第四章 游戏开发过程

4.1 中国象棋功能介绍

4.2 游戏初始化

4.3 图形处理

4.3.1 建立窗口DC

4.3.2 棋子贴图

4.4 行棋规则

4.5 音乐处理

4.6 操作处理

4.7 中国象棋游戏制作说明

附录:

参考文献

致谢

 

 

作品:

 

1.中国象棋V1.0版 制作:赵飞宇 徐兴国 主程序历时两天完工。

       优点:操作简单,画面优美,人性化。

       缺点:无人工智能,不能联网对战。

    2. 五子棋游戏V1.0版 制作:赵飞宇 主程序历时三天完工。

       优点:画面优美直观,有极强的人工智能。

       缺点:功能简单,背景图片太大。


* Win32编程基础

尽管Windows应用程序千变万化,令人眼花缭乱,但,消息机制和窗口过程却始终它们的基础,掌握了这两项技术,也就相当于把握住了问题的关键DirectX编程也是建立在这个基础之上的,所以,在你可以熟练的进行简单的Win32编程之后DirectX编程也就触手可得。

1.1 简单的WIN32程序

在以前的C语言编程中,一个最简单的程序可以只有两行。

void main(void)

{ printf "Hello World!"; }

而要实现同样功能的Windows程序却最少也要写几十行,这并不是说明Windows应用程序效率低下,难于掌握,只是说明程序在Windows环境下有更丰富的内涵。

    Windows程序的效率其实不低,在所有的Windows应用程序中,都有一个程序初始化的过程,这得用上几十条语句,这段初始化的代码对于任何Windows应用程序而言,都是大同小异的。下面以一个实现最简单功能的程序EasyWin为例,说明Windows程序的基本框架。

//*******************************************************************

// 工程:easywin

// 文件:easywin.cpp

// 内容:一个基本的Win32程序

//*******************************************************************

#include <windows.h>

#include <windowsx.h>

//函数声明

BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );

LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam,

LPARAM lParam );

//*******************************************************************

//函数:WinMain()

//功能:Win32应用程序入口函数。创建主窗口,处理消息循环

//*******************************************************************

int PASCAL WinMain( HINSTANCE hInstance, //当前实例句柄

HINSTANCE hPrevInstance, //前一个实例句柄

LPSTR lpCmdLine, //命令行字符

int nCmdShow) //窗口显示方式

{

MSG msg;

//创建主窗口

if ( !InitWindow( hInstance, nCmdShow ) )

return FALSE;

//进入消息循环:

//从该应用程序的消息队列中检取消息,送到消息处理过程,

//当检取到WM_QUIT消息时,退出消息循环。

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

//程序结束

return msg.wParam;

}

//******************************************************************

//函数:InitWindow()

//功能:创建窗口。

//******************************************************************

static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )

{

HWND hwnd; //窗口句柄

WNDCLASS wc; //窗口类结构

//填充窗口类结构

wc.style = CS_VREDRAW | CS_HREDRAW;

wc.lpfnWndProc = (WNDPROC)WinProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );

wc.hCursor = LoadCursor( NULL, IDC_ARROW );

wc.hbrBackground = GetStockObject(WHITE_BRUSH);

wc.lpszMenuName = NULL;

wc.lpszClassName = "EasyWin";

RegisterClass( &wc );          //注册窗口类

hwnd = CreateWindow(        //创建主窗口

"EasyWin", //窗口类名称

"一个基本的Win32程序", //窗口标题

WS_OVERLAPPEDWINDOW, //窗口风格,定义为普通型

100, //窗口位置的x坐标

100, //窗口位置的y坐标

400, //窗口的宽度

300, //窗口的高度

NULL, //父窗口句柄

NULL, //菜单句柄

hInstance, //应用程序实例句柄

NULL ); //窗口创建数据指针

if( !hwnd ) return FALSE;

//显示并更新窗口

ShowWindow( hwnd, nCmdShow );

UpdateWindow( hwnd );

return TRUE;

}

//******************************************************************

//函数:WinProc()

//功能:处理主窗口消息

//******************************************************************

LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam,

LPARAM lParam )

{

switch( message )

{

case WM_KEYDOWN://击键消息

switch( wParam )

{

case VK_ESCAPE:

MessageBox(hWnd,"ESC键按下了!","Keyboard",MB_OK);

break;

}

break;

case WM_RBUTTONDOWN://鼠标消息

{

MessageBox(hWnd,"鼠标右键按下了!","Mouse",MB_OK);

break;

}

case WM_PAINT://窗口重画消息

{

char hello[]="你好,我是EasyWin !";

HDC hdc;

PAINTSTRUCT ps;

hdc=BeginPaint( hWnd,&ps ); //取得设备环境句柄

SetTextColor(hdc, RGB(0,0,255)); //设置文字颜色

TextOut( hdc, 20, 10, hello, strlen(hello) );//输出文字

EndPaint( hWnd, &ps ); //释放资源

break;

}

case WM_DESTROY://退出消息

PostQuitMessage( 0 );//调用退出函数

break;

}

//调用缺省消息处理过程

return DefWindowProc(hWnd, message, wParam, lParam);

}

其实,这个程序可以看成是所有Win32应用程序的框架,在以后所有的程序中,你会发现它们都是在这个程序的基础之上再添加代码。

1.2 WinMain()函数

WinMain()函数是应用程序开始执行时的入口点,通常也是应用程序结束任务退出时的出口点。它与DOS程序的main()函数起同样的作用,有一点不同的是,WinMain()函数必须带有四个参数,它们是系统传递给它的。WinMain()函数的原型如下:

int PASCAL WinMain( HINSTANCE hInstance, //当前实例句柄

HINSTANCE hPrevInstance, //前一个实例句柄

LPSTR lpCmdLine, //命令行字符

int nCmdShow) //窗口显示方式

第一个参数hInstance,是标识该应用程序当前的实例的句柄。它是HINSTANCE类型,HINSTANCE是Handle of Instance的缩写,表示实例的句柄。hInstance是一个很关键的数据,它唯一的代表该应用程序,在后面初始化程序主窗口的过程中需要用到这个参数。这里有两个概念,一个是实例,一个是句柄。实例代表的是应用程序执行的整个过程和方法,一个应用程序如果没有被执行,只是存在于磁盘上,那么就说它是没有被实例化的;只要一执行,则说该程序的一个实例在运行。句柄,顾名思义,指的是一个对象的把柄。在Windows中,有各种各样的句柄,它们都是32位的指针变量,用来指向该对象所占据的内存区。句柄的使用,可以极大的方便Windows管理其内存中的各种对象。

第二个参数是hPrevInstance,它是用来标识该应用程序的前一个实例句柄。对于基于Win32的应用程序来说,这个参数总是NULL。这是因为在Win95操作系统中,应用程序的每个实例都有各自独立的地址空间,即使同一个应用程序被执行了两次,在内存中也会为它们的每一个实例分配新的内存空间,所以一个应用程序被执行后,不会有前一个实例存在的可能。也就是说,hPrevInstance这个参数是完全没有必要的,只是为了提供与16位Windows的应用程序形式上的兼容性,才保留了这个参数。在以前的16位Windows环境下(如Windows3.2),hPrevInstance用来标识与hInstance相关的应用程序的前一个句柄。

第三个参数是lpCmdLine,是指向应用程序命令行参数字符串的指针。如在Win95的“开始”菜单中单击“运行”,输入“easywin hello”,则此参数指向的字符串为“hello”。

最后一个参数是nCmdShow,是一个用来指定窗口显示方式的整数。这个整数值可以是SW_SHOW、SW_HIDE、SW_SHOWMAXIMIZED、SW_SHOWMINIMIZED等。

1.3 注册窗口类

一个应用程序可以有许多窗口,但只有一个是主窗口,它是与该应用程序的实例句柄唯一关联的。上面的例程中,创建主窗口的函数是InitWindow() 通常要对填充一个窗口类结构WNDCLASS,然后调用RegisterClass()对该窗口类进行注册。每个窗口都有一些基本的属性,如窗口边框、窗口标题栏文字、窗口大小和位置、鼠标、背景色、处理窗口消息函数的名称等等。注册的过程也就是将这些属性告诉系统,然后再调用CreateWindow()函数创建出窗口。这也就象你去裁缝店订做一件衣服,先要告诉店老板你的身材尺寸、布料颜色、以及你想要的款式,然后他才能为你做出一件让你满意的衣服。

VC的帮助中,可以看到WNDCLASS结构是这样定义的:typedef struct _WNDCLASS {UINT style; //窗口的风格*WNDPROC lpfnWndProc; //指定窗口的消息处理函数的远指针*int cbClsExtra; //指定分配给窗口类结构之后的额外字节数*int cbWndExtra; //指定分配给窗口实例之后的额外字节数HANDLE hInstance; //指定窗口过程所对应的实例句柄*HICON hIcon; //指定窗口的图标HCURSOR hCursor; //指定窗口的鼠标HBRUSH hbrBackground; //指定窗口的背景画刷LPCTSTR lpszMenuName; //窗口的菜单资源名称LPCTSTR lpszClassName; //该窗口类的名称*} WNDCLASS;

Win95WinNT的具有新界面特性的系统中,为了支持新的窗口界面特性,还有一种扩展的窗口类型WNDCLASSEX,它的定义如下:typedef struct _WNDCLASSEX {UINT cbSize; //指定WNDCLASSEX结构的大小UINT style; WNDPROC lpfnWndProc; int cbClsExtra;int cbWndExtra;HANDLE hInstance; HICON hIcon; HCURSOR hCursor;HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName;HICON hIconSm; //窗口的小图标} WNDCLASSEX;

WNDCLASSWNDCLASSEX这两个结构基本上是一致的,只是WNDCLASSEX结构中多了cbSizehIconSm这两个成员。WNDCLASS结构的各成员中,其注释后打了星号的表示该项应特别注意。

WNDCLASS结构的第一个成员style表示窗口类的风格,它往往是由一些基本的风格通过位的操作(操作符位“|”)组合而成。下表列出了一些常用的基本窗口风格:

风格 含义 CS_HREDRAW 如果窗口客户区宽度发生改变,重绘整个窗口 CS_VREDRAW 如果窗口客户区高度发生改变,重绘整个窗口 CS_DBLCLKS 能感受用户在窗口中的双击消息 CS_NOCLOSE 禁用系统菜单中的关闭命令 CS_OWNDC 为该窗口类的各窗口分配各自独立的设备环境 CS_CLASSDC 为该窗口类的各窗口分配一个共享的设备环境 CS_PARENTDC 指定子窗口继承其父窗口的设备环境 CS_SAVEBITS 把被窗口遮掩的屏幕图象部分作为位图保存起来。当该窗口被移动时,Windows使用被保存的位图来重建屏幕图象

EasyWin应用程序中,是按如下方式对WNDCLASS结构进行填充和注册的:wc.style = CS_VREDRAW | CS_HREDRAW;wc.lpfnWndProc = (WNDPROC)WinProc;wc.cbClsExtra = 0;wc.cbWndExtra = 0;wc.hInstance = hInstance;wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );wc.hCursor = LoadCursor( NULL, IDC_ARROW );wc.hbrBackground = GetStockObject(WHITE_BRUSH);wc.lpszMenuName = NULL;wc.lpszClassName = "EasyWin";

可以看到,wc.style被设为CS_VREDRAW | CS_HREDRAW,表示只要窗口的高度或宽度发生变化,都会重画整个窗口。

第二个成员lpfnWndProc的值为(WNDPROC)WinProc。表明该窗口类的消息处理函数是WinProc()函数。这里,要指定窗口的消息处理函数的远指针,输入消息处理函数的函数名称即可,必要时应该进行强制类型转换,将其转换成WNDPROC型。

接下来的cbClsExtrawc.cbWndExtra在大多数情况下都会设为0

然后的hInstance成员,给它的值是由WinMain()传来的应用程序的实例句柄,表明该窗口与该实例是相关联的。事实上,只要是注册窗口类,该成员的值始终是该程序的实例句柄,你应该象背书一样记住它。

下面的hIcon,是让你给这个窗口指定一个图标,调用 LoadIcon( hInstance, IDI_APPLICATION ),可以调用系统内部预先定义好的标志符为IDC_APPLICATION的图标作为该窗口的图标。

同样,调用LoadCursor( NULL, IDC_ARROW )为该窗口调用系统内部预先定义好的箭头型鼠标。

hbrBackground成员用来定义窗口的背景画刷颜色,也就是该窗口的背景色。调用GetStockObject(WHITE_BRUSH)可以获得系统内部预先定义好的白色画刷作为窗口的背景色。

上面的LoadIcon()LoadCursor()GetStockObject()都是WindowsAPI函数,它们的用法可以参看VC的帮助,这里就不多介绍了。

lpszMenuName成员的值我们给它NULL,表示该窗口将没有菜单。如果你想让你的窗口拥有菜单,就把lpszMenuName成员赋值为标志菜单资源的字符串。

WNDCLASS结构的最后一个成员lpszClassName是让你给这个窗口类起一个唯一的名称,因为Windows操作系统中有许许多多的窗口类,必须用一个独一无二的名称来代表它们。通常,你可以用你的程序名来命名这个窗口类的名称。这个名称将在创建窗口的CreateWindow()函数中用到。

填充完毕后,对于WNDCLASS结构,调用RegisterClass()函数进行注册;对于WNDCLASSEX结构,调用RegisterClassEx()函数进行注册,它们的原型分别如下:ATOM RegisterClass( CONST WNDCLASS *lpWndClass ); ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx );

该函数如调用成功,则返回一个非0值,表明系统中已经注册了一个名为EasyWin的窗口类。如果失败,则返回0

 

1.4 创建窗口

当窗口类注册完毕之后,并不会有窗口显示出来,因为注册的过程仅仅是为创建窗口所做的准备工作。实际创建一个窗口的是通过调用CreateWindow()函数完成的。窗口类中已经预先定义了窗口的一般属性,而CreateWindow()中的参数可以进一步指定一个窗口的更具体的属性,在EasyWin程序中,是如下调用CreateWindow()函数来创建窗口的:

hwnd = CreateWindow(

"EasyWin", //创建窗口所用的窗口类的名称*

"一个基本的Win32程序", //窗口标题

WS_OVERLAPPEDWINDOW, //窗口风格,定义为普通型*

100, //窗口位置的x坐标

100, //窗口位置的y坐标

400, //窗口的宽度

300, //窗口的高度

NULL, //父窗口句柄

NULL, //菜单句柄

hInstance, //应用程序实例句柄*

NULL ); //一般都为NULL

CreateWindow()函数的参数的含义在上面的注释中已有介绍,注释后打了星号标记的参数应该着重注意,其它的参数都很简单,不多做介绍,可参看VC的帮助。

第一个参数是创建该窗口所使用的窗口类的名称,注意这个名称应与前面所注册的窗口类的名称一致。

第三个参数为创建的窗口的风格,下表列出了常用的窗口风格:

风格 含义

WS_OVERLAPPEDWINDOW 创建一个层叠式窗口,有边框、标题栏、系统菜单、最大最小化按钮,是以下几种风格的集合:WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX

WS_POPUPWINDOW 创建一个弹出式窗口,是以下几种风格的集合: WS_BORDER,WS_POPUP,WS_SYSMENU。WS_CAPTION与WS_POPUPWINDOW风格必须一起使用才能使窗口菜单可见

WS_OVERLAPPED 创建一个层叠式窗口,它有标题栏和边框,与WS_TILED风格一样

WS_POPUP 该窗口为弹出式窗口,不能与WS_CHILD同时使用

WS_BORDER 窗口有单线边框

WS_CAPTION 窗口有标题栏

WS_CHILD 该窗口为子窗口,不能与WS_POPUP同时使用

WS_DISABLED 该窗口为无效,即对用户操作不产生任何反应

WS_HSCROLL 窗口有水平滚动条

WS_ICONIC 窗口初始化为最小化

WS_MAXIMIZE 窗口初始化为最大化

WS_MAXIMIZEBOX 窗口有最大化按钮

WS_MINIMIZE 与WS_MAXIMIZE一样

WS_MINIMIZEBOX 窗口有最小化按钮

WS_SIZEBOX 边框可进行大小控制的窗口

WS_SYSMENU 创建一个有系统菜单的窗口,必须与WS_CAPTION风格同时使用

WS_THICKFRAME 创建一个大小可控制的窗口,与WS_SIZEBOX 风格一样.

WS_TILED 创建一个层叠式窗口,有标题栏

WS_VISIBLE 窗口为可见

WS_VSCROLL 窗口有垂直滚动条

程序中使用了WS_OVERLAPPEDWINDOW标志,它是创建一个普通窗口常用的标志。而在DirectX编程中,我们常用的是WS_POPUP,用这个标志创建的窗口没有标题栏和系统菜单,如果设定窗口为最大化,客户区可以占满整个屏幕,以满足DirectX编程的需要。

CreateWindow()函数后面的参数中,仍用到了该应用程序的实例句柄hInstance。

如果窗口创建成功,返回值是新窗口的句柄,否则返回NULL。

显示和更新窗口

窗口创建后,并不会在屏幕上显示出来,要真正把窗口显示在屏幕上,还得使用ShowWindow()函数,其原型如下:

BOOL ShowWindow( HWND hWnd, int nCmdShow );

参数hWnd指定要显示得窗口的句柄,nCmdShow表示窗口的显示方式,这里指定为从WinMain()函数的nCmdShow所传递而来的值。

由于ShowWindow()函数的执行优先级不高,所以当系统正忙着执行其它的任务时,窗口不会立即显示出来,此时,调用UpdateWindow()函数以可以立即显示窗口。其函数原型如下:

BOOL UpdateWindow( HWND hWnd );

1.5 Windows消息

1.5.1 消息循环

在Win32编程中,消息循环是相当重要的一个概念,看似很难,但是使用起来却是非常简单。在WinMain()函数中,调用InitWindow()函数成功的创建了应用程序主窗口之后,就要启动消息循环,其代码如下:

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

Windows应用程序可以接收以各种形式输入的信息,这包括键盘、鼠标动作 、记时器产生的消息,也可以是其它应用程序发来的消息等等。Windows系统自动监控所有的输入设备,并将其消息放入该应用程序的消息队列中。

GetMessage()函数则是用来从应用程序的消息队列中按照先进先出的原则将这些消息一个个的取出来,放进一个MSG结构中去。GetMessage()函数原型如下:

BOOL GetMessage(

LPMSG lpMsg, //指向一个MSG结构的指针,用来保存消息

HWND hWnd, //指定哪个窗口的消息将被获取

UINT wMsgFilterMin, //指定获取的主消息值的最小值

UINT wMsgFilterMax //指定获取的主消息值的最大值

);

GetMessage()将获取的消息复制到一个MSG结构中。如果队列中没有任何消息,GetMessage()函数将一直空闲直到队列中又有消息时再返回。如果队列中已有消息,它将取出一个后返回。MSG结构包含了一条Windows消息的完整信息,其定义如下:

typedef struct tagMSG {

HWND hwnd; //接收消息的窗口句柄

UINT message; //主消息值

WPARAM wParam; //副消息值,其具体含义依赖于主消息值

LPARAM lParam; //副消息值,其具体含义依赖于主消息值

DWORD time; //消息被投递的时间

POINT pt; //鼠标的位置

} MSG;

该结构中的主消息表明了消息的类型,例如是键盘消息还是鼠标消息等,副消息的含义则依赖于主消息值,例如:如果主消息是键盘消息,那么副消息中则存储了是键盘的哪个具体键的信息。

GetMessage()函数还可以过滤消息,它的第二个参数是用来指定从哪个窗口的消息队列中获取消息,其它窗口的消息将被过滤掉。如果该参数为NULL,则GetMessage()从该应用程序线程的所有窗口的消息队列中获取消息。

第三个和第四个参数是用来过滤MSG结构中主消息值的,主消息值在wMsgFilterMin和wMsgFilterMax之外的消息将被过滤掉。如果这两个参数为0,则表示接收所有消息。

当且仅当GetMessage()函数在获取到WM_QUIT消息后,将返回0值,于是程序退出消息循环。

TranslateMessage()函数的作用是把虚拟键消息转换到字符消息,以满足键盘输入的需要。DispatchMessage()函数所完成的工作是把当前的消息发送到对应的窗口过程中去。

开启消息循环其实是很简单的一个步骤,几乎所有的程序都是按照EasyWin的这个方法。你完全不必去深究这些函数的作用,只是简单的照抄就可以了。

1.5.2 消息处理函数

消息处理函数又叫窗口过程,在这个函数中,不同的消息将用switch语句分配到不同的处理程序中去。Windows的消息处理函数都有一个确定的样式,即这种函数的参数个数和类型以及其返回值的类型都有明确的规定。在VC的说明书中,消息处理函数的原型是这样定义的:

LRESULT CALLBACK WindowProc(

HWND hwnd, //接收消息窗口的句柄

UINT uMsg, //主消息值

WPARAM wParam, //副消息值

LPARAM lParam //副消息值

);

如果你的程序中还有其它的消息处理函数,也都必须按照上面的这个样式来定义,但函数名称可以随便取。EasyWin中的WinProc()函数就是这样一个典型的消息处理函数。

消息处理函数的四个参数是由GetMessage()函数从消息队列中获得MSG结构,然后分解后得到的。第二个参数uMsg和MSG结构中的message值是一致的,代表了主消息值。程序中用switch语句来将不同类型的消息分配到不同的处理程序中去。

WinProc()函数明确的处理了4个消息,分别是WM_KEYDOWN(击键消息)、WM_RBUTTONDOWN(鼠标右键按下消息)、WM_PAINT(窗口重画消息)、WM_DESTROY(销毁窗口消息)。

值得注意的是,应用程序发送到窗口的消息远远不止以上这几条,象WM_SIZE、WM_MINIMIZE、WM_CREATE、WM_MOVE等这样频频使用的消息就有几十条。为了减轻编程的负担,Windows的API提供了DefWindowProc()函数来处理这些最常用的消息,调用了这个函数后,这些消息将按照系统默认的方式得到处理。

因此,在switch_case语句中,只须明确的处理那些有必要进行特别响应的消息,把其余的消息交给DefWindowProc()函数来处理,是一种明智的选择,也是你必须做的一件事。

1.5.3 结束消息循环

当用户按Alt+F4或单击窗口右上角的退出按钮,系统就向应用程序发送一条WM_DESTROY的消息。在处理此消息时,调用了PostQuitMessage()函数,该函数会给窗口的消息队列中发送一条WM_QUIT的消息。在消息循环中,GetMessage()函数一旦检索到这条消息,就会返回FALSE,从而结束消息循环,随后,程序也结束。

1.6 小结

本章介绍的是Win32编程的基础知识,在进行DirectX编程之前,掌握它们是十分必要的。

* DirectX编程基础

DirectXwindows环境下的多媒体开发工具.DirectX 编程是现在图形编程特别是游戏编程的热点.

2.1 DirectX组成部分

    DirectX由以下几个部分组成:

1DirectDraw:通过直接访问显示内存和软硬件加速技术,实现快速直接存取。    2DirectSound:提供软硬件声音混合和录音再生功能。    3DirectPlay :提供多人游戏的交互功能,让您轻松实现网上互连。    4Direct3D:交互式的三维图形技术    5DirectInput:使你的程序能够控制输入设备如鼠标键盘,和游戏杆等。    6DirectSetup:完成DirectX驱动程序安装    7AutoPlay:只要您把光碟一放入光驱它便会自动运行。

2.2 简单的DirectX程序

    下面介绍一下最简单的DirectX程序.

1、在使用 DirextDraw之前,首先需要创建一个对象DirectDraw的实体,该对象实体代表微机的显示适配器。然后,才能使用接口所提供的方法来操作该对象实体,使之完成有关命令和任务。    所以程序的开头先声明了一个DirectDraw对象:

LPDIRECTDRAW    lpDD;    然后如下创建该对象:HRESULT  ddrval;ddrval = DirectDrawCreate( NULL, &lpDD, NULL );

 if( ddrval == DD_OK )  {  // DirectDraw 对象lpDD创建成功 } else {  // DirectDraw对象不能被创建 }

2、接下来设置协作层的运行方式。即DirectDraw程序的运行方式,指整屏模式、窗口模式、MODEX模式等。HRESULT  ddrval;ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ); if( ddrval == DD_OK ){  // 全屏独占方式设置成功  } else {  // 设置失败  }     3、设置显示模式,即屏幕分辨率和色彩数。HRESULT  ddrval;ddrval = lpDD->SetDisplayMode( 640, 480, 8 ); if( ddrval == DD_OK ) { // 成功的把屏幕设置成了640X480X256 } else {  // 显示模式不能改变 }

    4、创建表面。我们可以把表面看成是一块内存缓冲区,所有的操作都是对缓冲区进行的,操作完成后只须把整个缓冲区翻转成主表面(显存)即完成了快速写屏。利用这种技术,就可以实现平滑无闪烁的动画效果,下面讲解如何创建表面。    创建可切换式表面(Surface)的第一步是:在DDSURFACEDESC结构中设定表面(Surface)的各项参数,请看下面的程序:

// Create the primary surface with 1 back buffer.ddsd.dwSize = sizeof( ddsd );ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |DDSCAPS_FLIP | DDSCAPS_COMPLEX;ddsd,dwBackBufferCount = 1;

在以上几行中,DDSURFACEDESC结构的大小被赋给dwSize成员。这样做可防止你在调用DirectDraw的方法时返回一个无效值,且便于今后DDSURFACEDESC结构的扩展。

成员dwFlags用于标明DDSURFACEDESC结构中哪些区域填入的信息有效,哪些区域的信息无效。程序中,我们用dwFlags表明了你要使用结构DDSCAPS(DDSC_CAPS),以及你要创建一个后台缓冲区(DDSD_BACKBUFFERCOUNT)。例程中成员dwCaps包含了一些用在DDSCAPS结构中的标志。这样一来,成员dwCaps就定义了一个主表面(Surface)(DDSCAPS_PRIMARYSURFACE),一个弹出式表面(Surface)(DDS-CPAS_PRIMARYSURFACE),和一个复表面(Surface)(DDSCAPS_COMPLEX)。所谓复表面(Surface)是指,该表面(Surface)是由若干子表面(Surface)组成的。最后,上面的例程定义了一个后台缓冲区。这个后台缓冲区是我们真正进行操作的内存区。操作完成后,再把它们从后台缓冲区弹到主表面(Surface)上就行了。在这里,后台缓冲区的个数被设为1。但你可以设置任意多的后台缓冲区,只要你的内存允许的话。

填完了DDSURFACEDESC结构,就可以使用该结构和lpDD创建表面的指针了: ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ); if( ddrval == DD_OK ) {  // lpDDSPrimary 指向主表面 } else{      return FALSE;   }    如果调用成功,则IDirectDraw::CreateSurface函数返回指向主表面的指针lpDDSPrimary 。然后调用 IDirectDrawSurface::GetAttachedSurface 取得后台缓冲区的指针: ddscaps.dwCaps = DDSCAPS_BACKBUFFER; ddrval = lpDDSPrimary->GetAttachedSurface( &ddcaps, &lpDDSBack ); if( ddrval == DD_OK ) {  // lpDDSBack 指向后台缓冲区 } else{  return FALSE; }    如果IDirectDrawSurface::GetAttachedSurface调用成功的话,lpDDSBack 就指向后台缓冲区了。现在我们就可以对后台缓冲区进行各种的操作,把想显示的内容写进去。比如游戏,我们就先调入背景图象,然后再调入各种角色当前应显示的图象,最后是对白文字等。全部完成之后,把后台缓冲区和主表面进行翻转,就把你想要的东西显示出来了。

5、翻转表面 while( 1 ) {            HRESULT ddrval;            ddrval = lpDDSPrimary->Flip( NULL, 0 );            if( ddrval == DD_OK )                                   {                break; //转完成,退出循环                 }            //果表面丢失了                     if( ddrval == DDERR_SURFACELOST )                        {                       ddral = LpDDSPrimary->Restore(); //复表面                      if( ddral != DD_OK )                      {                      break;//恢复成功,退出循环                            }              }            //果前一次表面翻转还未发生            if( ddrval != DDERR_WASSTILLDRAWING )            {                break;           }       }    例中,lpDDSPrimary指明了主表面及其后台缓冲区。调用IDirectDrawSurface::Flip后,主表面和后台缓冲区交换。如果调用成功,返回DD_OK,程序终止While循环;如果返回DDERR_SURFACELOST,表明可能是表面丢失,需要用IDirectDrawSurface::Restore 恢复该表面,若恢复成功,就再一次调用IDirectDrawSurface::Flip方法;如果失败,程序终止While循环并返回一个错误值。另外,即使IDirectDrawSurface::Flip调用成功,交换也不是立即完成,它将等到系统中在此之前的表面交换都完成后才进行。如果前一次的表面翻转还未发生,IDirectDrawSurface::Flip 就返回DDERR_WASSTILLDRAWINGIDirectDrawSurface::Flip将继续循环直到返回DD_OK

6、结束程序之前别忘了用Release()释放你建立的对象。    if( lpDD != NULL )            {        if( lpDDSPrimary != NULL )        {            lpDDSPrimary->Release();            lpDDSPrimary = NULL;       }        lpDD->Release();        lpDD = NULL;

2.3 小结

简单介绍了DirectX编程, 要想全面掌握DirectX编程的技巧,你还须付出艰辛的努力.

* 游戏开发环境

本人制作的中国象棋和五子棋游戏均在Visual C++.NET下开发完成.

运行环境:所有Windows操作平台


* 游戏开发过程

我开发的游戏源码中已有比较详尽的注释,即便是一个初学编程的人也能够看懂,在此不再详叙。

此章以我开发的中国象棋为例介绍游戏的开发过程。

4.1 中国象棋功能介绍

    1.符合中国象棋行棋规则。

    2.开始新游戏。

    3.悔棋。

    4.行棋记录。

    5.背景音乐。

4.2 游戏初始化

     1.定义变量,定义棋子类:

class pawn                                      // 棋子类

{

public:

    int x,y;                                    // 坐标

    int fag;                                    // 类型

    BOOL type;                                  // 属于那一方

    BOOL cz;                                    // 存在性

    pawn(void){};

    ~pawn(void){};

};

class Data                                      // 保存行棋信息类

{

public:

    char str[20];                               // 所走名称

    int x,y;                                    // 走至坐标

    int tx,ty;                                  // 原本坐标

    int fag;                                    // 棋子类型

    BOOL type;                                  // 所属那一方

    BOOL cz;                                    // 所走位置是否有子,即被吃子

    int nfag;                                   // 被吃子的类型

    BOOL ntype;                                 // 被吃子属于那一方

    Data(){};

    ~Data(){};

};

// 全局变量:

HINSTANCE hInst;                                // 当前实例

TCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本

TCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名

HDC hdc,mdc,buffdc;                             // 窗口DC,缓冲区DC,当前DC

HBITMAP bmp;                                    // 空位图对象

HBITMAP bg,cs;                                  // 背景图片,棋子图片

HWND hWnd;                                      // 主窗口句柄

char str[20]="";                                //

char ch[20]="";                                 //

Data d[1000];                                   // 保存行棋信息

pawn p[9][10];                                  // 棋子储存数组

BOOL tt=false;                                  // 判断是否需要画方框

BOOL run=false;                                 // 判断是否可走

int col;                                        // 记录走过的一方

int tx,ty;                                      // 上次鼠标点击的坐标

int x,y;                                        // 当前鼠标点击的坐标

int cx,cy;                                      // 鼠标移动坐标

int leng=0;                                     // 判断车和炮走子中间有几个子

int t=1;                                        // 鼠标点击计数

int n;                                          // 计算走的步数

Midi midi;                                      // 播放音乐对象

int playlist;                                   // 播放音乐列表下标

char *music[6]={"剑侠情缘.mid","谢雨欣天仙子.mid","轩辕剑-天之痕插曲.mid","仙剑问情.mid","御剑江湖.mid","挥剑问情.mid"};       // 音乐列表

2.游戏初始化函数

游戏初始化函数init(),初始化对象:

    游戏中用到的变量,主要是开始新游戏用到的变量。

    初始化棋子位置。

4.3 图形处理

    图形是游戏中的重点,正确的贴图才能创造完美的游戏,可以说贴图是游戏的核心部分。

4.3.1 建立窗口DC

    获取窗口DC,建立缓存DC,建立位图对象,加载图片。

   hdc=GetDC(hWnd);                             //获取主窗口DC

   mdc=CreateCompatibleDC(hdc);                 //创建副窗口DC,即缓存DC,兼容DC,内存DC

   buffdc=CreateCompatibleDC(hdc);              //当前DC,临时DC

 

   //以下两行是在mdc上进行透明处理

   bmp=CreateCompatibleBitmap(hdc,632,434);     //建立一个空位图对象

   SelectObject(mdc,bmp);                       //将改位图对象置于缓存DC

   

   bg=(HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,632,434,LR_LOADFROMFILE);   //加载背景图片

   cs=(HBITMAP)LoadImage(NULL,"c.bmp",IMAGE_BITMAP,320,160,LR_LOADFROMFILE);    //加载棋子图片

 

    将位图绘制到窗口中必须经过以下几个步骤:

    从文件中加载位图对象。

    建立一个与窗口DC兼容的内存DC

    内存DC使用步骤1所建立的位图对象。

    将内存DC的内容粘贴到窗口DC中,完成显像操作。

4.3.2 棋子贴图

1.将屏蔽图与背景图做“AND”(Raster 值为SRCAND)运算,贴到目的地DC中。

2.将前景图与背景图做“OR”(Raster 值为SRCPAINT)运算,贴到目的地DC中。

代码如下:

    //mdc中贴背景图片

    SelectObject(buffdc,bg);

    BitBlt(mdc,0,0,632,434,buffdc,0,0,SRCCOPY);

   

    //以下为贴棋子透明处理 异或贴图

    SelectObject(buffdc,cs);

    for(i=0;i<=8;i++)

        for(j=0;j<=9;j++)

        if(p[i][j].cz==1)

        {

            BitBlt(mdc,48+39*p[i][j].x,19+39*p[i][j].y,38,38,buffdc,275,1,SRCAND);

    BitBlt(mdc,48+39*p[i][j].x,19+39*p[i][j].y,38,38,buffdc,1+39*p[i][j].fag,1+39*p[i][j].type,SRCPAINT);

        }

BitBlt(hdc,0,0,632,434,mdc,0,0,SRCCOPY);    //粘贴内存DC内容至主窗口

    游戏初始化后贴图如下:

4.4 行棋规则

    象棋行棋规则是游戏编程中最难的部分,需要精通数据结构,有很强的逻辑思维。车、马、相、士、帅、炮、兵,七种棋子的行棋规则各不相同,特别士车和炮的编写最难,几乎要浪费整个游戏制作的一半时间。具体处理方法见游戏源码。

4.5 音乐处理

    为了在游戏中能够播放背景音乐,我重新编写了一个音乐播放器,可以播放MID音乐。

enum Status{Opened,Played,Paused,Stoped,Closed};

class Midi

{

public:

    Midi(void);

    ~Midi(void);

    DWORD Open(char *fileName);                 // 打开设备

    void Close(void);                           // 关闭设备

    DWORD Play(HWND hWnd,char *fileName);       // 播放音乐

    void Stop(void);                            // 停止播放

    void Pause(void);                           // 暂停播放

    Status m_Status;                            // 设备状态

    MCIDEVICEID m_wDeviceID;                    // 保存设备号

    void Resume(void);

    DWORD GetStatus(DWORD dwItem,HWND m_hWnd);

};

    各个函数的实现在此略去,详见源码。

4.6 操作处理

    此部分均在消息处理函数中实现,具体包括新游戏,悔棋,退出,音乐的播放、暂停、停止、选歌等,具体实现见源码。

4.7 中国象棋游戏制作说明

1,中国象棋游戏按照中国象棋行棋规则,可以由两个人下,一方走过另一方走,各走一步曰一个回合,游戏中点击鼠标左键单击可以选中将要走的棋子(棋子周围有黑色方块圈着),在目的地再次点击鼠标左键即可行走.

2,游戏右侧空白处显示行走步数和每一次所走的名称,在游戏菜单中有悔棋选项,每选中一次可以悔棋一步,最多可以回到棋盘初始化位置.

3,在音乐菜单中可以播放音乐,直接选中播放将随机播放音乐,也可以直接选择自己喜欢听的音乐,音乐播放完毕不会循环播放.

4,由于象棋人工智能难度太大,非一人所为,所以我没有写人工只能.然游戏必须两人操作,此是游戏的最大缺点,本来想写网络对战,但我Winsock编程不是太熟,一直未写,见谅!

赵飞宇 2006.4.12

另附:

我制作了中国象棋和五子棋游戏安装程序,可以把游戏安装在您的电脑上,在程序组和桌面创建快捷方式,以便于您的使用.

安装程序包括了主程序和音乐图片资源,还有程序源码.