当前位置:文档之家› 飞机大战实训报告

飞机大战实训报告

程序设计综合实践

实习报告

学院名称信息科学与工程学院

专业班级软件工程2012-3

学生姓名程大川

学号201201051002

指导教师孙红梅

山东科技大学

一、实习题目:飞机大战游戏的设计与实现

二、实习时间:7周~ 8周实习地点:J13-128

三、实习任务:

1.基本功能要求:飞机大战游戏必须运用透明贴图、按键处理、定时控制、双缓冲技术等技术实现战机(玩家)和敌机(计算机)对战功能

2.扩展功能:在完成基本功能基础上可以增加双人对战、游戏积分制、声音效果、多种子弹类型、客机类型、游戏关卡设计、多种类型敌机、游戏中间结果存贮等功能

四、小组分工说明:此次实习项目,所有内容均由一人完成

五、实习成绩

六、指导教师对实习的评语:

指导教师(签章):

2014年月日

目录

1. 概述 (4)

1.1实训概述 (4)

1.2功能概述 (4)

2. 相关技术 (4)

2.1滚动背景技术 (4)

2.2透明贴图技术 (4)

2.3定时器技术 (4)

2.4双缓冲技术 (5)

2.5连续按键处理技术 (5)

2.6碰撞处理技术 (5)

2.7链表存储技术 (5)

2.8动态效果技术 (5)

3.需求分析 (6)

3.1功能需求分析 (6)

3.2数据需求分析 (6)

3.3行为需求分析 (7)

3.4其他需求 (7)

4. 总体设计与详细设计 (7)

4.1 系统模块划分 (7)

4.2 主要功能模块 (8)

4.2.1玩家控制模块设计图 (8)

4.2.2游戏逻辑模块设计图(包含关卡控制) (8)

4.2.3 图形显示模块设计(包含敌方生成模块) (10)

4.3 主要类图 (11)

4.4 软件结构设计体会 (11)

5. 编码实现 (11)

5.1编码规范 (11)

5.2代码组织策略 (12)

5.3关键技术代码实现 (12)

6.测试情况说明。 (20)

6.1主要模块测试情况 (20)

模块1、碰撞模块测试 (20)

模块2、滚动背景模块测试 (20)

模块3、关卡模块测试 (21)

6.2 主要功能测试情况 (21)

功能1、飞机控制功能测试 (21)

功能2、能量块吃取功能测试 (21)

功能3、BOSS功能测试 (21)

功能3、显示功能测试 (21)

7. 实训中遇到的主要问题及解决方法 (21)

8. 实训收获与体会 (22)

1. 概述

1.1实训概述

这次实训,目的以Window7 + VS 2013为环境,编写一个简单的飞机大战游戏,熟悉软件工程的思想,学习MFC编程的方法,加深对面向对象思想的理解

1.2功能概述

用户可以左右移动控制战机和发射子弹,子弹命中敌机会造成伤害,敌机被击毁可以获得积分,积分达到一定程度会出现BOSS,BOSS被击杀后即可进行下一关。

游戏中会不定期出现道具包,吃取不同的道具包会有不同的增益效果。

2. 相关技术

本项目涉及的几个主要技术介绍和简要实现方式如下:

2.1滚动背景技术

为了让游戏场景更加逼真,游戏节奏更加合理,可以通过让背景滚动来提高游戏的场景效应,实现这一技术时,可以将一张背景图片复制为三张,从上到下连续的拼接在一起,并将中间的图片进行垂直翻转,在输出图像时,每次只输出合成后图片的1/3部分,每次输出后,将位图中的纵坐标输出位置增加一个位移量,当输出位置到达图片的2/3部分时,将图片的纵坐标输出位置置为0,由于图片的起始位置和图片的2/3位置是一致,所以在背景图片的显示中,不会出现图片的卡顿和瞬移现象。

2.2透明贴图技术

在飞机大战项目中,需要将很多的BMP图像贴到画布上,结合逻辑处理完成对整个游戏的设计,为了实现更好的显示效果,使移动元素所用的图像更加贴合背景,则需要利用透明贴图技术,将BMP图像中的无关颜色做透明处理,实现方式为调用CDC中的TransparentBlt函数,并且利用函数的最后一个参数填写RGB信息,实现对对应颜色的透明化。

