游戏程序设计课程设计
学院:信息科学与技术学院
专业:软件工程
班级:09级软件(二)班
姓名:刘宇
学号:200905070201
摘要
娱乐一直是计算机的一大应用。在中国,随着网络的发展,休闲化游戏拥有了大量的玩家,且逐渐发展出棋牌类、音乐类、竞速类、体育类、格斗类和益智类等多种游戏类型。 最常见的竞速类游戏就是赛车游戏,玩家可从疾驰的速度中获得刺激感,而从高超技巧带来的流畅性操作中还可获取一定的满足感。3d赛车游戏是整个赛车游戏的重要组成部分,随着游戏行业的快速发展,2d游戏市场份额已经大幅降低
本文实现了一款赛车游戏,该游戏分为场景渲染,天空渲染,赛车的地形跟踪和碰撞检测等几个部分。场景渲染,和天空渲染等构成游戏的渲染画面,基于OPENGL技术实现。赛车在场景中的漫游和赛车的地形跟踪系统和碰撞检测构成了游戏的主要逻辑。
关键词: 3D, OpenGL, 游戏程序设计, 赛车游戏
引
言
课题背景及意义
当前随着计算机的深入普及,越来越多的人有了个人电脑,人们运用计算机完成许多重要的工作,计算机在人们的生活中变得越来越重要。随着计算机的应用和发展计算机已经不单纯是一个工作工具,人们越来越重视计算机的娱乐性。今年来游戏产业规模持续成长,全球每年游戏业总产值已经突破200亿美金,远远超越好莱坞电影产业以及音乐娱乐事业,而成为目前娱乐事业的最大主流。不过游戏产业光鲜亮丽的背后却隐含着高风险、高成本的危机,显示游戏产业慢慢步入微利时代。
在这种趋势下各种小型的低成本的游戏软件应运而生。这些小游戏都以益智和娱乐为目的,不仅给紧张工作的人们以轻松,还可以让人们的大脑得到开发。
关于本课题
使用游戏软件自然是为了满足人们对娱乐性的要求,本次课程设计是采用3D设计,对系统配置有一定的要求。
作为本系统的开发工具,Microsoft Visual C++成为首选。它具有可视化的编程界面、详细的提示、以及完善的帮助文档,使得软件开发人员感到无比的亲切感。另外,还需要配置OpenGL。
一、游戏的总体
与设计
1.1游戏的功能简介
通过仿真游戏地图和游戏场景,完成本系统后,能通过W、A、D、S键控制赛车行驶方向。
1.2系统设计
1.2.1模块与
规划
1.2.2系统流程图
初始化:这部分执行与其他任何程序类似的
操作,如内存分配,资源获取,从磁盘加载数据等;
进入游戏循环:这部分进入游戏循环,用户将在这里不断地执行动作,知道退出主循环为止;
接收玩家输入:这部分处理玩家输入,或将其存储到缓冲区,供游戏逻辑使用;
执行游戏逻辑:这部分包含游戏代码的主体部分,将执行人工智能,物理系
统和通用游戏逻辑,并根据结果在屏幕上绘制下一帧;
图形渲染:在这一部分中,讲根据玩家输入以及逻辑的执行结果,生成下一个游戏动画帧。
关闭:退出游戏;
1.2.3各类的设计
1)GGameMap
方法:(1)载入位图高程
static bool loadHightMap(const char *fileName, int **&ymap, int &w, int &h);
(2)构造地图单例对象
static GGameMap * create();
(3)释放地图单例对象
static void destroy();
(4)得到地图单例对象指针
static GGameMap * getCurrent()
(5)检测指定点与赛道是否发生碰撞
bool isCollision(double x, double z);
(6)清空数据结构, 释放资源
void clear();
(7)载入地图
bool load(const char *fileName, GTreeBillboard *pTreeBillboard);
Gpool
方法:(1)池塘构造函数, 传入池塘地面纹理和水波纹理
GPool(GTexture2D *pLandTex, GTexture2D *pWaterTex);
(2)绘制池塘
virtual void draw();
GSkyDome
方法:(1)初始化
GSkyDome(GTexture2D *pTex, double r=1000);
(2)绘制
virtual void draw();
GTree
方法:(1)从文件流中载入树木
bool load(FILE *fp);
(2)根据视点坐标更新旋转角度和距离
void update(double cx, double cz);
(3)绘制树木
virtual void draw();
(4)根据视点到树木的距离反比较树木对象指针
int gCompareTreePtr(const void *ptr1, const void *ptr2);
GCurvedRoad
方法:(1)得到旋转角
double getRotAngle()
(2)设置旋转角
void setRotAngle(double rot)
(3)绘制直赛道(调用前, 请先设置正确的旋转角度和顺时针标志)
virtual void draw();
GCar
方法:(1)判断是否刹车
bool isBreak()
(2)设置状态为刹车
void setBreak(bool isBreak)
(3)判断是否转向角
double getSteerAngle()
(4)设置转向角
void setSteerAngle(double af)
(5)绘制汽车
virtual void draw();
GDashBoard
方法:(1)速度变化, 更新指针角度
void changePointer(double v);
(2)绘制仪表盘
virtual void draw();
GMiniMap
方法:(1)绘制道路瓦片
void drawRoadTile(char typ);
(2)汽车的位置
void setCarPos(const GVector3d &pos);
(3)绘制迷你地图
virtual void draw();
详细设计
2.1 赛车地图资源的加载
赛车地图资源共分为平原高程,山地高程,池塘高程的加载。
在功能的实现主要是在GGameMap类中实现的。该类中使用load()方法用于对资源的加载。
2.2 天空渲染
天空的渲染主要是使用天空穹技术来实现的。天空穹是一个笼罩在整个赛场之上的半球,它会随着赛道的改变而发生变化,从而使人感觉一直都在场景之中。在三维赛车游戏中我们的视线一直是小于45度的,所以在绘制天空时不必将半圆形全部画上,只画在我们视线内的部分就足够。在绘制是主要使用三角网格技术,将没有顶盖的半球分解成若干个三角形,分别计算出每个三角形各定点的坐标和纹理坐标,再在显示列表中绘制这些三角形。
在程序当中,天空穹主要是用 GSkyDome类来实现的。该类继承于GShape类。在GSkyDome类有GTexture2D
*mpSkyTex// 天空纹理 double mRadius// 天空穹半径 double*mTexBuf// 纹理缓冲double
*mVertexBuf// 顶点缓冲 int mTriCount// 三角形数目 GLint
mDisplayList;// 显示列表一些数据成员 这样一些数据成员。同时还有一个draw()方法用来实现对天空穹的绘制。
2.3 赛车道路的绘制
赛道共分为2种分别为直道和弯道。直道的绘制相对比较简单只需要绘制一个矩形即可,而弯道要用三角网格技术来将赛道分解成许多三角形,弯赛道有四种类型,但是我们只需绘制其中一种,其他三种可以通过旋转绘制出来的那种类型来得到。由于在游戏当中并不能看到整个赛道,所以在绘制时只需要绘制部分可见的赛道即可。
直赛道的绘制是在GStraightRoad类当中完成的,该类同样继承于GShape类,在GStraightRoad的构造函数中实现了各个定点的坐标和纹理坐标的计算,最后在通过调用该类的draw()方法实现直赛道的绘制。
弯赛道的绘制是在GRoadCirque类中完成的,同样它也是继承于GShape类,它的构造函数实现了各个定点的坐标和纹理坐标的计算。最后再调用draw()方法绘制。
2.4 赛车模型的加载和绘制
汽车模型需要从文件读取,为了显示汽车轮胎的转动和是否刹车(尾灯的亮与不亮),车身与四个轮胎和尾灯要分别绘制。用load()读取汽车模型的文件,汽车也是用三角网格方法绘制。
在程序当中,使用GTriMesh类来实现赛车赛车模型的加载首先通过loadFromObj()方法对赛车模型的加载,同时使用GCar类对赛车模型进行绘画。
2.5 树木的绘制
树木的绘制是采用广告牌的方式进行绘制的。广告牌技术及无论赛车怎样旋转始终是图片与我们的视线锤子,即赛车旋转多少度,图片亦随之旋转相应的角度。树木图片加载后还要对其进行消影,对树木按与视点的远近进行排序,远处的树排在前面,绘制的时候最后绘制。
树木的绘制主要是在GTreeBillboard类中实现的,该类首先通过load()方法实现对树木图片的加载,其次使用 update()方法获得树木需要旋转的角度以及树木的距离,最后在调用draw()进行树木的绘画。
2.6 速度仪的绘制
汽车速度表盘的设计与实现:先绘制一个正方形表盘,再贴上红色纹理表示指针。指针要先平移后旋转,从而达到指针跟随汽车速度的增加与减少而在不同的位置绘制的效果。
速度仪的绘制是在GDashBoard中完成的,首先该类使用changePointer()方法获得在速度改变是指针需要旋转的角度,在使用draw()方法对速度表盘进行绘制。
2.7 迷你地图的绘制
迷你地图绘制先是绘制一个地图模型,然后在贴上纹理指针。最后根据汽车的移动计算出指针的移动速度。
迷你地图的绘制是在GMiniMap 类中实现的,该类首先通过drawRoadTile()方法绘制道路瓦片,然后在通过setCarPos()方法计算出指针一定速度和方向,最后使用draw()绘制迷你地图。
2.8 碰撞检测
碰撞检测主要是检测赛车与赛道之间的碰撞,即赛车与直赛道和玩赛道之间的碰撞检测。
直赛道检测时只需要查看汽车两边是否超出赛道的两边即可,而玩赛道检测则必须检测弯道的四种方式。
三、运行效果
四、
在完成本次课程设计的过程中遇到了很多的问题,其中的很多知识还没有了解和掌握。在本次课程设计中,同学给予了很多的帮助,使我对整个课程设计的思路有了总体的把握,并耐心的帮助我解决了许多问题,使我有了很大的收获。
附录(部分主要源代码)
1地图类的实现
GGameMap * GGameMap::create()
{
if(mpGameMap) destroy();
// 载入平原高程
if(!loadHightMap(PLAIN_MAP_FNAME, PLAIN_Y_MAP, CELL_COLS, CELL_ROWS))
{
return NULL;
}
// 载入山地高程
if(!loadHightMap(HILL_MAP_FNAME, HILL_Y_MAP, CELL_COLS, CELL_ROWS))
{
return NULL;
}
// 载入池塘高程
if(!loadHightMap(POOL_MAP_FNAME, POOL_Y_MAP, CELL_COLS, CELL_ROWS))
{
return NULL;
}
// 将池塘地面高度向下减去255
int i, j;
for(i=0; i
load(fp);
mTreeList[i] = pTree;
}
}
}
fclose(fp);
return true;
}
2.池塘类的实现
GPool::GPool(GTexture2D *pLandTex, GTexture2D *pWaterTex)
{
mpLand = new GLand(pLandTex, POOL_Y_MAP);
mpWaterTex = pWaterTex;
mFrameIdx = 0;
int i,j,k, it,nrow, ncol;
double s,x,z,y0,y1,ymax,deltAF,af,unitSize,offsetX,offsetZ;
nrow = 8; ncol = 8;
s =(double) CELL_ROWS /nrow;
ymax = 2.0;
deltAF = PI * 4/ nrow;
unitSize = s* UNIT_SIZE;
offsetX = -0.5*unitSize * ncol;
offsetZ = -0.5*unitSize * nrow;
mTriCount = nrow * ncol *2;
//分配缓存
for( i = 0; i<8; i++)
{
mVertexBufs[i] = new double[mTriCount * 9];
}
mTexBuf = new double[mTriCount * 6];
gltGenerateTexCoord(ncol,nrow,1.0/nrow,1.0/ncol,mTexBuf);
for(k = 0; k<8; k++)
{
af = k * PI * 0.25;
it = 0;
for(i = 0; i< nrow; i++)
{
z = offsetZ + i* unitSize;
for(j=0; juse();
glPushMatrix();
glTranslated(0, -4.0, 0);
glBegin(GL_TRIANGLES);
for(i=0; idraw();
glCallList(mDisplayLists[mFrameIdx]);
}
3.天空穹类的实现
GSkyDome::GSkyDome(GTexture2D *pTex, double r)
{
mpSkyTex = pTex;
mRadius = r;
int ic,it,nw,nh;
double step, v, h, vr,hr, sr;
step = 15; //角度间隔
sr = 15 * ANGLE_TO_RADIAN;
nw = (int) (360 / step);
nh = (int) (45 / step);
mTriCount = nw * nh * 2;
mVertexBuf = new double[mTriCount * 9];
mTexBuf
= new double[mTriCount * 6];
gltGenerateTexCoord(nw, nh, 1.0/nw, 1.0/nh, mTexBuf);
int i,j,k = 0;
for(i=0, v=0; iuse();
glBegin(GL_TRIANGLES);
for(i=0; imVDistance2 > p2->mVDistance2) return -1;
else if(p1->mVDistance2 < p2->mVDistance2) return 1;
else return 0;
}
5.直赛道的实现
GRoadCylinder::GRoadCylinder(GTexture2D *pTex, double length, double r,
double start, double over, int slices)
{
int j, k;
double x, span, angle;
mpTex = pTex;
mTriCount = slices * 1 * 2;
mVertexBuf = new double[mTriCount * 9];
mTexBuf = new double[mTriCount * 6];
gltGenerateTexCoord(1, slices, 1, 1.0/slices, mTexBuf);
x = length * 0.5;
start *= ANGLE_TO_RADIAN;
over *= ANGLE_TO_RADIAN;
span = (over-start) / slices;
k = 0;
for(j=0, angle=start; juse();
glBegin(GL_TRIANGLES);
for(i=0; iuse();
glBegin(GL_TRIANGLES);
for(i=0; iuse();
for(i=0; imRowCount; i++)
{
for(j=0; jmColCount; j++)
{
if(pMap->mRoadMap[i][j] >= 0)
{
glPushMatrix();
glTranslated(j+0.5, i+0.5, 0);
drawRoadTile(pMap->mRoadMap[i][j]);
glPopMatrix();
}
}
}
glEndList();
}
void GMiniMap::drawRoadTile(char typ)
{
if(typ < 0) return ;
glBegin(GL_QUADS);
glTexCoord2d(mTexBufs[typ][0], mTexBufs[typ][1]);
glVertex2d(-0.5, 0.5);
glTexCoord2d(mTexBufs[typ][2], mTexBufs[typ][3]);
glVertex2d(-0.5, -0.5);
glTexCoord2d(mTexBufs[typ][4], mTexBufs[typ][5]);
glVertex2d(0.5, -0.5);
glTexCoord2d(mTexBufs[typ][6], mTexBufs[typ][7]);
glVertex2d(0.5, 0.5);
glEnd();
}
void GMiniMap::draw()
{
glCallList(mDisplayList);
double x, z;
x = mCarPos.x() / X_SPAN;
z = mCarPos.z() / Z_SPAN;
glPushMatrix();
glTranslated(x, z, 1);
glDisable(GL_BLEND);
glColor3d(1, 0, 0);
glutSolidSphere(0.6, 16, 2);
glEnable(GL_BLEND);
glPopMatrix();
}
10.摄像机类的实现
GCamera::GCamera()
{
mProjectMode = gPerspective;
mPosition.set(0, 0, 1);
mForward.set(0, 0, -1);
mUp.set(0, 1, 0);
mZNear = 0.5;
mZFar = 2.5;
mFovy = 90;
mAspect = 1;
mLeft = -1;
mTop = 1;
mRight = 1;
mBottom = -1;
}
void GCamera::setViewVolume(double fovy, double aspect, double zNear, double zFar)
{
mZNear = zNear;
mZFar = zFar;
mFovy = fovy;
mAspect = aspect;
mTop = zNear*tan(fovy*ANGLE_TO_RADIAN/2);
mBottom = -mTop;
mRight = mTop*aspect;
mLeft = -mRight;
}
void GCamera::setViewVolume(double left, double right, double bottom, double top,
double zNear, double zFar)
{
mLeft = left;
mRight = right;
mBottom = bottom;
mTop = top;
mZNear = zNear;
mZFar = zFar;
mFovy = 2*atan2(top, zNear)*RADIAN_TO_ANGLE;
mAspect = (mRight-mLeft)/(mTop-mBottom);
}
void GCamera::project()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
double aspect = 1;
double scrw = mScrWidth >= 1 ? mScrWidth : 1;
double scrh = mScrHeight >= 1 ? mScrH