|
第21课 |
|
|
线,反走样,计时,正投影和简单的声音:
这是我第一个大的教程,它将包括线,反走样,计时,正投影和简单的声音。希望这一课中的东西能让每个人感到高兴。 |
|
|
|
欢迎来到第21课,在这一课里,你将学会直线,反走样,正投影,计时,基本的音效和一个简单的游戏逻辑。希望这里的东西可以让你高兴。我花了两天的时间写代码,并用了两周的时间写这份HTML文件,希望你能享受我的劳动。
在这课的结尾你将获得一个叫"amidar"的游戏,你的任务是走完所有的直线。这个程序有了一个基本游戏的一切要素,关卡,生命值,声音和一个游戏道具。
我们从第一课的程序来逐步完整这个程序,按照惯例,我们只介绍改动的部分。 | |
#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保存了组成我们游戏网格垂直方向上的121条线,上下水平各11条。hline保存了水平方向上的
121条线,用ap来检查A键是否已经按下。
当网格被填满时, filled被设置为TRUE而反之则为FALSE。gameover这个变量的作用显而易见,当他的值为TRUE时,游戏结束。anti指出抗锯齿功能是否打开,当设置为TRUE时,该功能是打开着的。active
和 fullscreen 指出窗口是否被最小化以及游戏窗口是窗口模式还是全屏模式。 | |
bool keys[256];
bool vline[11][10];
bool hline[10][11];
bool ap;
bool filled;
bool gameover;
bool anti=TRUE;
bool active=TRUE;
bool fullscreen=TRUE;
|
接着设置整型变量。loop1 和 loop2 被用来检查网格,查看是否有敌人攻击我们,以及在网格上给对象一个随机的位置。你将看到loop1
/ loop2在后面的程序得到使用。delay 是一个计数器,我用他来减慢那些坏蛋的动作。当delay的值大于某一个馈值的时候,敌人才可以行动,此时delay将被重置。
adjust是一个非常特殊的变量,即使我们的程序拥有一个定时器,他也仅仅用来检查你的计算机是否运行地太快。如果是,则需要暂停一下以减慢运行速度。在我地GeForce显卡上,程序的运行平滑地简直变态,并且非常非常快。但是在我的PIII/450+Voodoo
3500TV上测试的时候,我注意到程序运行地非常缓慢。我发现问题在于关于时间控制那部分代码只能够用来减慢游戏进行而并不能加速之。因此我引入了一个叫做adjust
的变量。它可以是0到5之间的任何值。游戏中的对象移动速度的不同依赖于这个变量的值。值越小,运动越平滑;而值越大,则运动速度越快。这是在比较慢的机器上运行这个程序最简单有效的解决方案了。但是请注意,不管对象移动的速度有多快,游戏的速度都不会比我期望的更快。我们推荐把adjust值设置为3,这样在大部分机器上都有比较满意的效果。
我们把lives的值设置成5,这样我们的英雄一出场就拥有5条命。level是一个内部变量,用来指出当前游戏的难度。当然,这并不是你在屏幕上所看到的那个Level。变量level2开始的时候和Level拥有相同的值,但是随着你技能的提高,这个值也会增加。当你成功通过难度3之后,这个值也将在难度3上停止增加。level
是一个用来表示游戏难度的内部变量,stage才是用来记录当前游戏关卡的变量。 |
|
int loop1;
int loop2;
int delay;
int adjust=3;
int lives=5;
int level=1;
int level2=level;
int stage=1;
|
接下来我们需要一个结构来记录游戏中的对象。fx和fy每次在网格上移动我们的英雄和敌人一些较小的象素,以创建一个平滑的动画效果。x和y则记录着对象处于网格的那个交点上。
上下左右各有11个点,因此x和y可以是0到10之间的任意值。这也是我们为什么需要fx和fy的原因。考虑如果我们只能够在上下和左右方向的11个点间移动的话,我们的英雄不得不
在各个点间跳跃前进。这样显然是不够平滑美观的。
最后一个变量spin用来使对象在Z轴上旋转。 |
|
struct object
{
int fx, fy;
int x, y;
float spin;
};
|
既然我们已经为我们的玩家,敌人,甚至是秘密武器。设置了结构体,那么同样的,为了表现刚刚创设的结构体的功能和特性,我们也可以为此设置新的结构体。
为我们的玩家创设结构体之下的第一条直线。基本上我们将会为玩家提供fx,fy,x,y和spin值几种不同的结构体。通过增加这些直线,仅需查看玩家的x值我们就很容易取得玩家的位置,同时我们也可以通过增加玩家的旋转度来改变玩家的spin值。
第二条直线略有不同。因为同一屏幕我们可以同时拥有至多15个敌人。我们需要为每个敌人创造上面所提到的可变量。我们通过设置一个有15个敌人的组来实现这个目标,如第一个敌人的位置被设定为敌人(0).x.第二个敌人的位置为(1),x等等
第三条直线使得为宝物创设结构体实现了可能。宝物是一个会时不时在屏幕上出现的沙漏。我们需要通过沙漏来追踪x和y值。但是因为沙漏的位置是固定的所以我们不需要寻找最佳位置,而通过为程序后面的其他物品寻找好的可变量来实现(如fx和fy) |
|
struct object player;
struct object enemy[9];
struct object hourglass;
|
现在我们创建一个描述时间的结构,使用这个结构我们可以很轻松的跟踪时间变量。
接下来的第一步,就是创建一个64位的频率变量,它记录时间的频率。
resolution变量用来记录最小的时间间隔。
mm_timer_start和mm_timer_elapsed保存计时器开始时的时间和计时器开始后流失的时间。这两个变量只有当计算机不拥有performance
counter时才启用。
变量performance_timer用来标识计算机是否有performance counter
如果performance counter启用,最后两个变量用来保存计时器开始时的时间和计时器开始后流失的时间,它们比普通的根据精确。 | |
struct
{
__int64 frequency;
float resolution;
unsigned long mm_timer_start;
unsigned long mm_timer_elapsed;
bool performance_timer;
__int64 performance_timer_start;
__int64 performance_timer_elapsed;
} timer;
|
下一行代码定义了速度表。如前所说,对象移动的速度依赖于值adjust,而以adjust为下标去检索速度表,就可以获得对象的移动速度。 |
|
int steps[6]={ 1, 2, 4, 5, 10, 20 };
|
接下来我们将为纹理分配空间。纹理一共2张,一张是背景而另外一张是一张字体纹理。如本系列教程中的其他课程一样,base用来指出字符显示列表的基,同样的我们在最后声明了窗口过程WndProc()。 |
|
GLuint texture[2];
GLuint base;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
|
接下来会是很有趣的工作。接下来的一段代码会初始化我们的计时器。代码会检查performance
counter(非常精确的计数器)是否可用,如果不可用,则使用多媒体计时器。这段代码是可以移植的。 |
|
void TimerInit(void)
{
memset(&timer;, 0, sizeof(timer));
if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency;))
{
timer.performance_timer = FALSE;
timer.mm_timer_start = timeGetTime();
timer.resolution = 1.0f/1000.0f;
timer.frequency = 1000;
timer.mm_timer_elapsed = timer.mm_timer_start;
}
|
如果performance counter 可用,则执行下面的代码: |
|
else
{
QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance;_timer_start);
timer.performance_timer = TRUE;
timer.resolution = (float) (((double)1.0f)/((double)timer.frequency));
timer.performance_timer_elapsed = timer.performance_timer_start;
}
}
|
上面的代码设置了计时器,而下面的代码则读出计时器并返回已经经过的时间,以毫秒计。代码很简单,首先检查是否支持performance
counter,若支持,则调用其相关函数;否则调用多媒体函数。 |
|
float TimerGetTime()
{
__int64 time;
if (timer.performance_timer)
{
QueryPerformanceCounter((LARGE_INTEGER *) &time;);
return ( (float) ( time - timer.performance_timer_start) * timer.resolution)*1000.0f;
}
else
{
return( (float) ( timeGetTime() - timer.mm_timer_start) * timer.resolution)*1000.0f;
}
}
|
在下面的代码里,我们把玩家重置在屏幕的左上角,而给敌人设置一个随机的位置。 |
|
void ResetObjects(void)
{
player.x=0;
player.y=0;
player.fx=0;
player.fy=0;
|
接着我们给敌人一个随机的开始位置,敌人的数量等于难度乘上当前关卡号。记着,难度最大是3,而最多有3关。因此敌人最多有9个。 |
|
for (loop1=0; loop1<(stage*level); loop1++)
{
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;
}
}
|
并没有做任何改动,因此我将跳过它。在LoadGLTextures函数里我将载入那两个纹理--背景和字体。并且我会把这两副图都转化成纹理,这样我们就可以在游戏中使用他们。纹理创建好之后,象素数据就可以删除了。没有什么新东西,你可以阅读以前的课程以获得更多信息。
| |
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;
}
|
下面的代码建立了显示列表。对于字体的显示,我已经写过教程。在这里我把字体图象分成16×16个单元共256个字符。如果你有什么不明白,请参阅前面的教程 |
|
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();
}
}
|
当我们不再需要显示列表的时候,销毁它是一个好主意。在这里我仍然把代码加上了,虽然没有什么新东西。 |
|
GLvoid KillFont(GLvoid)
{
glDeleteLists(base,256);
}
|
函数没有做太多改变。唯一的改动是它可以打印变量了。我把代码列出这样你可以容易看到改动的地方。
请注意,在这里我激活了纹理并且重置了视图矩阵。如果set被置1的话,字体将被放大。我这样做是希望可以在屏幕上显示大一点的字符。在一切结束后,我会禁用纹理。 |
|
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);
}
|
下面的代码基本没有变化,只是把透视投影变为了正投影 |
|
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");
glColor3f(1.0f,1.0f,0.0f);
glPrint(20,20,1,"Level:%2i",level2);
glPrint(20,40,1,"Stage:%2i",stage);
|
现在我们检测游戏是否结束,如果游戏结束绘制"Gmae over"并提示玩家按空格键重新开始 |
|
if (gameover)
{
glColor3ub(rand()%255,rand()%255,rand()%255);
glPrint(472,20,1,"GAME OVER");
glPrint(456,40,1,"PRESS SPACE");
}
|
在屏幕的右上角绘制玩家的剩余生命 |
|
for (loop1=0; loop1<lives-1; loop1++)
{
glLoadIdentity();
glTranslatef(490+(loop1*40.0f),40.0f,0.0f);
glRotatef(-player.spin,0.0f,0.0f,1.0f);
glColor3f(0.0f,1.0f,0.0f);
glBegin(GL_LINES);
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();
}
|
下面我们来绘制网格,我们设置变量filled为TRUE,这告诉程序填充网格。
接着我们把线的宽度设置为2,并把线的颜色设置为蓝色,接着我们检测线断是否被走过,如果走过我们设置颜色为白色。
| |
filled=TRUE;
glLineWidth(2.0f);
glDisable(GL_LINE_SMOOTH);
glLoadIdentity();
for (loop1=0; loop1<11; loop1++)
{
for (loop2=0; loop2<11; loop2++)
{
glColor3f(0.0f,0.5f,1.0f);
if (hline[loop1][loop2])
{
glColor3f(1.0f,1.0f,1.0f);
}
if (loop1<10)
{
if (!hline[loop1][loop2])
{
filled=FALSE;
}
glBegin(GL_LINES);
glVertex2d(20+(loop1*60),70+(loop2*40));
glVertex2d(80+(loop1*60),70+(loop2*40));
glEnd();
}
|
下面的代码绘制垂直的线段 |
|
glColor3f(0.0f,0.5f,1.0f);
if (vline[loop1][loop2])
{
glColor3f(1.0f,1.0f,1.0f);
}
if (loop2<10)
{
if (!vline[loop1][loop2])
{
filled=FALSE;
}
glBegin(GL_LINES);
glVertex2d(20+(loop1*60),70+(loop2*40));
glVertex2d(20+(loop1*60),110+(loop2*40));
glEnd();
}
glEnable(GL_TEXTURE_2D);
glColor3f(1.0f,1.0f,1.0f);
glBindTexture(GL_TEXTURE_2D, texture[1]);
if ((loop1<10) && (loop2<10))
{
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);
|
下面的代码用来设置是否启用直线反走样 |
|
if (anti)
{
glEnable(GL_LINE_SMOOTH);
}
|
为了使游戏变得简单些,我添加了一个时间停止器,当你吃掉它时,可以让追击的你的敌人停下来。
下面的代码用来绘制一个时间停止器。 | |
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);
glRotatef(player.spin,0.0f,0.0f,1.0f);
glColor3f(0.0f,1.0f,0.0f);
glBegin(GL_LINES);
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();
|
接下来绘制追击玩家的敌人 |
|
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函数基本没有变化,只在最后一行添加KillFont函数 |
|
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();
}
|
函数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;
}
|
在创建完OpenGL窗口后,我们添加如下的代码,它用来创建玩家和敌人,并初始化时间计时器 |
|
if (!CreateGLWindow("NeHe's Line Tutorial",640,480,16,fullscreen))
{
return 0;
}
ResetObjects();
TimerInit();
while(!done)
{
if (PeekMessage(&msg;,NULL,0,0,PM_REMOVE))
{
if (msg.message==WM_QUIT)
{
done=TRUE;
}
else
{
TranslateMessage(&msg;);
DispatchMessage(&msg;);
}
}
else
{
|
接下来取得当前的时间,并在速度快的机器上让其空循环,使得程序在所有的机器上都拥有同样的帧率 |
|
float start=TimerGetTime();
if ((active && !DrawGLScene()) || keys[VK_ESCAPE])
{
done=TRUE;
}
else
{
SwapBuffers(hDC);
}
while(TimerGetTime()<start+float(steps[adjust]*2.0f)) {}
|
下面的部分没有改变,按F1执行窗口和全屏的切换 |
|
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)
{
ap=TRUE;
anti=!anti;
}
if (!keys['A'])
{
ap=FALSE;
}
|
如果游戏没有结束,执行游戏循环 |
|
if (!gameover && active)
{
for (loop1=0; loop1<(stage*level); loop1++)
{
|
根据玩家的位置,让敌人追击玩家 |
|
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--;
}
|
如果时间停止器的显示时间结束,而玩家又没有吃到,那么重置计时计算器。 |
|
if (delay>(3-level) && (hourglass.fx!=2))
{
delay=0;
for (loop2=0; loop2<(stage*level); loop2++)
{
|
下面的代码调整每个敌人的位置,并绘制它们的显示效果 |
|
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--;
if (lives==0)
{
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;
}
|
调整玩家的位置,让动画看起来跟自然 |
|
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];
}
}
|
如果游戏结束,按空格开始新的一局游戏 |
|
else
{
if (keys[' '])
{
gameover=FALSE;
filled=TRUE;
level=1;
level2=1;
stage=0;
lives=5;
}
}
|
如果顺利通过本关,播放通关音乐,并提高游戏难度,开始新的一局 |
|
if (filled)
{
PlaySound("Data/Complete.wav", NULL, SND_SYNC);
stage++;
if (stage>3)
{
stage=1;
level++;
level2++;
if (level>3)
{
level=3;
lives++;
if (lives>5)
{
lives=5;
}
}
}
|
进入到下一关卡,重置所有的游戏变量 |
|
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;
}
}
}
}
|
如果玩家吃到时间停止器,记录这一信息 |
|
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;
hourglass.fy=0;
}
|
显示玩家的动画效果 |
|
player.spin+=0.5f*steps[adjust];
if (player.spin>360.0f)
{
player.spin-=360;
}
|
显示时间停止器的动画 |
|
hourglass.spin-=0.25f*steps[adjust];
if (hourglass.spin<0.0f)
{
hourglass.spin+=360.0f;
}
|
下面的代码计算何时出现一个时间停止计数器 |
|
hourglass.fy+=steps[adjust];
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;
hourglass.fy=0;
}
|
如果玩家没有拾取时间停止器,则过一段时间后,它自动消失 |
|
if ((hourglass.fx==1) && (hourglass.fy>6000/level))
{
hourglass.fx=0;
hourglass.fy=0;
}
|
如果玩家吃到时间停止器,在时间停止停止阶段播放一段音乐,过一段时间停止播放音乐 |
|
if ((hourglass.fx==2) && (hourglass.fy>500+(500*level)))
{
PlaySound(NULL, NULL, 0);
hourglass.fx=0;
hourglass.fy=0;
}
| 增加敌人的延迟计数器的值,这个值用来更新敌人的运动
| |
delay++;
}
}
KillGLWindow();
return (msg.wParam);
}
|
我花了很长时间写这份教程,它开始于一个简单的直线教程,结束与一个小型的游戏。希望它能给你一些有用的信息,我知道你们中大部分喜欢那些基于“贴图”的游戏,但我觉得这些将教会你关于游戏更多的东西。如果你不同意我的看法,请让我知道,因为我想写最好的OpenGL教程。
请注意,这是一个很大的程序了。我尽量去注释每一行代码,我知道程序运行的一切细节,但把它表达出来又是另一回事。如果你有更好的表达能力,请告诉我如何更好的表达。我希望通过我们的努力,这份教程越来越好。谢谢
|
版权与使用声明:
我是个对学习和生活充满激情的普通男孩,在网络上我以DancingWind为昵称,我的联系方式是[email protected],如果你有任何问题,都可以联系我。
引子
网络是一个共享的资源,但我在自己的学习生涯中浪费大量的时间去搜索可用的资料,在现实生活中花费了大量的金钱和时间在书店中寻找资料,于是我给自己起了个昵称DancingWind,其意义是想风一样从各个知识的站点中吸取成长的养料。在飘荡了多年之后,我决定把自己收集的资料整理为一个统一的资源库。
版权声明
所有DancingWind发表的内容,大多都来自共享的资源,所以我没有资格把它们据为己有,或声称自己为这些资源作出了一点贡献。故任何人都可以复制,修改,重新发表,甚至以自己的名义发表,我都不会追究,但你在做以上事情的时候必须保证内容的完整性,给后来的人一个完整的教程。最后,任何人不能以这些资料的任何部分,谋取任何形式的报酬。
发展计划
在国外,很多资料都是很多人花费几年的时间慢慢积累起来的。如果任何人有兴趣与别人共享你的知识,我很欢迎你与我联系,但你必须同意我上面的声明。
感谢
感谢我的母亲一直以来对我的支持和在生活上的照顾。
感谢我深爱的女友田芹,一直以来默默的在精神上和生活中对我的支持,她甚至把买衣服的钱都用来给我买书了,她真的是我见过的最好的女孩,希望我能带给她幸福。
源码 RAR格式 |
| <
第20课 |
第22课
> |