2.3定时器技术

为了实现屏幕的定时刷新功能和暂停功能,需要设置定时器,当设置好时间间隔时,每次经过一个设置的时间间隔,Windows都会向程序发送一个WM_TIMER,并且调用相关函数,程序执行OnTimer函数,并根据定时器编号执行相应的操作。实现方式为使用SetTimer 函数设置定时器标识和时间间隔。另外也可以利用定时器实现对连续按键的处理,使子弹发射和移动效果更加平滑合理。

2.4双缓冲技术

利用普通的绘制函数对项目中元素进行绘图时,由于绘图时机不一定连续,绘图效率不一定统一,会造成重影和闪屏的不良效果,为了解决这一问题,可以利用双缓冲技术,先将要绘制的每部分内容存储到内存中的一个虚拟的绘图设备中,当所有部分的内容都绘制完毕后,再一次性的将内存中的内容绘制到真实屏幕上,实现这一方式,可以在程序定义一个CDC指针变量和CBitmap指针向量实现,在构造函数中为两个指针分配内存空间,将所有的图像绘制完毕后,再利用GetDC函数取得真实窗口CDC指针和BitBlt函数将内存中的内容完整的拷贝至真实屏幕上。

2.5连续按键处理技术

在飞机大战项目中,需要根据键盘输入决定游戏中各种位置的改变和一些变量的内容,在利用战机发射子弹或者进行移动时,需要按下键盘的空格键或者方向键,在该项目中,如果利用MFC中提供的默认消息处理函数进行按键消息的处理,则会导致子弹发射频率过快,战机移动过快,甚至程序卡死等严重问题,所以应将按键消息处理放在OnTimer函数里,以达到平滑的移动和发射子弹效果

2.6碰撞处理技术

在该项目中,需要处理对象之间的各种碰撞,比如,战机子弹与敌机的碰撞、敌机子弹与战机的碰撞、敌机与战机的碰撞、子弹与客机的碰撞等等。在处理碰撞时,可以为每个对象设置一个与位图大小相同的矩形,矩形的位置与对应位图所在位置一直,当判断两个元素是否发生了碰撞时,只需要利用IntersectRect函数判断两个矩形是否相交即可。

2.7链表存储技术

一局游戏中会有很多的敌机,敌机子弹以及战机子弹,并且需要其进行添加、删除、遍历等操作,为了提高程序执行效率,选用链表存储这些类型,当需要生产一个敌机或者敌机子弹时,只需要在链表尾部添加一个对应的对象即可,时间复杂度为O(1),当敌机被击毁或者子弹失效时,只需要在链表中将对应位置的对象删除即可,时间复杂度为O(1),当绘图时,需要对所有的对象进行遍历,此时只需要从头至尾遍历一个链表即可,时间复杂度为O(n),所有的复杂度已经是最低,所以利用CList链表存储项目各种对象,是一个非常合理的选择。

2.8动态效果技术

在敌机被击毁时,需要显示敌机爆炸的画面,这个动态的画面是由一组静态图像连续绘制形成的,为了实现这一效果,可以利用MFC中的CImageList类,将一组由静态图像组成

的连续位图按照一定格式转化为CImageList对象,并为每个对象设置一个index,在敌机生命值为0时,根据index逐帧播放图像,实现敌机爆炸的动态效果。

3.需求分析

3.1功能需求分析

●飞机大战项目需要实现的主要功能如下:

●战机在用户的控制下可以自由移动,但不可以移动到窗体外。

●战机可以连续发射子弹,子弹的起始位置和战机位置有关。

●敌机在窗口顶部随机产生,产生后垂直向下移动。

●敌机可以发射子弹,每个敌机在一个发射时刻可以发射两枚子弹。

●客机在窗口顶部随机产生,产生后由极慢的速度向下移动。

●客机的大小十分庞大,远大于敌机和战机

●战机子弹由战机产生,垂直向上飞行,并且可以击中敌机和客机

