直接在第十一课C基础上修改,参考Nehe第21课。
本课未完成,请直接跳过
在这课的结尾你将获得一个叫"amidar"的游戏,你的任务是走完所有的直线。这个程序有了一个基本游戏的一切要素,关卡,生命值,声音和一个游戏道具。
按照惯例,我们只介绍改动的部分。
bool类型的变量,bVLine保存了组成我们游戏网格垂直方向上的121条线,上下水平各11条。bHLine保存了水平方向上的 121条线。
当网格被填满时, bGridFilled被设置为TRUE而反之则为FALSE。bGameOver这个变量的作用显而易见,当他的值为TRUE时,游戏结束。
bAntiAlias指出抗锯齿功能是否打开,当设置为TRUE时,该功能是打开着的。
BOOL bVLine[11][10];// 保存垂直方向的11根线条中,每根线条中的10段是否被走过
BOOL bHLine[10][11];// 保存水平方向的11根线条中,每根线条中的10段是否被走过
BOOL bGridFilled;//网格是否被填满
BOOL bGameOver;//游戏是否结束
BOOL bAntiAlias;//是否开启反走样
接着设置整型变量。
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才是用来记录当前游戏关卡的变量。
view类中添加变量
int delay;//敌人的暂停时间
int adjust;//调整显示的速度,初始值为3
int lives;//玩家的生命,初始值为5
int level;//内部游戏的等级,初始为1
int level2;//显示的游戏的等级,初始为=level
int stage;//游戏的关卡,初始为1
在View构造函数中初始化
adjust=3;
lives=5;
level=1;
level2=level;
stage=1;
创建对象类
接下来我们需要一个结构来记录游戏中的对象。
采用面向对象方法,建立一个类CArchObject,C=Class Arch=Archie,VC6.0中Insert|New Class,类类型选择Generic Class,输入类名
fx和fy每次在网格上移动我们的英雄和敌人一些较小的象素,以创建一个平滑的动画效果。
x和y则记录着对象处于网格的那个交点上。
上下左右各有11个点,因此x和y可以是0到10之间的任意值。这也是我们为什么需要fx和fy的原因。考虑如果我们只能够在上下和左右方向的11个点间移动的话,我们的英雄不得不在各个点间跳跃前进。这样显然是不够平滑美观的。
最后一个变量spin用来使对象在Z轴上旋转。
class CArchObject
{
public:
int fx;// 使移动变得平滑
int fy;
int x;//对象的当前位置
int y;
float spin;//旋转方向
CArchObject();
virtual ~CArchObject();
};
既然我们已经为我们的玩家,敌人,甚至是秘密武器设置了结构体,那么同样的,为了表现刚刚创设的结构体的功能和特性,我们也可以为此设置新的结构体。
为我们的玩家创设结构体之下的第一条直线。
基本上我们将会为玩家提供fx,fy,x,y和spin值几种不同的结构体。通过增加这些直线,仅需查看玩家的x值我们就很容易取得玩家的位置,同时我们也可以通过增加玩家的旋转度来改变玩家的spin值。
第二条直线略有不同。
因为同一屏幕我们可以同时拥有至多15个敌人。我们需要为每个敌人创造上面所提到的可变量。我们通过设置一个有15个敌人的组来实现这个目标,如第一个敌人的位置被设定为敌人(0).x.第二个敌人的位置为(1),x等等
第三条直线使得为宝物创设结构体实现了可能。宝物是一个会时不时在屏幕上出现的沙漏。我们需要通过沙漏来追踪x和y值。但是因为沙漏的位置是固定的所以我们不需要寻找最佳位置,而通过为程序后面的其他物品寻找好的可变量来实现(如fx和fy)
在doc.h中添加
#include "ArchObject.h"
并添加CArchObject变量
public:
CArchObject AOPlayer;//玩家信息
CArchObject AOEnemy[9];//最多9个敌人的信息
CArchObject AOHourGlass;//宝物信息 (中文翻译为沙漏)
创建时间类
现在我们创建一个描述时间的结构,使用这个结构我们可以很轻松的跟踪时间变量。
接下来的第一步,就是创建一个64位的频率变量,它记录时间的频率。
resolution变量用来记录最小的时间间隔。
mm_timer_start和mm_timer_elapsed保存计时器开始时的时间和计时器开始后流失的时间。这两个变量只有当计算机不拥有performance counter时才启用。
变量performance_timer用来标识计算机是否有performance counter
如果performance counter启用,最后两个变量用来保存计时器开始时的时间和计时器开始后流失的时间,它们比普通的根据精确。
同样添加到ArchObject的头文件和源文件中
添加初始化时间Init和计算运行时间函数GetTime
class CArchTimer
{
public:
__int64 frequency; //频率 64位整数
float resolution; //时间间隔
unsigned long mm_timer_start; // 多媒体计时器的开始时间
unsigned long mm_timer_elapsed; // 多媒体计时器的执行时间
bool performance_timer; // 使用Performance Timer?
__int64 performance_timer_start; // Performance Timer计时器的开始时间
__int64 performance_timer_elapsed; // Performance Timer计时器的执行时间
CArchTimer();
virtual ~CArchTimer();
};
void CArchTimer::Init()
{
// 检测Performance Counter是否可用,可用则创建
if (!QueryPerformanceFrequency((LARGE_INTEGER *) &this->frequency))
{
// 如果不可用
this->performance_timer = FALSE; // 设置Performance Timer为false
this->mm_timer_start = timeGetTime(); // 使用普通的计时器
this->resolution = 1.0f/1000.0f; // 设置单位为毫秒
this->frequency = 1000; // 设置频率为1000
this->mm_timer_elapsed = this->mm_timer_start; // 设置流失的时间为当前的时间
}
//如果performance counter 可用,则执行下面的代码:
else
{
// 使用Performance Counter计时器
QueryPerformanceCounter((LARGE_INTEGER *) &this->performance_timer_start);
this->performance_timer = TRUE; // 设置Performance Timer为TRUE
// 计算计时的精确度
this->resolution = (float) (((double)1.0f)/((double)this->frequency));
// 设置流失的时间为当前的时间
this->performance_timer_elapsed = this->performance_timer_start;
}
}
// 返回经过的时间,以毫秒为单位
float CArchTimer::GetTime()
{
__int64 time; // 使用64位的整数
if (this->performance_timer) // 是否使用Performance Timer计时器?
{
QueryPerformanceCounter((LARGE_INTEGER *) &time); // 返回当前的时间
// 返回时间差
return ( (float) ( time - this->performance_timer_start) * this->resolution)*1000.0f;
}
else
{
// 使用普通的计时器,返回时间差
return( (float) ( timeGetTime() - this->mm_timer_start) * this->resolution)*1000.0f;
}
}
下一行代码定义了速度表view。如前所说,对象移动的速度依赖于值adjust,而以adjust为下标去检索速度表,就可以获得对象的移动速度。
int steps[6]; // 用来调整显示的速度
steps[0]= 1;
steps[1]= 2;
steps[2]= 4;
steps[3]= 5;
steps[4]= 10;
steps[5]= 20;
接下来我们将为纹理分配空间。纹理共3张是背景(Nehe教程是2张,这并不影响学习的过程)。
GLuint texture[3]; // 保存1个纹理标志
定义计时器变量(doc类中已添加ArchObject头文件)
CArchTimer ATTimer;//计时器
在下面的代码里,我们把玩家重置在屏幕的左上角,而给敌人设置一个随机的位置。
// 重置玩家和敌人
void COpenglbaseDoc::ResetObjects()
{
AOPlayer.x=0;
AOPlayer.y=0;
AOPlayer.fx=0;
AOPlayer.fy=0;
// 接着我们给敌人一个随机的开始位置,敌人的数量等于难度乘上当前关卡号。
//记着,难度最大是3,而最多有3关。因此敌人最多有9个。
for (int loop=0; loop<(stage*level); loop++) // 循环随即放置所有的敌人
{
AOEnemy[loop].x=5+rand()%6;
AOEnemy[loop].y=rand()%11;
AOEnemy[loop].fx=AOEnemy[loop].x*60;
AOEnemy[loop].fy=AOEnemy[loop].y*40;
}
}
在LoadGLTextures函数里载入纹理--背景。
//
//加载纹理
//
int COpenglbaseView::LoadGLTextures()
{
int Status=FALSE;
AUX_RGBImageRec *TextureImage[1]; // 创建保存1个纹理的数据结构
memset(TextureImage,0,sizeof(void *)*1); // 初始化
if (TextureImage[0]=LoadBMP("Data/Image.bmp")) // 加载纹理0
{
Status=TRUE;
glGenTextures(1, &texture[0]); // 创建1个纹理,纹理存放首地址为&texture[0]
//创建 Nearest 滤波贴图.我们在 MIN 和 MAG 时都采用了GL_NEAREST,你可以混合使用 GL_NEAREST 和 GL_LINEAR.MIN_FILTER在图像绘制时小于贴图的原始尺寸时采用。MAG_FILTER在图像绘制时大于贴图的原始尺寸时采用。
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY,
0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
}
if (TextureImage[0]) // 纹理是否存在
{
if (TextureImage[0]->data) // 纹理图像是否存在
{
free(TextureImage[0]->data); // 释放纹理图像占用的内存
}
free(TextureImage[0]); // 释放图像结构
}
return Status;
}
正投影
下面的代码基本没有变化,只是把透视投影变为了正投影
OnSize
// gluPerspective(45, aspect, 0.1f, 100.0f);
glOrtho(0.0f,width,height,0.0f,-1.0f,1.0f);
绘制代码
首先我们清空缓存,接着绑定字体的纹理,绘制游戏的提示字符串,每一刻场景函数后加数字,如12课,为RenderScene12
BOOL COpenglbaseView::RenderScenc12()
{
COpenglbaseDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
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:2",m_pDC->GetSafeHdc()); // 绘制当前的级别
glPrint(20,40,1,"Stage:2"),m_pDC->GetSafeHdc(); // 绘制当前级别的关卡
// 现在我们检测游戏是否结束,如果游戏结束绘制"Gmae over"并提示玩家按空格键重新开始
if (bGameOver) // 游戏是否结束?
{
glColor3ub(rand()%255,rand()%255,rand()%255); // 随机选择一种颜色
glPrint(472,20,1,"GAME OVER",m_pDC->GetSafeHdc()); // 绘制 GAME OVER 字符串到屏幕
glPrint(456,40,1,"PRESS SPACE",m_pDC->GetSafeHdc()); // 提示玩家按空格键重新开始
}
// 在屏幕的右上角绘制玩家的剩余生命
int loop1=0;
int loop2=0;
for (loop1=0; loop1<lives-1; loop1++) //循环绘制玩家的剩余生命
{
glLoadIdentity();
glTranslatef(490+(loop1*40.0f),40.0f,0.0f); // 移动到屏幕右上角
glRotatef(-pDoc->AOPlayer.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(-pDoc->AOPlayer.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,并把线的颜色设置为蓝色,接着我们检测线断是否被走过,如果走过我们设置颜色为白色。
bGridFilled=TRUE; // 在测试前,把填充变量设置为TRUE
glLineWidth(2.0f); // 设置线宽为2.0f
glDisable(GL_LINE_SMOOTH); // 禁用反走样
glLoadIdentity();
for (loop1=0; loop1<11; loop1++) // 循环11根线
{
for (loop2=0; loop2<11; loop2++) // 循环每根线的线段
{
glColor3f(0.0f,0.5f,1.0f); // 设置线为蓝色
if (bHLine[loop1][loop2]) // 是否走过?
{
glColor3f(1.0f,1.0f,1.0f); // 是,设线为白色
}
if (loop1<10) // 绘制水平线
{
if (!bHLine[loop1][loop2]) // 如果当前线段没有走过,则不填充
{
bGridFilled=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 (bVLine[loop1][loop2]) // 是否走过
{
glColor3f(1.0f,1.0f,1.0f); // 是,设线为白色
}
if (loop2<10) // 绘制垂直线
{
if (!bVLine[loop1][loop2]) // 如果当前线段没有走过,则不填充
{
bGridFilled=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 (bHLine[loop1][loop2] && bHLine[loop1][loop2+1] &&
bVLine[loop1][loop2] && bVLine[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 (bAntiAlias) // 是否启用反走样?
{
glEnable(GL_LINE_SMOOTH);
}
// 为了使游戏变得简单些,我添加了一个时间停止器,当你吃掉它时,可以让追击的你的敌人停下来。
// 下面的代码用来绘制一个时间停止器。
if (pDoc->AOHourGlass.fx==1)
{
glLoadIdentity();
glTranslatef(20.0f+(pDoc->AOHourGlass.x*60),70.0f+(pDoc->AOHourGlass.y*40),0.0f);
glRotatef(pDoc->AOHourGlass.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(pDoc->AOPlayer.fx+20.0f,pDoc->AOPlayer.fy+70.0f,0.0f); // 设置玩家的位置
glRotatef(pDoc->AOPlayer.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(pDoc->AOPlayer.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(pDoc->AOEnemy[loop1].fx+20.0f,pDoc->AOEnemy[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(pDoc->AOEnemy[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;
}
在创建完OpenGL窗口后,我们添加如下的代码,它用来创建玩家和敌人,并初始化时间计时器