游戏入门
游戏入门
if(!你喜欢游戏)exit(0);
if(你精通游戏编程)exit(0);
各位游戏fans是否想过编自己的游戏。小弟我学习这个问题很久了,从DOS时代的ModeX到Windows下的DirectX,其间略有所得。愿我的经验和教训,为大家抛砖引玉,引出中国的世界级游戏大作,拿起你的笔开始写游戏吧!
什么是游戏呢?从最原始的游戏机到现在,游戏已经越来越精美,种类也越来越多,角色扮演、即时战略、第一人称射击等等。这些游戏的共性是什么呢?就我个人看来,游戏有一个游戏空间,有一套游戏规则和将游戏空间和游戏规则封装起来的交互界面。玩家(人或AI)通过交互界面,遵循游戏规则去改变游戏空间的状态(当然电脑可以绕过交互界面),直到游戏结束。无论什么游戏都可归结为此。
比如下象棋。游戏空间可描述为一个矩阵,矩阵中的元素有“无”、“车”、“将”等属性。游戏规则自然是象棋的规则了。棋盘棋子组成交互界面。下棋时,通过棋盘棋子去改变游戏空间,当然必须遵循各种棋子的走法。有一步时,游戏空间中少了有“将”或“帅”属性的元素,则下完了。与电脑对弈时,电脑可以直接改变游戏空间,但改变必须通过交互界面表现出来。
举一个电脑游戏的例子,如Starcraft。表示地形、建筑、部队的数据结构构成了游戏空间。各兵种的攻防,建筑的防护,生产的速度等等内容是人和电脑都要遵守的规则(不排除有些游戏中用作弊以掩盖AI太低的做法)。至于Starcraft的交互界面大家一定都很熟了吧,我就不再罗嗦了!
游戏空间和规则可化为内核,在电脑游戏中,AI也可算内核的一部分。现在有些游戏,只注重交互界面而不注重内核。比如有些游戏换成了花里胡哨的3D界面,而内核则和几年前的老前辈一模一样,实在……
现在,大家对如何实现游戏有了一个大概了解了吧!根据这个框架就可以有如下的实现方法:首先用数学模型表示游戏空间,然后将数学模型反映到交互界面上去。当玩家产生一个输入时,由交互界面处理,按游戏规则改变游戏空间,再将游戏空间反映到交互界面上。重复该过程直到游戏结束。
虽然游戏的内核是吸引玩家的关键,但再好的内核也需要交互界面来表现。交互界面,多数时候是图形动画,对游戏来说也是十分重要的。下面讲一讲动画的基本原理。
现在做动画的软件很多Flash,Director等等,但用程序去实现动画就必须从动画的基本原理入手。动画和电影一样,将差异很小的图像一幅幅显示,由于人眼的视觉暂留现象,看上去就像图像动了起来。比如在RPG中,我们要实现主角从(x1,y1)移动到(x2,y2)处,我们可用如下代码:
while(主角不在(x2,y2)){
在主角(x,y)处恢复背景;
在(x+dx,y+dy)处重绘主角;
x=x+dx;y=y+dy;
};
当主角很小时,这种方法是可行的,但主角稍大,就会出现闪烁现象。闪烁的原因不是因为你的CPU和GPU不够快,而同样是因为视觉暂留。当你恢复背景之后,屏幕上没有主角,虽然这个过程很短,但由于视觉暂留的原因,这幅图仍然会在你的眼中持续一段时间,你就会觉得主角一会出现一会儿消失。解决的方法是双缓冲技术。即构造两个屏幕缓冲区,一个表示当前屏幕,一个在后台表示下一帧的屏幕。在后台缓冲区上画好下一帧后,一次性送到前台上去,这样就不会出现闪烁现象了。刚才的过程可以描述为:
while(主角不在(x2,y2)){
在后台缓冲恢复背景;
在后台缓冲(x+dx,y+dy)处恢主角;
将后台缓冲送到前台;
x=x+dx;y=y+dy;
};
以上过程在ModeX下可用VGA的寄存器置位来实现,在DirectX中可用Flip或Blt实现。ModeX是实模式下的编程方式,以前DOS下的游戏经常使用,在《图形编程人员手册》(作者是编写QUACK I 小组的成员)中详细讲述了Mode 13和Mode X下如何编程。DirectX,什么你不知道?你是游戏Fans吗?
说了这么多枯燥的理论,下面 --开始编程了。
游戏SDK属DirectX和OpenGL(OpenGL的主要用途并不是编游戏),这里我们主要讲DirectX中的DirectDraw。因为三维图形编程非常复杂,需要大量图形学的知识,非几篇文章可以讲清的。我们不用3D方式,Direct3D和OpenGL就用不上了,各位Fans如果对3D编程感兴趣,努力去学计算机图形学吧!
用DirectX编程就需要编译器和DirectX的SDK。我使用的是MS的VC++6.0和DirectX7.0的SDK。一般DirectX7.0的安装组件是不行的,需要一份207MB的SDK。假定SDK安装在E:\mssdk\,文件夹下会有bin,doc, include, lib ,samples几个文件来。对我们最有用的是inlude和lib文件夹,编译器需要的头文件和库文件都在这里。VC++6.0自带对DirectX5.0的支持,要使VC使用新版的头文件和库文件,需要作如下设置:在Tools -> Option的Directories标签下,为include files 和library files分别添加E:\mssdk\include 和 E:\mssdk\lib ,注意,应放在最前面。
做完了上述准备活动后,我们就可以使用DirectX7.0的强大功能了。下面让我们小试牛刀一把!
创建一个空的Win32 Application工程。在Project -> Settings的Link标签下的Object/library modules 中添加ddraw.lib 和dxguid.lib(用到其他库时应添加相应的库)。
头文伴Dobject.h
#include <ddraw.h>
int TIMER;
LPDIRECTDRAW pDD;
LPDIRECTDRAW7 pDD7;
LPDIRECTDRAWSURFACE7 pDDSPrimary=NULL;
LPDIRECTDRAWSURFACE7 pDDSBack=NULL;
int bActive;
本文件定义了所用的全局变量。LPDIRECTDRAW7是指向DirectDraw7对象接口的指针,LPDIRECTDRAWSURFACE7是指向DirectDrawSurface7对象接口的指针。TIMER是一个定时器,bActive则是表示窗口是否被激活。
头文件 Dfun.h
#include "windows.h"
int CreateDrawScreen(int cx,int cy,int bit_depth,HWND hwnd);
int onTIMER(HWND hwnd, UINT iMsg, WPARAM wParam,LPARAM lParam);
int onKEYDOWN(HWND hwnd, UINT iMsg, WPARAM wParam,LPARAM lParam);
int UpdataBackSurface(HWND hwnd);
int ReleaseAllObjects(void);
这里定义了全局函数。这些函数从名字上一看即知其用途,我就不细讲了。至于定义为int,纯属小弟我的习惯。
源文件DD1main.cpp
#include "Dfun.h"
#include <windows.h>
HINSTANCE pInstance;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow) {
static char szAppName[] = "AppName" ; // Application name
HWND hwnd ;
MSG msg ;
extern int TIMER;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW ¦ CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
pInstance = hInstance;
// Registering the structure wmdclass
RegisterClassEx (&wndclass) ;
// CreateWindow()
/*1*/ hwnd = CreateWindowEx (WS_EX_TOPMOST,
szAppName,
"DD1", // window caption
/*2*/ WS_POPUP¦WS_MINIMIZE, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
/*3*/ GetSystemMetrics(SM_CXSCREEN), // initial x size
/*4*/ GetSystemMetrics(SM_CYSCREEN), // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL) ; // creation parameters
ShowWindow (hwnd, iCmdShow) ;
/*5*/ CreateDrawScreen(640,480,16,hwnd);
SetTimer(hwnd,TIMER,200,NULL);
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
//****************************
// Windows Procedure
//****************************
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam,
LPARAM lParam)
{ extern int bActive;
switch (iMsg)
{ case WM_KEYDOWN:
onKEYDOWN(hwnd,iMsg,wParam,lParam);
break;
case WM_TIMER:
onTIMER(hwnd,iMsg,wParam,lParam);
break;
case WM_ACTIVATE:
bActive = !((BOOL)HIWORD(wParam));
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
ReleaseAllObjects();
return 0;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
这个源文件应改是大家比较熟悉的(什么?你不会用Win32API编程!快去找本书学习一下)。需要注意的地方,我都加了号。/*1*/行我们将窗口属性设为TOPMOST,同时在/*2*/和/*3*/行设置了POPUP和MINIZE属性。因为DirectDraw程序一般以全屏独占模式运行,所以设为没有标题栏和边框的弹出式窗口,并且总在最上方,而MINIZE使你的窗口可以最小化,否则别的应用程序无法激活。为了使窗口以全屏大小运行,在/*4*//*5*/行用GetSystemMetrics( )函数取得屏幕大小,并将窗口设为屏幕大小。ShowWindow()之后,我们调用CreateDrawSurface()函数将分辩率设为640*480*16位色,并初始化了全局变量。接着用SetTimer()设置了一个每200亳秒产生一个WM_TIMER消息的定时器。
源文件Dfun.cpp
#include "Dobject.h"
#include "Dfun.h"
#include <windows.h>
int ReleaseAllObjects()
{
if (pDD7!= NULL)
{ if(pDDSBack!=NULL){
pDDSBack->Release();
pDDSBack=NULL;
}
if (pDDSPrimary != NULL)
{
pDDSPrimary->Release();
pDDSPrimary = NULL;
}
pDD7->Release();
pDD7= NULL;
}
return 1;
};
int UpdataBackSurface(HWND hwnd)
{ DDBLTFX ddbltfx;
RECT rc;
HRESULT hRet;
static int phase=0;
HDC hdc;
ZeroMemory(&ddbltfx, sizeof(ddbltfx));
ddbltfx.dwSize=sizeof(ddbltfx);
ddbltfx.dwFillColor=0;
pDDSBack->Blt(NULL, NULL, NULL, DDBLT_COLORFILL ¦ DDBLT_WAIT, &ddbltfx);
//insert Draw Suface code end
if (pDDSBack->GetDC(&hdc) == DD_OK)
{
SetBkColor(hdc, RGB(0, 0, 255));
SetTextColor(hdc, RGB(255, 255, 0));
GetClientRect(hwnd, &rc);
if (phase)
{ DrawText(hdc,"你好",-1,&rc,DT_CENTER);
phase=0;
}else{
DrawText(hdc,"DirectTex",-1,&rc,DT_TOP);
phase=1;
};
pDDSBack->ReleaseDC(hdc);
}
return 1;
}
int CreateDrawScreen(int cx,int cy,int bit_depth,HWND hwnd)
{ HRESULT hRet;
DDSURFACEDESC2 ddsc;
DDSCAPS2 ddsCaps;
hRet=DirectDrawCreateEx(NULL,(void**)&pDD7,IID_IDirectDraw7,NULL);
if(hRet!=DD_OK)return 0;
hRet=pDD7->SetCooperativeLevel(hwnd,DDSCL_EXCLUSIVE ¦ DDSCL_FULLSCREEN);
if(hRet!=DD_OK)return 0;
hRet=pDD7->SetDisplayMode(cx,cy,bit_depth,NULL,NULL);
if(hRet!=DD_OK)return 0;
ZeroMemory(&ddsc, sizeof(ddsc));
ddsc.dwSize=sizeof(ddsc);
ddsc.dwFlags=DDSD_CAPS ¦ DDSD_BACKBUFFERCOUNT;
ddsc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE¦DDSCAPS_FLIP ¦ DDSCAPS_COMPLEX;
ddsc.dwBackBufferCount = 1;
hRet=pDD7->CreateSurface(&ddsc,&pDDSPrimary,NULL);
if(hRet!=DD_OK)return 0;
ZeroMemory(&ddsCaps, sizeof(ddsCaps));
ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
hRet=pDDSPrimary->GetAttachedSurface(&ddsCaps,&pDDSBack);
if(hRet!=DD_OK)return 0;
return 1;
};
int onTIMER(HWND hwnd, UINT iMsg, WPARAM wParam,LPARAM lParam)
{ HRESULT hRet;
if(bActive&&TIMER==wParam){
UpdataBackSurface(hwnd);
while(1){
hRet=pDDSPrimary->Flip(NULL, 0);
if (hRet==DD_OK)break;
if (hRet==DDERR_SURFACELOST){
hRet=pDDSPrimary->Restore();
if (hRet != DD_OK)break;
}
if (hRet!= DDERR_WASSTILLDRAWING)break;
};
};
return 1;
};
int onKEYDOWN(HWND hwnd, UINT iMsg, WPARAM wParam,LPARAM lParam)
{
switch(wParam)
{
case VK_F12:
case VK_F11:
PostQuitMessage(0);
ReleaseAllObjects();
return 0;
};
return 0;
}
本文件集中了关键性代码,一定要注意了。ReleaseAllObjects( )函数释放所有的全局对象。onKEYDOWN()就不用我说了吧!CreateDrawScreen()完成了所有DirectDraw对象的初始化工作。首先当然是创建一个DriectDraw对象并使pDD7指向他接口。然后设置合作等级为全屏独占模式。设为独占模式后才能设分辩率为640*480*16bit。下一步是创建表面,这里创建了一个复杂的表面,一个由pDDSPrimary和pDDSBack所指向的两个DirectDrawSurface7对象组成的Flip队列。pDDSBack指向的DirectDrawSurface7对象是附加到主表面上的。CreateDrawScreen()是一个通用的函数,以后的程序都可以重用他。
onTIMER()函数响应TIMER定时器。如果本程序被激活,则更新后缓冲表面,然后Flip(翻转)到前台。UpdataBackSurface()先用无源表面的Blt(位块传输)是整个表面被黑色填充,再通过DirectDrawSurface对象的GetDC方法得到一个与普通DC完全兼容的DC,然后用API函数DrawText输出文本。简单吧,有了DC,Win32API都可用了。
编译,链接之后,运行。你可以看到两行文字在屏幕上闪烁。看上去很简单,但DriectDraw的两个关键方法 Blt 和Flip 都用上了。可能讲的不明白,下次再仔细的讲DirectDraw,DirectDrawSurface,以及高性能的动画 。
问题点数:20、回复次数:16Top
1 楼windindance(风舞轻扬·白首为功名)回复于 2001-11-03 14:21:16 得分 0
关注Top
2 楼xxjiang(箫湘)回复于 2001-11-03 19:16:34 得分 0
刚才看了你以前发的帖子,忘了加分,特来补上。
在那里留了言,希望你去看看,交个朋友!Top
3 楼yeahwin(佳佳幼儿园)回复于 2001-11-03 20:00:23 得分 0
兴趣~好
GZ~Top
4 楼jerry_baimor(崇拜starfish)回复于 2001-11-10 11:39:04 得分 0
关注。Top
5 楼jackhacker()回复于 2001-11-11 09:24:31 得分 0
很好Top
6 楼chenweijin(陈伟金)回复于 2001-11-11 11:56:11 得分 0
能教我吗?Top
7 楼jhwh(弹剑长歌(搬个凳子来灌水))回复于 2001-11-11 12:53:29 得分 5
最好的教程是M$的 GemeSDKTop
8 楼jackhacker()回复于 2001-12-25 20:11:45 得分 5
gamesdk???Top
9 楼zhouqin442(妖刀)回复于 2001-12-25 22:31:51 得分 5
程序错误百出!Top
10 楼KingofMagic(大魔头)回复于 2001-12-26 08:57:57 得分 0
?????!!!!!!!!!!!Top
11 楼wangqiqi(polymath)回复于 2001-12-26 13:04:48 得分 0
你把内核和外壳搞反了。
3D 图象显示是游戏的内核(ENGINE)而游戏的规则等是在引擎的基础上编写的。Top
12 楼liutuohb()回复于 2001-12-26 22:33:58 得分 0
留个QQ
56235872Top
13 楼kingofark(平凡的丑人)回复于 2001-12-27 09:04:18 得分 5
大家不妨去看看
www.lostsidedead.com 上面有用极小的图形库编程简单游戏的C++源代码,非常适合初学入门。
www.1cplusplusstreet.com 上面有很多游戏源代码,也都很不错。Top
14 楼ilovesex(爱在性)回复于 2001-12-27 10:56:46 得分 0
好哦,我会去看的。Top
15 楼KingofMagic(大魔头)回复于 2001-12-28 09:05:42 得分 0
只是把那几个通用的例子变了一种形式说出来吧了!Top
16 楼hoomail(lh)回复于 2001-12-28 16:52:43 得分 0
厉害!Top