●敌机子弹由敌机产生,垂直向下飞行,并且可以击中战机和客机

●游戏中会不定期出现道具包,飞机经过道具包会出现不同的效果

●击毁敌机可以获得一定的得分,得分到达一定数量会出现BOSS

●BOSS产生于顶部,不会向下移动,会被子弹击中,击毁后可通过关卡。

●通过关卡后会使游戏难度增加,通过所有关卡会通关,完成游戏。

3.2数据需求分析

飞机大战项目中所需要的数据有

3.3行为需求分析

飞机大战项目中的行为可以用下面的用例图表示

3.4其他需求

背景可以滚动

4. 总体设计与详细设计

(介绍游戏的功能模块划分,主要类图、状态图、活动图或顺序图)4.1 系统模块划分

4.2 主要功能模块

4.2.1玩家控制模块设计图

4.2.2游戏逻辑模块设计图(包含关卡控制)

4.2.3 图形显示模块设计(包含敌方生成模块)

4.3 主要类图

4.4 软件结构设计体会

软件结构设计部分是软件工程开发过程的重要部分,设计内容的好坏关系到整个软件的优劣,在这次飞机大战项目开发中,对结构设计中出现的种种问题以及解决方法,加深了对软件结构设计方法的理解,熟悉了对其的应用,从系统功能模块设计到主要功能设计,从类图到状态图,都是着软件开发的每一个步骤,从宏观和细节处展示了整个软件的体系结构,使整个软件更有层次感。

5. 编码实现

5.1编码规范

●在这次飞机大战设计开发项目中,涉及到的编码规范主要包括

●类、对象、变量、常量、函数的名称含义准确不臃肿

●类的命名必须以C开头。

●类的名称中的单词首字母大写。

●变量名称中的单词首字母大写。

●函数名称中的单词首字母大写

●对象、变量、常量的定义尽量定义在CPaperPlaneDoc里。

●函数定义在CPaperPlaneView里。

●对不易理解的部分进行注释

5.2代码组织策略

以MFC代码标准组织方式为主。

5.3关键技术代码实现

5.3.1双缓冲的实现

CDC* pMemDC;

CBitmap* pBitmap;

CPaperPlaneView::CPaperPlaneView()

{

// TODO: 在此处添加构造代码

pMemDC = new CDC();

pBitmap = new CBitmap();

}

DrawBG(pMemDC);

pDC->BitBlt(0, 0, 600, 600, pMemDC, 0, 0, SRCCOPY);

CPaperPlaneView::~CPaperPlaneView()

{

delete pMemDC;

delete pBitmap;

}

5.3.2透明贴图的实现

void CCloud::draw(CDC* pDC)

{

CDC memDC;

memDC.CreateCompatibleDC(pDC);

CBitmap bmpDraw;

bmpDraw.LoadBitmap(CloudBitmapNum);

memDC.SelectObject(&bmpDraw);

pDC->TransparentBlt(pos.x, pos.y, 80,50, &memDC, 0, 0, 80,50, RGB(255, 255, 255)); }

5.3.3滚动背景的实现

DrawBG(pMemDC);

void CPaperPlaneView::DrawBG(CDC* pDC)

{

CPaperPlaneDoc* pDoc = GetDocument();

ASSERT_V ALID(pDoc);

if (!pDoc)

return;

//*/

CBitmap bitmap;

bitmap.LoadBitmap(pDoc->BackGroundNum);

BITMAP BitMap;

bitmap.GetBitmap(&BitMap);

CDC dcMem;

dcMem.CreateCompatibleDC(pDC);

dcMem.SelectObject(&bitmap);

pDC->StretchBlt(0, 0, 600, 600, &dcMem, 0, pDoc->BGposY, 600, 600, SRCCOPY);

pDoc->BGposY -= 10;

if (pDoc->BGposY<0)

pDoc->BGposY = 1200;

}

5.3.4主要游戏逻辑

void CPaperPlaneView::OnTimer(UINT_PTR nIDEvent)

