NeHe OpenGL教程 第二十一课:线的游戏
轉(zhuǎn)自【翻譯】NeHe OpenGL 教程
前言
聲明,此?NeHe OpenGL教程系列文章由51博客yarin翻譯(2010-08-19),本博客為轉(zhuǎn)載并稍加整理與修改。對(duì)NeHe的OpenGL管線教程的編寫,以及yarn的翻譯整理表示感謝。
?
NeHe OpenGL第二十一課:線的游戲
線,反走樣,計(jì)時(shí),正投影和簡(jiǎn)單的聲音:
這是我第一個(gè)大的教程,它將包括線,反走樣,計(jì)時(shí),正投影和簡(jiǎn)單的聲音。希望這一課中的東西能讓每個(gè)人感到高興。
?
歡迎來到第21課,在這一課里,你將學(xué)會(huì)直線,反走樣,正投影,計(jì)時(shí),基本的音效和一個(gè)簡(jiǎn)單的游戲邏輯。希望這里的東西可以讓你高興。我花了兩天的時(shí)間寫代碼,并用了兩周的時(shí)間寫這份HTML文件,希望你能享受我的勞動(dòng)。
在這課的結(jié)尾你將獲得一個(gè)叫"amidar"的游戲,你的任務(wù)是走完所有的直線。這個(gè)程序有了一個(gè)基本游戲的一切要素,關(guān)卡,生命值,聲音和一個(gè)游戲道具。
我們從第一課的程序來逐步完整這個(gè)程序,按照慣例,我們只介紹改動(dòng)的部分。
#include?<windows.h>???????
#include?<stdio.h>???????
#include?<stdarg.h>??????
#include?<gl\gl.h>???????
#include?<gl\glu.h>???????
#include?<gl\glaux.h>???????
HDC??hDC=NULL;???????
HGLRC??hRC=NULL;???????
HWND??hWnd=NULL;???????
HINSTANCE?hInstance;???????
??
bool類型的變量,vline保存了組成我們游戲網(wǎng)格垂直方向上的121條線,上下水平各11條。hline保存了水平方向上的 121條線,用ap來檢查A鍵是否已經(jīng)按下。
當(dāng)網(wǎng)格被填滿時(shí), filled被設(shè)置為TRUE而反之則為FALSE。gameover這個(gè)變量的作用顯而易見,當(dāng)他的值為TRUE時(shí),游戲結(jié)束。anti指出抗鋸齒功能是否打開,當(dāng)設(shè)置為TRUE時(shí),該功能是打開著的。active 和 fullscreen 指出窗口是否被最小化以及游戲窗口是窗口模式還是全屏模式。
bool??keys[256];???????
bool??vline[11][10];???????// 保存垂直方向的11根線條中,每根線條中的10段是否被走過
bool??hline[10][11];???????//保存水平方向的11根線條中,每根線條中的10段是否被走過
bool??ap;????????// A鍵是否已經(jīng)按下
bool??filled;????????// 網(wǎng)格是否被填滿?
bool??gameover;???????// 游戲是否結(jié)束?
bool??anti=TRUE;???????// 是否啟用反走樣?
bool??active=TRUE;???????
bool??fullscreen=TRUE;??????
??
接著設(shè)置整型變量。loop1 和 loop2 被用來檢查網(wǎng)格,查看是否有敵人攻擊我們,以及在網(wǎng)格上給對(duì)象一個(gè)隨機(jī)的位置。你將看到loop1 / loop2在后面的程序得到使用。delay 是一個(gè)計(jì)數(shù)器,我用他來減慢那些壞蛋的動(dòng)作。當(dāng)delay的值大于某一個(gè)饋值的時(shí)候,敵人才可以行動(dòng),此時(shí)delay將被重置。
adjust是一個(gè)非常特殊的變量,即使我們的程序擁有一個(gè)定時(shí)器,他也僅僅用來檢查你的計(jì)算機(jī)是否運(yùn)行地太快。如果是,則需要暫停一下以減慢運(yùn)行速度。在我地GeForce顯卡上,程序的運(yùn)行平滑地簡(jiǎn)直變態(tài),并且非常非???。但是在我的PIII/450+Voodoo 3500TV上測(cè)試的時(shí)候,我注意到程序運(yùn)行地非常緩慢。我發(fā)現(xiàn)問題在于關(guān)于時(shí)間控制那部分代碼只能夠用來減慢游戲進(jìn)行而并不能加速之。因此我引入了一個(gè)叫做adjust 的變量。它可以是0到5之間的任何值。游戲中的對(duì)象移動(dòng)速度的不同依賴于這個(gè)變量的值。值越小,運(yùn)動(dòng)越平滑;而值越大,則運(yùn)動(dòng)速度越快。這是在比較慢的機(jī)器上運(yùn)行這個(gè)程序最簡(jiǎn)單有效的解決方案了。但是請(qǐng)注意,不管對(duì)象移動(dòng)的速度有多快,游戲的速度都不會(huì)比我期望的更快。我們推薦把a(bǔ)djust值設(shè)置為3,這樣在大部分機(jī)器上都有比較滿意的效果。
我們把lives的值設(shè)置成5,這樣我們的英雄一出場(chǎng)就擁有5條命。level是一個(gè)內(nèi)部變量,用來指出當(dāng)前游戲的難度。當(dāng)然,這并不是你在屏幕上所看到的那個(gè)Level。變量level2開始的時(shí)候和Level擁有相同的值,但是隨著你技能的提高,這個(gè)值也會(huì)增加。當(dāng)你成功通過難度3之后,這個(gè)值也將在難度3上停止增加。level 是一個(gè)用來表示游戲難度的內(nèi)部變量,stage才是用來記錄當(dāng)前游戲關(guān)卡的變量。?
??
int??loop1;????????// 通用循環(huán)變量
int??loop2;????????// 通用循環(huán)變量
int??delay;????????// 敵人的暫停時(shí)間
int??adjust=3;???????// 調(diào)整顯示的速度
int??lives=5;???????// 玩家的生命
int??level=1;???????// 內(nèi)部游戲的等級(jí)
int??level2=level;???????// 顯示的游戲的等級(jí)
int??stage=1;???????// 游戲的關(guān)卡
接下來我們需要一個(gè)結(jié)構(gòu)來記錄游戲中的對(duì)象。fx和fy每次在網(wǎng)格上移動(dòng)我們的英雄和敵人一些較小的象素,以創(chuàng)建一個(gè)平滑的動(dòng)畫效果。x和y則記錄著對(duì)象處于網(wǎng)格的那個(gè)交點(diǎn)上。
上下左右各有11個(gè)點(diǎn),因此x和y可以是0到10之間的任意值。這也是我們?yōu)槭裁葱枰猣x和fy的原因??紤]如果我們只能夠在上下和左右方向的11個(gè)點(diǎn)間移動(dòng)的話,我們的英雄不得不
在各個(gè)點(diǎn)間跳躍前進(jìn)。這樣顯然是不夠平滑美觀的。
最后一個(gè)變量spin用來使對(duì)象在Z軸上旋轉(zhuǎn)。?
??
struct??object????????// 記錄游戲中的對(duì)象
{
?int?fx, fy;????????// 使移動(dòng)變得平滑
?int?x, y;????????// 當(dāng)前游戲者的位置
?float?spin;????????// 旋轉(zhuǎn)方向
};
既然我們已經(jīng)為我們的玩家,敵人,甚至是秘密武器。設(shè)置了結(jié)構(gòu)體,那么同樣的,為了表現(xiàn)剛剛創(chuàng)設(shè)的結(jié)構(gòu)體的功能和特性,我們也可以為此設(shè)置新的結(jié)構(gòu)體。
為我們的玩家創(chuàng)設(shè)結(jié)構(gòu)體之下的第一條直線。基本上我們將會(huì)為玩家提供fx,fy,x,y和spin值幾種不同的結(jié)構(gòu)體。通過增加這些直線,僅需查看玩家的x值我們就很容易取得玩家的位置,同時(shí)我們也可以通過增加玩家的旋轉(zhuǎn)度來改變玩家的spin值。
第二條直線略有不同。因?yàn)橥黄聊晃覀兛梢酝瑫r(shí)擁有至多15個(gè)敵人。我們需要為每個(gè)敵人創(chuàng)造上面所提到的可變量。我們通過設(shè)置一個(gè)有15個(gè)敵人的組來實(shí)現(xiàn)這個(gè)目標(biāo),如第一個(gè)敵人的位置被設(shè)定為敵人(0).x.第二個(gè)敵人的位置為(1),x等等
第三條直線使得為寶物創(chuàng)設(shè)結(jié)構(gòu)體實(shí)現(xiàn)了可能。寶物是一個(gè)會(huì)時(shí)不時(shí)在屏幕上出現(xiàn)的沙漏。我們需要通過沙漏來追蹤x和y值。但是因?yàn)樯陈┑奈恢檬枪潭ǖ乃晕覀儾恍枰獙ふ易罴盐恢?#xff0c;而通過為程序后面的其他物品尋找好的可變量來實(shí)現(xiàn)(如fx和fy)?
??
struct?object?player;????????// 玩家信息
struct?object?enemy[9];???????// 最多9個(gè)敵人的信息
struct?object?hourglass;???????// 寶物信息
現(xiàn)在我們創(chuàng)建一個(gè)描述時(shí)間的結(jié)構(gòu),使用這個(gè)結(jié)構(gòu)我們可以很輕松的跟蹤時(shí)間變量。
接下來的第一步,就是創(chuàng)建一個(gè)64位的頻率變量,它記錄時(shí)間的頻率。
resolution變量用來記錄最小的時(shí)間間隔。
mm_timer_start和mm_timer_elapsed保存計(jì)時(shí)器開始時(shí)的時(shí)間和計(jì)時(shí)器開始后流失的時(shí)間。這兩個(gè)變量只有當(dāng)計(jì)算機(jī)不擁有performance counter時(shí)才啟用。
變量performance_timer用來標(biāo)識(shí)計(jì)算機(jī)是否有performance counter
如果performance counter啟用,最后兩個(gè)變量用來保存計(jì)時(shí)器開始時(shí)的時(shí)間和計(jì)時(shí)器開始后流失的時(shí)間,它們比普通的根據(jù)精確。
?
struct??? ???????// 保存時(shí)間信息的結(jié)構(gòu)
{
? __int64?????? frequency;???????// 頻率
? float???????? resolution;???????// 時(shí)間間隔
? unsigned long mm_timer_start;???????// 多媒體計(jì)時(shí)器的開始時(shí)間
? unsigned long mm_timer_elapsed;??????// 多媒體計(jì)時(shí)器的開始時(shí)間
? bool??performance_timer;??????// 使用Performance Timer?
? __int64?????? performance_timer_start;?????// Performance Timer計(jì)時(shí)器的開始時(shí)間
? __int64?????? performance_timer_elapsed;?????// Performance Timer計(jì)時(shí)器的開始時(shí)間
} timer;?????????
下一行代碼定義了速度表。如前所說,對(duì)象移動(dòng)的速度依賴于值adjust,而以adjust為下標(biāo)去檢索速度表,就可以獲得對(duì)象的移動(dòng)速度。?
??
int??steps[6]={ 1, 2, 4, 5, 10, 20 };????// 用來調(diào)整顯示的速度
接下來我們將為紋理分配空間。紋理一共2張,一張是背景而另外一張是一張字體紋理。如本系列教程中的其他課程一樣,base用來指出字符顯示列表的基,同樣的我們?cè)谧詈舐暶髁舜翱谶^程WndProc()。?
??
GLuint??texture[2];???????// 字符紋理
GLuint??base;????????// 字符顯示列表的開始值
LRESULT?CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);????
接下來會(huì)是很有趣的工作。接下來的一段代碼會(huì)初始化我們的計(jì)時(shí)器。代碼會(huì)檢查performance counter(非常精確的計(jì)數(shù)器)是否可用,如果不可用,則使用多媒體計(jì)時(shí)器。這段代碼是可以移植的。?
??
void TimerInit(void)????????// 初始化我們的計(jì)時(shí)器
{
?memset(&timer, 0, sizeof(timer));?????// 清空計(jì)時(shí)器結(jié)構(gòu)
?// 檢測(cè)Performance Counter是否可用,可用則創(chuàng)建
?if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency))
?{
??// 如果不可用
??timer.performance_timer?= FALSE;????// 設(shè)置Performance Timer為false
??timer.mm_timer_start?= timeGetTime();???// 使用普通的計(jì)時(shí)器
??timer.resolution?= 1.0f/1000.0f;????// 設(shè)置單位為毫秒
??timer.frequency??= 1000;?????// 設(shè)置頻率為1000
??timer.mm_timer_elapsed?= timer.mm_timer_start;???// 設(shè)置流失的時(shí)間為當(dāng)前的時(shí)間
?}
如果performance counter 可用,則執(zhí)行下面的代碼:?
?else
?{
??// 使用Performance Counter計(jì)時(shí)器
??QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance_timer_start);
??timer.performance_timer??= TRUE;????// 設(shè)置Performance Timer為TRUE
??// 計(jì)算計(jì)時(shí)的精確度
??timer.resolution??= (float) (((double)1.0f)/((double)timer.frequency));
??// 設(shè)置流失的時(shí)間為當(dāng)前的時(shí)間
??timer.performance_timer_elapsed?= timer.performance_timer_start;
?}
}
上面的代碼設(shè)置了計(jì)時(shí)器,而下面的代碼則讀出計(jì)時(shí)器并返回已經(jīng)經(jīng)過的時(shí)間,以毫秒計(jì)。代碼很簡(jiǎn)單,首先檢查是否支持performance counter,若支持,則調(diào)用其相關(guān)函數(shù);否則調(diào)用多媒體函數(shù)。?
??
float TimerGetTime()????????// 返回經(jīng)過的時(shí)間,以毫秒為單位
{
?__int64 time;????????// 使用64位的整數(shù)
?if (timer.performance_timer)??????// 是否使用Performance Timer計(jì)時(shí)器?
?{
??QueryPerformanceCounter((LARGE_INTEGER *) &time);??// 返回當(dāng)前的時(shí)間
??// 返回時(shí)間差
??return ( (float) ( time - timer.performance_timer_start) * timer.resolution)*1000.0f;
?}
?else
?{
??// 使用普通的計(jì)時(shí)器,返回時(shí)間差
??return( (float) ( timeGetTime() - timer.mm_timer_start) * timer.resolution)*1000.0f;
?}
}
在下面的代碼里,我們把玩家重置在屏幕的左上角,而給敵人設(shè)置一個(gè)隨機(jī)的位置。?
??
void ResetObjects(void)????????// 重置玩家和敵人
{
?player.x=0;????????// 把玩家置于左上角
?player.y=0;????????
?player.fx=0;????????
?player.fy=0;????????
接著我們給敵人一個(gè)隨機(jī)的開始位置,敵人的數(shù)量等于難度乘上當(dāng)前關(guān)卡號(hào)。記著,難度最大是3,而最多有3關(guān)。因此敵人最多有9個(gè)。?
??
?for (loop1=0; loop1<(stage*level); loop1++)????// 循環(huán)隨即放置所有的敵人
?{
??enemy[loop1].x=5+rand()%6;?????
??enemy[loop1].y=rand()%11;?????
??enemy[loop1].fx=enemy[loop1].x*60;????
??enemy[loop1].fy=enemy[loop1].y*40;????
?}
}
并沒有做任何改動(dòng),因此我將跳過它。在LoadGLTextures函數(shù)里我將載入那兩個(gè)紋理--背景和字體。并且我會(huì)把這兩副圖都轉(zhuǎn)化成紋理,這樣我們就可以在游戲中使用他們。紋理創(chuàng)建好之后,象素?cái)?shù)據(jù)就可以刪除了。沒有什么新東西,你可以閱讀以前的課程以獲得更多信息。
?
int LoadGLTextures()????????
{
?int Status=FALSE;???????
?AUX_RGBImageRec *TextureImage[2];?????
?memset(TextureImage,0,sizeof(void *)*2);????
?if ?((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&???// 載入字體紋理
?? (TextureImage[1]=LoadBMP("Data/Image.bmp")))???// 載入圖像紋理
?{
??Status=TRUE;???????
??glGenTextures(2, &texture[0]);?????
??for (loop1=0; loop1<2; loop1++)?????
??{
???glBindTexture(GL_TEXTURE_2D, texture[loop1]);
???glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop1]->sizeX, TextureImage[loop1]->sizeY,
????0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop1]->data);
???glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
???glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
??}
??for (loop1=0; loop1<2; loop1++)?????
??{
???if (TextureImage[loop1])????
???{
????if (TextureImage[loop1]->data)???
????{
?????free(TextureImage[loop1]->data);?
????}
????free(TextureImage[loop1]);???
???}
??}
?}
?return Status;???????
}
下面的代碼建立了顯示列表。對(duì)于字體的顯示,我已經(jīng)寫過教程。在這里我把字體圖象分成16×16個(gè)單元共256個(gè)字符。如果你有什么不明白,請(qǐng)參閱前面的教程?
??
GLvoid BuildFont(GLvoid)???????
{
?base=glGenLists(256);???????
?glBindTexture(GL_TEXTURE_2D, texture[0]);????
?for (loop1=0; loop1<256; loop1++)?????
?{
??float cx=float(loop1%16)/16.0f;?????
??float cy=float(loop1/16)/16.0f;?????
??glNewList(base+loop1,GL_COMPILE);????
???glBegin(GL_QUADS);?????
????glTexCoord2f(cx,1.0f-cy-0.0625f);??
????glVertex2d(0,16);????
????glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f);?
????glVertex2i(16,16);????
????glTexCoord2f(cx+0.0625f,1.0f-cy);??
????glVertex2i(16,0);????
????glTexCoord2f(cx,1.0f-cy);???
????glVertex2i(0,0);????
???glEnd();??????
???glTranslated(15,0,0);?????
??glEndList();???????
?}?????????
}
當(dāng)我們不再需要顯示列表的時(shí)候,銷毀它是一個(gè)好主意。在這里我仍然把代碼加上了,雖然沒有什么新東西。?
??
GLvoid KillFont(GLvoid)??
{
?glDeleteLists(base,256);??????
}
函數(shù)沒有做太多改變。唯一的改動(dòng)是它可以打印變量了。我把代碼列出這樣你可以容易看到改動(dòng)的地方。
請(qǐng)注意,在這里我激活了紋理并且重置了視圖矩陣。如果set被置1的話,字體將被放大。我這樣做是希望可以在屏幕上顯示大一點(diǎn)的字符。在一切結(jié)束后,我會(huì)禁用紋理。?
??
GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...)
{
?char??text[256];??????
?va_list??ap;???????
?if (fmt == NULL)??
??return;????????
?va_start(ap, fmt);???????
???? vsprintf(text, fmt, ap);??????
?va_end(ap);????????
?if (set>1)????????
?{
??set=1;???????
?}
?glEnable(GL_TEXTURE_2D);??????
?glLoadIdentity();???????
?glTranslated(x,y,0);???????
?glListBase(base-32+(128*set));??????
?if (set==0)????????
?{
??glScalef(1.5f,2.0f,1.0f);?????
?}
?glCallLists(strlen(text),GL_UNSIGNED_BYTE, text);???
?glDisable(GL_TEXTURE_2D);??????
}
下面的代碼基本沒有變化,只是把透視投影變?yōu)榱苏队?
??
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)????
{
?if (height==0)???????
?{
??height=1;???????
?}
?glViewport(0,0,width,height);??????
?glMatrixMode(GL_PROJECTION);??????
?glLoadIdentity();???????
?glOrtho(0.0f,width,height,0.0f,-1.0f,1.0f);
?glMatrixMode(GL_MODELVIEW);??????
?glLoadIdentity();???????
}
初始化的代碼和前面的代碼相比沒有什么改變?
??
int InitGL(GLvoid)
{
?if (!LoadGLTextures())???????
?{
??return FALSE;???????
?}
?BuildFont();????????
?glShadeModel(GL_SMOOTH);??????
?glClearColor(0.0f, 0.0f, 0.0f, 0.5f);?????
?glClearDepth(1.0f);???????
?glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);?????
?glEnable(GL_BLEND);???????
?glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
?return TRUE;????????
}
下面是我們的繪制代碼。
首先我們清空緩存,接著綁定字體的紋理,繪制游戲的提示字符串
?
int DrawGLScene(GLvoid)????????
{
?glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);???
?glBindTexture(GL_TEXTURE_2D, texture[0]);????// 選擇字符紋理
?glColor3f(1.0f,0.5f,1.0f);??????
?glPrint(207,24,0,"GRID CRAZY");??????// 繪制游戲名稱"GRID CRAZY"
?glColor3f(1.0f,1.0f,0.0f);??????
?glPrint(20,20,1,"Level:%2i",level2);?????// 繪制當(dāng)前的級(jí)別
?glPrint(20,40,1,"Stage:%2i",stage);?????// 繪制當(dāng)前級(jí)別的關(guān)卡
現(xiàn)在我們檢測(cè)游戲是否結(jié)束,如果游戲結(jié)束繪制"Gmae over"并提示玩家按空格鍵重新開始?
?if (gameover)????????// 游戲是否結(jié)束?
?{
??glColor3ub(rand()%255,rand()%255,rand()%255);???// 隨機(jī)選擇一種顏色
??glPrint(472,20,1,"GAME OVER");?????// 繪制 GAME OVER 字符串到屏幕
??glPrint(456,40,1,"PRESS SPACE");????// 提示玩家按空格鍵重新開始
?}
在屏幕的右上角繪制玩家的剩余生命?
?for (loop1=0; loop1<lives-1; loop1++)?????//循環(huán)繪制玩家的剩余生命
?{
??glLoadIdentity();??????
??glTranslatef(490+(loop1*40.0f),40.0f,0.0f);???// 移動(dòng)到屏幕右上角
??glRotatef(-player.spin,0.0f,0.0f,1.0f);????// 旋轉(zhuǎn)繪制的生命圖標(biāo)
??glColor3f(0.0f,1.0f,0.0f);?????// 繪制玩家生命
??glBegin(GL_LINES);??????// 繪制玩家圖標(biāo)
???glVertex2d(-5,-5);?????
???glVertex2d( 5, 5);?????
???glVertex2d( 5,-5);?????
???glVertex2d(-5, 5);?????
??glEnd();???????
??glRotatef(-player.spin*0.5f,0.0f,0.0f,1.0f);???
??glColor3f(0.0f,0.75f,0.0f);?????
??glBegin(GL_LINES);??????
???glVertex2d(-7, 0);?????
???glVertex2d( 7, 0);?????
???glVertex2d( 0,-7);?????
???glVertex2d( 0, 7);?????
??glEnd();???????
?}
下面我們來繪制網(wǎng)格,我們?cè)O(shè)置變量filled為TRUE,這告訴程序填充網(wǎng)格。
接著我們把線的寬度設(shè)置為2,并把線的顏色設(shè)置為藍(lán)色,接著我們檢測(cè)線斷是否被走過,如果走過我們?cè)O(shè)置顏色為白色。
?filled=TRUE;????????// 在測(cè)試前,把填充變量設(shè)置為TRUE
?glLineWidth(2.0f);???????// 設(shè)置線寬為2.0f
?glDisable(GL_LINE_SMOOTH);??????// 禁用反走樣
?glLoadIdentity();???????
?for (loop1=0; loop1<11; loop1++)?????// 循環(huán)11根線
?{
??for (loop2=0; loop2<11; loop2++)????// 循環(huán)每根線的線段
??{
???glColor3f(0.0f,0.5f,1.0f);????// 設(shè)置線為藍(lán)色
???if (hline[loop1][loop2])????// 是否走過?
???{
????glColor3f(1.0f,1.0f,1.0f);???// 是,設(shè)線為白色
???}
???if (loop1<10)??????// 繪制水平線
???{
????if (!hline[loop1][loop2])???// 如果當(dāng)前線段沒有走過,則不填充
????{
?????filled=FALSE;????
????}
????glBegin(GL_LINES);????// 繪制當(dāng)前的線段
?????glVertex2d(20+(loop1*60),70+(loop2*40));?
?????glVertex2d(80+(loop1*60),70+(loop2*40));?
????glEnd();?????
???}
下面的代碼繪制垂直的線段?
??
???glColor3f(0.0f,0.5f,1.0f);????// 設(shè)置線為藍(lán)色
???if (vline[loop1][loop2])????// 是否走過
???{
????glColor3f(1.0f,1.0f,1.0f);???// 是,設(shè)線為白色
???}
???if (loop2<10)??????// 繪制垂直線
???{
????if (!vline[loop1][loop2])???// 如果當(dāng)前線段沒有走過,則不填充
????{
?????filled=FALSE;????
????}
????glBegin(GL_LINES);????// 繪制當(dāng)前的線段
?????glVertex2d(20+(loop1*60),70+(loop2*40));?
?????glVertex2d(20+(loop1*60),110+(loop2*40));?
????glEnd();?????
???}
接下來我們檢測(cè)長(zhǎng)方形的四個(gè)邊是否都被走過,如果被走過我們就繪制一個(gè)帶紋理的四邊形。
我們用下圖來解釋這個(gè)檢測(cè)過程
如果對(duì)于垂直線vline的相鄰兩個(gè)邊都被走過,并且水平線hline的相鄰兩個(gè)邊也被走過,那么我們就可以繪制這個(gè)四邊形了。我們可以使用循環(huán)檢測(cè)每一個(gè)四邊形,代碼如下:?
???glEnable(GL_TEXTURE_2D);????// 使用紋理映射
???glColor3f(1.0f,1.0f,1.0f);????// 設(shè)置為白色
???glBindTexture(GL_TEXTURE_2D, texture[1]);??// 綁定紋理
???if ((loop1<10) && (loop2<10))????// 繪制走過的四邊形
???{
????// 這個(gè)四邊形是否被走過?
????if (hline[loop1][loop2] && hline[loop1][loop2+1] && vline[loop1][loop2] && vline[loop1+1][loop2])
????{
?????glBegin(GL_QUADS);???// 是,則繪制它
??????glTexCoord2f(float(loop1/10.0f)+0.1f,1.0f-(float(loop2/10.0f)));
??????glVertex2d(20+(loop1*60)+59,(70+loop2*40+1));?
??????glTexCoord2f(float(loop1/10.0f),1.0f-(float(loop2/10.0f)));
??????glVertex2d(20+(loop1*60)+1,(70+loop2*40+1));?
??????glTexCoord2f(float(loop1/10.0f),1.0f-(float(loop2/10.0f)+0.1f));
??????glVertex2d(20+(loop1*60)+1,(70+loop2*40)+39);?
??????glTexCoord2f(float(loop1/10.0f)+0.1f,1.0f-(float(loop2/10.0f)+0.1f));
??????glVertex2d(20+(loop1*60)+59,(70+loop2*40)+39);?
?????glEnd();????
????}
???}
???glDisable(GL_TEXTURE_2D);????
??}
?}
?glLineWidth(1.0f);???????
下面的代碼用來設(shè)置是否啟用直線反走樣?
?if (anti)????????// 是否啟用反走樣?
?{
??glEnable(GL_LINE_SMOOTH);?????
?}
為了使游戲變得簡(jiǎn)單些,我添加了一個(gè)時(shí)間停止器,當(dāng)你吃掉它時(shí),可以讓追擊的你的敵人停下來。
下面的代碼用來繪制一個(gè)時(shí)間停止器。
?if (hourglass.fx==1)???????
?{
??glLoadIdentity();??????
??glTranslatef(20.0f+(hourglass.x*60),70.0f+(hourglass.y*40),0.0f);?
??glRotatef(hourglass.spin,0.0f,0.0f,1.0f);???
??glColor3ub(rand()%255,rand()%255,rand()%255);???
??glBegin(GL_LINES);
???glVertex2d(-5,-5);?????
???glVertex2d( 5, 5);?????
???glVertex2d( 5,-5);?????
???glVertex2d(-5, 5);?????
???glVertex2d(-5, 5);?????
???glVertex2d( 5, 5);?????
???glVertex2d(-5,-5);?????
???glVertex2d( 5,-5);?????
??glEnd();???????
?}
接下來繪制我們玩家?
?glLoadIdentity();
?glTranslatef(player.fx+20.0f,player.fy+70.0f,0.0f);???// 設(shè)置玩家的位置
?glRotatef(player.spin,0.0f,0.0f,1.0f);?????// 旋轉(zhuǎn)動(dòng)畫
?glColor3f(0.0f,1.0f,0.0f);??????
?glBegin(GL_LINES);???????
??glVertex2d(-5,-5);??????
??glVertex2d( 5, 5);??????
??glVertex2d( 5,-5);??????
??glVertex2d(-5, 5);??????
?glEnd();????????
??
繪制玩家的顯示效果,讓它看起來更好看些(其實(shí)沒用)?
?glRotatef(player.spin*0.5f,0.0f,0.0f,1.0f);
?glColor3f(0.0f,0.75f,0.0f);??????
?glBegin(GL_LINES);???????
??glVertex2d(-7, 0);??????
??glVertex2d( 7, 0);??????
??glVertex2d( 0,-7);??????
??glVertex2d( 0, 7);??????
?glEnd();???????
??
接下來繪制追擊玩家的敵人?
?for (loop1=0; loop1<(stage*level); loop1++)
?{
??glLoadIdentity();??????
??glTranslatef(enemy[loop1].fx+20.0f,enemy[loop1].fy+70.0f,0.0f);
??glColor3f(1.0f,0.5f,0.5f);?????
??glBegin(GL_LINES);??????
???glVertex2d( 0,-7);?????
???glVertex2d(-7, 0);?????
???glVertex2d(-7, 0);?????
???glVertex2d( 0, 7);?????
???glVertex2d( 0, 7);?????
???glVertex2d( 7, 0);?????
???glVertex2d( 7, 0);?????
???glVertex2d( 0,-7);?????
??glEnd();???????
??
下面的代碼繪制敵人的顯示效果,讓其更好看。?
??glRotatef(enemy[loop1].spin,0.0f,0.0f,1.0f);???
??glColor3f(1.0f,0.0f,0.0f);?????
??glBegin(GL_LINES);??????
???glVertex2d(-7,-7);?????
???glVertex2d( 7, 7);?????
???glVertex2d(-7, 7);?????
???glVertex2d( 7,-7);?????
??glEnd();???????
?}
?return TRUE;????????
}
KillGLWindow函數(shù)基本沒有變化,只在最后一行添加KillFont函數(shù)?
??
GLvoid KillGLWindow(GLvoid)???????
{
?if (fullscreen)???????
?{
??ChangeDisplaySettings(NULL,0);?????
??ShowCursor(TRUE);??????
?}
?if (hRC)????????
?{
??if (!wglMakeCurrent(NULL,NULL))?????
??{
???MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
??}
??if (!wglDeleteContext(hRC))?????
??{
???MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
??}
??hRC=NULL;???????
?}
?if (hDC && !ReleaseDC(hWnd,hDC))?????
?{
??MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
??hDC=NULL;???????
?}
?if (hWnd && !DestroyWindow(hWnd))?????
?{
??MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
??hWnd=NULL;???????
?}
?if (!UnregisterClass("OpenGL",hInstance))????
?{
??MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
??hInstance=NULL;???????
?}
?KillFont();????????// 刪除創(chuàng)建的字體
}
函數(shù)CreateGLWindow() and WndProc() 沒有變化。
游戲控制在WinMain中完成的
?
int WINAPI WinMain(?HINSTANCE?hInstance,????
???HINSTANCE?hPrevInstance,????
???LPSTR??lpCmdLine,????
???int??nCmdShow)????
{
?MSG?msg;????????
?BOOL?done=FALSE;???????
?
?if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
?{
??fullscreen=FALSE;??????
?}
在創(chuàng)建完OpenGL窗口后,我們添加如下的代碼,它用來創(chuàng)建玩家和敵人,并初始化時(shí)間計(jì)時(shí)器?
??
?if (!CreateGLWindow("NeHe's Line Tutorial",640,480,16,fullscreen))?
?{
??return 0;???????
?}
?ResetObjects();????????// 重置玩家和敵人
?TimerInit();????????// 初始化時(shí)間計(jì)時(shí)器
?while(!done)????????
?{
??if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))???
??{
???if (msg.message==WM_QUIT)????
???{
????done=TRUE;?????
???}
???else???????
???{
????TranslateMessage(&msg);????
????DispatchMessage(&msg);????
???}
??}
??else????????
??{
接下來取得當(dāng)前的時(shí)間,并在速度快的機(jī)器上讓其空循環(huán),使得程序在所有的機(jī)器上都擁有同樣的幀率??
??
???float start=TimerGetTime();????// 返回當(dāng)前的時(shí)間
???if ((active && !DrawGLScene()) || keys[VK_ESCAPE])?
???{
????done=TRUE;?????
???}
???else???????
???{
????SwapBuffers(hDC);????
???}
???while(TimerGetTime()<start+float(steps[adjust]*2.0f)) {}// 速度快的機(jī)器上讓其空循環(huán)
下面的部分沒有改變,按F1執(zhí)行窗口和全屏的切換?
??
???if (keys[VK_F1])
???{
????keys[VK_F1]=FALSE;????
????KillGLWindow();?????
????fullscreen=!fullscreen;????
????if (!CreateGLWindow("NeHe's Line Tutorial",640,480,16,fullscreen))
????{
?????return 0;????
????}
???}
按A鍵切換是否啟用反走樣?
??
???if (keys['A'] && !ap)?????// 如果'A' 鍵被按下,啟用反走樣
???{
????ap=TRUE;?????
????anti=!anti;?????
???}
???if (!keys['A'])??????
???{
????ap=FALSE;?????
???}
如果游戲沒有結(jié)束,執(zhí)行游戲循環(huán)???
???if (!gameover && active)????// 如果游戲沒有結(jié)束,則進(jìn)行游戲循環(huán)
???{
????for (loop1=0; loop1<(stage*level); loop1++)?// 循環(huán)不同的難度等級(jí)
????{
根據(jù)玩家的位置,讓敵人追擊玩家?
?????if ((enemy[loop1].x<player.x) && (enemy[loop1].fy==enemy[loop1].y*40))
?????{
??????enemy[loop1].x++;??
?????}
?????if ((enemy[loop1].x>player.x) && (enemy[loop1].fy==enemy[loop1].y*40))
?????{
??????enemy[loop1].x--;??
?????}
?????if ((enemy[loop1].y<player.y) && (enemy[loop1].fx==enemy[loop1].x*60))
?????{
??????enemy[loop1].y++;??
?????}
?????if ((enemy[loop1].y>player.y) && (enemy[loop1].fx==enemy[loop1].x*60))
?????{
??????enemy[loop1].y--;??
?????}
如果時(shí)間停止器的顯示時(shí)間結(jié)束,而玩家又沒有吃到,那么重置計(jì)時(shí)計(jì)算器。?
?????if (delay>(3-level) && (hourglass.fx!=2))??// 如果沒有吃到時(shí)間停止器
?????{
??????delay=0;?????// 重置時(shí)間停止器
??????for (loop2=0; loop2<(stage*level); loop2++)?// 循環(huán)設(shè)置每個(gè)敵人的位置
??????{
下面的代碼調(diào)整每個(gè)敵人的位置,并繪制它們的顯示效果????
???????if (enemy[loop2].fx<enemy[loop2].x*60)?
???????{
????????enemy[loop2].fx+=steps[adjust];?
????????enemy[loop2].spin+=steps[adjust];?
???????}
???????if (enemy[loop2].fx>enemy[loop2].x*60)?
???????{
????????enemy[loop2].fx-=steps[adjust];?
????????enemy[loop2].spin-=steps[adjust];?
???????}
???????if (enemy[loop2].fy<enemy[loop2].y*40)?
???????{
????????enemy[loop2].fy+=steps[adjust];?
????????enemy[loop2].spin+=steps[adjust];?
???????}
???????if (enemy[loop2].fy>enemy[loop2].y*40)?
???????{
????????enemy[loop2].fy-=steps[adjust];?
????????enemy[loop2].spin-=steps[adjust];?
???????}
??????}
?????}
如果敵人的位置和玩家的位置相遇,這玩家死亡,開始新的一局?
?????// 敵人的位置和玩家的位置相遇?
?????if ((enemy[loop1].fx==player.fx) && (enemy[loop1].fy==player.fy))
?????{
??????lives--;???// 如果是,生命值減1
??????if (lives==0)???// 如果生命值為0,則游戲結(jié)束
??????{
???????gameover=TRUE;??
??????}
??????ResetObjects();???// 重置所有的游戲變量
??????PlaySound("Data/Die.wav", NULL, SND_SYNC);?// 播放死亡的音樂
?????}
????}
使用上,下,左,右控制玩家的位置?
????if (keys[VK_RIGHT] && (player.x<10) && (player.fx==player.x*60) && (player.fy==player.y*40))
????{
?????hline[player.x][player.y]=TRUE;??
?????player.x++;????
????}
????if (keys[VK_LEFT] && (player.x>0) && (player.fx==player.x*60) && (player.fy==player.y*40))
????{
?????player.x--;????
?????hline[player.x][player.y]=TRUE;??
????}
????if (keys[VK_DOWN] && (player.y<10) && (player.fx==player.x*60) && (player.fy==player.y*40))
????{
?????vline[player.x][player.y]=TRUE;??
?????player.y++;????
????}
????if (keys[VK_UP] && (player.y>0) && (player.fx==player.x*60) && (player.fy==player.y*40))
????{
?????player.y--;????
?????vline[player.x][player.y]=TRUE;??
????}
調(diào)整玩家的位置,讓動(dòng)畫看起來跟自然????
????if (player.fx<player.x*60)???
????{
?????player.fx+=steps[adjust];??
????}
????if (player.fx>player.x*60)???
????{
?????player.fx-=steps[adjust];??
????}
????if (player.fy<player.y*40)???
????{
?????player.fy+=steps[adjust];??
????}
????if (player.fy>player.y*40)???
????{
?????player.fy-=steps[adjust];??
????}
???}
如果游戲結(jié)束,按空格開始新的一局游戲?
???else???????// 如果游戲結(jié)束
???{
????if (keys[' '])?????// 按下空格?
????{
?????gameover=FALSE;????// 開始新的一局
?????filled=TRUE;????// 重置所有的變量
?????level=1;????
?????level2=1;???
?????stage=0;????
?????lives=5;????
????}
???}
如果順利通過本關(guān),播放通關(guān)音樂,并提高游戲難度,開始新的一局??
???if (filled)??????// 所有網(wǎng)格是否填滿
???{
????PlaySound("Data/Complete.wav", NULL, SND_SYNC);?// 播放過關(guān)音樂
????stage++;?????// 增加游戲難度
????if (stage>3)?????// 如果當(dāng)前的關(guān)卡大于3,則進(jìn)入到下一個(gè)大的關(guān)卡?
????{
?????stage=1;????// 重置當(dāng)前的關(guān)卡
?????level++;????// 增加大關(guān)卡的值
?????level2++;????
?????if (level>3)????
?????{
??????level=3;???// 如果大關(guān)卡大于3,則不再增加
??????lives++;???// 完成一局給玩家獎(jiǎng)勵(lì)一條生命
??????if (lives>5)???// 如果玩家有5條生命,則不再增加
??????{
???????lives=5;??
??????}
?????}
????}
進(jìn)入到下一關(guān)卡,重置所有的游戲變量?
????ResetObjects();?????
????for (loop1=0; loop1<11; loop1++)
????{
?????for (loop2=0; loop2<11; loop2++)?
?????{
??????if (loop1<10)???
??????{
???????hline[loop1][loop2]=FALSE;?
??????}
??????if (loop2<10)???
??????{
???????vline[loop1][loop2]=FALSE;?
??????}
?????}
????}
???}
如果玩家吃到時(shí)間停止器,記錄這一信息?
??
???if ((player.fx==hourglass.x*60) && (player.fy==hourglass.y*40) && (hourglass.fx==1))
???{
????// 播放一段聲音
????PlaySound("Data/freeze.wav", NULL, SND_ASYNC | SND_LOOP);
????hourglass.fx=2;?????// 設(shè)置fx為2,表示吃到時(shí)間停止器
????hourglass.fy=0;?????// 設(shè)置fy為0
???}
顯示玩家的動(dòng)畫效果?
???player.spin+=0.5f*steps[adjust];???// 旋轉(zhuǎn)動(dòng)畫
???if (player.spin>360.0f)?????
???{
????player.spin-=360;????
???}
顯示時(shí)間停止器的動(dòng)畫?
??
???hourglass.spin-=0.25f*steps[adjust];???// 旋轉(zhuǎn)動(dòng)畫
???if (hourglass.spin<0.0f)????
???{
????hourglass.spin+=360.0f;????
???}
下面的代碼計(jì)算何時(shí)出現(xiàn)一個(gè)時(shí)間停止計(jì)數(shù)器?
???hourglass.fy+=steps[adjust];????// 增加fy的值,當(dāng)他大于一定的時(shí)候,產(chǎn)生時(shí)間停止計(jì)數(shù)器
???if ((hourglass.fx==0) && (hourglass.fy>6000/level))?
???{???????
????PlaySound("Data/hourglass.wav", NULL, SND_ASYNC);?
????hourglass.x=rand()%10+1;???
????hourglass.y=rand()%11;????
????hourglass.fx=1;?????//fx=1表示時(shí)間停止器出現(xiàn)?????
????hourglass.fy=0;?????
???}
如果玩家沒有拾取時(shí)間停止器,則過一段時(shí)間后,它自動(dòng)消失?
???if ((hourglass.fx==1) && (hourglass.fy>6000/level))?
???{????
????hourglass.fx=0;?????// 消失后重置時(shí)間停止器
????hourglass.fy=0;?????
???}
如果玩家吃到時(shí)間停止器,在時(shí)間停止停止階段播放一段音樂,過一段時(shí)間停止播放音樂?
??
???if ((hourglass.fx==2) && (hourglass.fy>500+(500*level)))
???{???????
????PlaySound(NULL, NULL, 0);???// 停止播放音樂
????hourglass.fx=0;?????// 重置變量
????hourglass.fy=0;?????
???}
增加敵人的延遲計(jì)數(shù)器的值,這個(gè)值用來更新敵人的運(yùn)動(dòng)?
???delay++;??????// 增加敵人的延遲計(jì)數(shù)器的值
??}
?}
?// 關(guān)閉
?KillGLWindow();????????// 刪除窗口
?return (msg.wParam);???????// 退出程序
}
原文及其個(gè)版本源代碼下載:
http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=21
總結(jié)
以上是生活随笔為你收集整理的NeHe OpenGL教程 第二十一课:线的游戏的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: openresty 前端开发入门四之Re
- 下一篇: SVM学习(续)核函数 松弛变量和惩罚