{

// TODO: 在此添加消息处理程序代码和/或调用默认值

CPaperPlaneDoc* pDoc = GetDocument();

ASSERT_V ALID(pDoc);

if (!pDoc)

return;

CDC* pDC = GetDC();

switch (nIDEvent)

{

case 1:

DrawBG(pMemDC);

CloudInPlane(pDoc->myPlane,pDoc->enemy);

FireInCloud(pDoc->FireList,pDoc->enemy);

WaterInPlane(pDoc->WaterList, pDoc->myPlane);

PackageInPlane(pDoc->package, pDoc->myPlane);

FireInBusiness(pDoc->FireList, pDoc->business);

DrawFire(pDoc->FireList);

DrawCloud(pDoc->enemy);

DrawWater(pDoc->WaterList);

pDoc->myPlane.Draw(pMemDC);

pDoc->package.Draw(pMemDC);

pDoc->business.Draw(pMemDC);

DrawTitle();

if (pDoc->PlaneScore >= pDoc->PassBase*100*(3*pDoc->NowLevel-2)+300){ KillTimer(1);

if (pDoc->NowLevel<2)

AfxMessageBox(L"恭喜你通过关卡,点击确定进入下一关", MB_OK);

SetLevel(++pDoc->NowLevel);

if (pDoc->NowLevel<=2)

SetTimer(1, 25, 0);

}

else if (pDoc->PlaneScore >= pDoc->PassBase *100* (3 * pDoc->NowLevel - 2) - 300 && pDoc->PlaneScorePassBase * 100*(3 * pDoc->NowLevel - 2)){

if (pDoc->boss.pos.y == -200)

killAllEnemy();

FireInBoss(pDoc->FireList, pDoc->boss);

pDoc->boss.Draw(pMemDC);

}

else if (pDoc->PlaneScore == pDoc->PassBase * 100*(3 * pDoc->NowLevel - 2)){ if (pDoc->boss.pos.y <= -200)

pDoc->PlaneScore = pDoc->PassBase * 100 * (3 * pDoc->NowLevel - 2) + 1000;

pDoc->boss.DrawUp(pMemDC);

}

if (pDoc->myPlane.life <= 0){

KillTimer(1);

if (AfxMessageBox(L"你输了,是否重新游戏", MB_YESNO) == 6){

pDoc->PlaneScore = 0;

SetLevel(pDoc->NowLevel=1);

SetTimer(1,25,0);

}

else

exit(1);

}

//GetKeyCommand();

if (GetAsyncKeyState('P')){

KillTimer(1);

SetTimer(2,300, 0);

}

if (GetAsyncKeyState(VK_UP)) pDoc->myPlane.up();

if (GetAsyncKeyState(VK_DOWN)) pDoc->myPlane.down();

if (GetAsyncKeyState(VK_LEFT)) pDoc->myPlane.left();

if (GetAsyncKeyState(VK_RIGHT)) pDoc->myPlane.right();

pDoc->TimerCnt++;

if (pDoc->TimerCnt % (40 / CFire::FireMakeSpeed) < 1)

if (GetAsyncKeyState(' ')){

pDoc->FireList.AddTail(CFire(pDoc->myPlane.pos + CPoint(25, -20)));

}

if (pDoc->TimerCnt % (40 / CCloud::CloudMakeSpeed) < 1)

if(pDoc->boss.pos.y <= -200){

pDoc->enemy.AddTail(CCloud());

rain(pDoc->enemy, pDoc->WaterList);

}

if (pDoc->TimerCnt>400&&pDoc->TimerCnt % (800 / CPackage::PackageMakeSpeed) < 1){

pDoc->package.pos.y = 0;

pDoc->package.PackType = rand() % 3 + 1;

}

if (pDoc->havePower > 0 && pDoc->TimerCnt - pDoc->havePower > 300)

CFire::FireMakeSpeed = 6;

if (pDoc->boss.pos.y > -200 && pDoc->TimerCnt % (40 / 1) < 1){

int base = rand() % 200;

for (int i = 0; i < 10; i++)

pDoc->WaterList.AddTail(CWater(CPoint(base+i * 20, 200)));

for (int i = 0; i < 10; i++)

pDoc->WaterList.AddTail(CWater(CPoint(base + 300 + i * 20, 200)));

}

if (pDoc->TimerCnt % 4000 < 1){

pDoc->business.pos.x = rand() % 450;

pDoc->business.pos.y = 0;

}

break;

case 2:

CBitmap bitmap;

bitmap.LoadBitmap(IDB_PAUSE);

BITMAP BitMap;

bitmap.GetBitmap(&BitMap);

CDC dcMem;

dcMem.CreateCompatibleDC(pMemDC);

dcMem.SelectObject(&bitmap);

pMemDC->StretchBlt(0, 0, 600, 600, &dcMem, 0, 0, 600, 600, SRCCOPY);

if (GetAsyncKeyState('P')){

KillTimer(2);

SetTimer(1,25,0);

}

if (GetAsyncKeyState('Q')){

KillTimer(2);

SetLevel(pDoc->NowLevel = 1);

SetTimer(1, 25, 0);

}

if (GetAsyncKeyState('E')){

KillTimer(2);

if (AfxMessageBox(L"确定退出游戏?", MB_YESNO) == 6)

exit(1);

else

SetTimer(2,300, 0);

}

}

pDC->BitBlt(0, 0, 600, 600, pMemDC, 0, 0, SRCCOPY);

ReleaseDC(pDC);

CView::OnTimer(nIDEvent);

}

5.3.5碰撞判断

int CPaperPlaneView::FireInCloud(CList& list, CList& enemy) {

CPaperPlaneDoc* pDoc = GetDocument();

ASSERT_V ALID(pDoc);

if (!pDoc) return 0;

POSITION pos = enemy.GetHeadPosition();

while (pos != NULL){

pDoc->PlaneScore+=enemy.GetNext(pos).beFired(list);

}

return 0;

}

void CPaperPlaneView::FireInBoss(CList& list, CBoss& boss)

{

CPaperPlaneDoc* pDoc = GetDocument();

ASSERT_V ALID(pDoc);

if (!pDoc) return ;

boss.beFired(list);

if (boss.life <= 0)

pDoc->PlaneScore = pDoc->PassBase * 100 * (3 * pDoc->NowLevel - 2);

return ;

}

int CPaperPlaneView::WaterInPlane(CList& list,CPlane& plane)

{

POSITION ps2,pos = list.GetHeadPosition();

while (pos != NULL){

ps2 = pos;

if (plane.beWatered(list.GetNext(pos)))

list.RemoveAt(ps2);

}

return 0;

}

int CPaperPlaneView::CloudInPlane(CPlane& plane, CList& enemy)

{

POSITION pos = enemy.GetHeadPosition();

while (pos != NULL){

CCloud& tmp = enemy.GetNext(pos);

CRect planeRect1, planeRect2, cloudRect, temRect;

planeRect1.SetRect(plane.pos + CPoint(10, 10), plane.pos + CPoint(60, 30));

planeRect2.SetRect(plane.pos + CPoint(0, 30), plane.pos + CPoint(70, 80));

cloudRect.SetRect(tmp.pos, tmp.pos + CPoint(80, 50));

if (plane.life>0 &&tmp.life>0&& (temRect.IntersectRect(cloudRect, planeRect1) || temRect.IntersectRect(cloudRect, planeRect2))){

plane.life -= tmp.damage;

tmp.life -= plane.damage;

return 1;

}

}

return 0;

}

5.3.6 链表遍历

int CPaperPlaneView::FireInCloud(CList& list, CList& enemy)

{

CPaperPlaneDoc* pDoc = GetDocument();

ASSERT_V ALID(pDoc);

if (!pDoc) return 0;

POSITION pos = enemy.GetHeadPosition();

while (pos != NULL){

pDoc->PlaneScore+=enemy.GetNext(pos).beFired(list);

}

return 0;

}

5.3.7 关卡设置

void CPaperPlaneView::SetLevel(int nLevel)

{

CPaperPlaneDoc* pDoc = GetDocument();

ASSERT_V ALID(pDoc);

if (!pDoc) return;

switch (nLevel)

{

case 1:

CPlane::PlaneLife = 6;

CPlane::PlaneDamage = 1;

CPlane::PlaneBitmapNum = IDB_PLANE1;

CCloud::CloudBitmapNum = IDB_CLOUD1;

CCloud::CloudBoommapNum = IDB_CLOUDBOOM1;

CCloud::CloudLife = 1;

CCloud::CloudDamage = 1;

CCloud::CloudMakeSpeed = 1;

CCloud::CloudMoveSpeed = 2;

CFire::FireMakeSpeed = 6; //1 - 9

CFire::FireMoveSpeed = 15;

CFire::FireDamage = 1;

CFire::FireBitmapNum = IDB_FIRE1;

CWater::WaterMakePossible = 40;

CWater::WaterMoveSpeed = 4;

CWater::WaterDamage = 1;

CWater::WaterBitmapNum = IDB_W ATER1;

CPackage::PackageMakeSpeed = 3;//every 20s

CPackage::PackageMoveSpeed = 2;

CBoss::BossBitmapNum = IDB_BOSS1;

pDoc->PassBase = 10;

pDoc->PlaneScore = 0;

pDoc->boss.pos.y = - 200;

pDoc->TimerCnt = 0;

pDoc->BGposY = 0;

pDoc->WaterList.RemoveAll();

pDoc->enemy.RemoveAll();

pDoc->FireList.RemoveAll();

pDoc->myPlane.init();

pDoc->NowLevel = 1;

pDoc->havePower = 0;

pDoc->package.PackType = rand() % 3 + 1;

pDoc->BackGroundNum = IDB_BACKGROUND1;

pDoc->boss.life = 10;

break;

case 2:

CPlane::PlaneLife = 6;

CPlane::PlaneDamage = 1;

CPlane::PlaneBitmapNum = IDB_PLANE2;

CCloud::CloudBitmapNum = IDB_CLOUD2;

CCloud::CloudBoommapNum = IDB_CLOUDBOOM1;

CCloud::CloudLife = 2;

CCloud::CloudDamage = 1;

CCloud::CloudMakeSpeed = 1;

CCloud::CloudMoveSpeed = 2;

CFire::FireMakeSpeed = 6; //1 - 40

CFire::FireMoveSpeed = 15;

CFire::FireDamage = 1;

CFire::FireBitmapNum = IDB_FIRE2;

CWater::WaterMakePossible = 60;

CWater::WaterMoveSpeed = 3;

CWater::WaterDamage = 1;

CWater::WaterBitmapNum = IDB_W ATER2;

CPackage::PackageMakeSpeed = 3;//every 20s

CPackage::PackageMoveSpeed = 2;

CBoss::BossBitmapNum = IDB_BOSS1;

pDoc->TimerCnt = 0;

pDoc->PassBase = 10;

pDoc->BGposY = 0;

pDoc->WaterList.RemoveAll();

pDoc->enemy.RemoveAll();

pDoc->FireList.RemoveAll();

pDoc->myPlane.init();

pDoc->havePower = 0;

pDoc->package.PackType = rand() % 3 + 1;

pDoc->BackGroundNum = IDB_BACKGROUND2;

pDoc->NowLevel = 2;

pDoc->boss.pos.y = -200;

pDoc->boss.life = 3;

break;

default:

KillTimer(1);

AfxMessageBox(L"恭喜你,通关了!", MB_OK);

SetTimer(2,300, 0);

break;

}

}

5.3.8 子弹生成

void CPaperPlaneView::rain(CList& enemy, CList& list) {

POSITION pos = enemy.GetHeadPosition();

POSITION ps2;

while (pos != NULL){

ps2 = pos;

CCloud& tmp = enemy.GetNext(pos);

if (tmp.pos.y > 600){

enemy.RemoveAt(ps2);

}

else{

if (rand() % 100 < CWater::WaterMakePossible)

tmp.rain(list);

}

}

}

void CCloud::rain(CList& list)

{

list.AddTail(CWater(CPoint(pos.x + 13, pos.y + 40)));

list.AddTail(CWater(CPoint(pos.x + 53, pos.y + 40)));

}

6.测试情况说明。

6.1主要模块测试情况

模块1、碰撞模块测试

模块2、滚动背景模块测试

相关主题
文本预览
相关文档 最新文档