结对作业 周杨威、吴原润
一、代码仓库项目地址
https://github.com/zhouyangwei123/PairingProject
二、设计历程
拿到任务后,我们首先准备做一款类似于“愤怒的小鸟”的小游戏。但是经过一段时间的尝试,功能实现情况不尽如人意。 这时和一位从事IT工作的朋友天点醒了我“有个词专门形容你们正做的事——Reinventing The Wheel,你们居然不用游戏引擎写游戏?”
我们一直困扰的问题终于迎刃而解——我们为什么觉得每个最简单的操作都举步维艰?为什么觉得自己的作品毫无竞争力(即便说出优点也生硬没有说服力)?因为考虑我们的知识储备,目标本身就不应该是完成一件画面或者操作手感多么优秀的作品,最重要是应该熟悉软件设计中的每个环节,熟练运用版本控制、代码测试等各种工具,之后再考虑努力在游戏性上做文章在,退一步说,通过“再造车轮”锻炼编程水平也算是这门课重要的收获。这样我们终于放下包袱,全心投入其中。
这时已经是提交作业前两天的晚上了,我们决定转变思路,做一款横版过关休闲小游戏。结合游戏性考虑,我们最终决定添随机障碍元素,进一步实现随即迷宫。可以用鼠标或者键盘控制人物移动,在不能碰壁的迷宫中寻找出路,人物移动是有惯性作用的,为游戏增加了难度。
三、需求分析
我们发觉,研究生阶段校园生活比较繁忙,并没有太多时间玩大型游戏,课业之余也常常没有精力玩烧脑的游戏。于是我们决定设计一款可以在碎片化的闲暇时间玩、不需要太多思考的横版小游戏。
对产品的功能性需求:
1、作为动作过关游戏,有良好的操作感,但没有复杂的键位、技能和需要背板的攻略。
2、地图、道具(待添加)等全随机,不容易厌烦。
3、游戏体积小、运行速度快、玩完就扔到一边。
4、有背景音乐(歌曲库)可以转换学习期间紧绷的心情。
四、设计实现过程
本程序建立三个额外CPP文件及对应H文件。 MAIN.cpp存放主函数。Model为输入控制、速度及坐标计算等。Painting为面板绘制、图片资源调用等函数,Others存放各种不好分类的函数。设计草稿及流程图如下:
功能的一步步实现:
1、首先实现了鼠标和键盘两种不同控制的方法。
2、加入了背景贴图
图五
3、实现了随机生成障碍和计算碰撞体积,碰壁则游戏结束。
图六
五、代码说明
选其中几个主要函数说明如下:
rand_p_block();
初始化随机障碍物坐标函数,调用rand()函数,赋值到坐标数组。
panel_init();
面板初始化,包括参数赋初值;调用easyx库创建画布、加载资源图片、调用背景绘制函数。
choose_input();
绘制模式选择界面和游戏说明。接受键盘输入,返回一个int型值。
contral_input(mod_flag);
调用非阻断函数接受键盘和鼠标输入,更新vx、vy值。
change_pos();
根据vx、vy乘以时间得出实时坐标并刷新显示。
draw_block_goal();
根据坐标值放置随机障碍物和终点,并检测碰撞。
int main() { //********** 初始化图形窗口********** panel_init(); //面板初始化 mod_flag = choose_input(); //控制模式选择 //*********************************** while(esc == 0) { contral_input(mod_flag); //读取鼠标或键盘输入,改变速度 limit_speed(); //检查限速 change_pos(); //位置改变坐标 draw_pos(); //位置显示刷新 Sleep(20); erase_pos(); //擦除旧图片 } // 关闭图形窗口,退出 closegraph(); return 0; }
//*********************初始化背景板********************** void panel_init(void) { int i,j; mod_flag = 0; esc = 0; px = 30; py = 30; initgraph(graph_X, graph_Y); //初始化画布 setcolor(WHITE); line(160, 240, 480, 240); loadimage(&img[0], _T("jpg"), MAKEINTRESOURCE(IDR_JPG1)); //飞行物 img 0 loadimage(&img[1], _T("jpg"), MAKEINTRESOURCE(IDR_JPG2)); //背景图 img 1 loadimage(&img[2], _T("jpg"), MAKEINTRESOURCE(IDR_JPG3)); //障碍物 img 2 loadimage(&img[3], _T("jpg"), MAKEINTRESOURCE(IDR_JPG4)); //按钮 img 3 loadimage(&img[4], _T("jpg"), MAKEINTRESOURCE(IDR_JPG5)); //终点 img 4 draw_background(); } //******************************************************* //***************显示飞行物位置,判断触边界,碰到则停止***************** void draw_pos(void) { draw_background(); if ((py < 5) || (py > 475)) //碰到上下边界 { vy = 0; gameover(); } if ((px < 5) || (px > 635)) //碰到左右边界 { vx = 0; gameover(); } setcolor(GREEN); putimage(px - 15, py - 15, &img[0]); } //******************************************************************** //************************* 擦除飞行物 ******************************* void erase_pos(void) { setcolor(BLACK); rectangle(px, py, px + 10, py + 10); } //******************************************************************** //****************** 生成随机障碍物坐标存入数组 ********************* void rand_p_block(void) { int i,j; srand((unsigned)time(NULL)); for (i = 0; i < block_num; i++) { p_block[0][i] = { rand() % 11 + 2 }; p_block[1][i] = { rand() % 8 + 2 }; } for (j = 0; j < goal_num; j++) { p_goal[0][j] = {13}; p_goal[1][j] = { rand() % 8 + 2 }; } } //*********************** 绘制地形方块 ************************ void draw_block_goal(void) { int k; for (k = 0; k < block_num; k++) { put_a_block (p_block[0][k] * 50, p_block[1][k] * 50); } for (k = 0; k < goal_num; k++) { put_a_goal(600, p_goal[1][k] * 50); } }
//************************* 输入模式选择 ************************* int choose_input(void) { int ch1 = 0; int ch2 = 0; char s1[] = "1 : press M -- mouse contral (click button) "; char s2[] = "2 : press K -- keyboard contral (W A S D)"; char s3[] = " tip : press SPACE -- STOP "; outtextxy(190, 170, s1); outtextxy(190, 210, s2); outtextxy(190, 300, s3); while (1) { ch1 = _getch(); if (ch1 == 77 || ch1 == 109) //if input M or m { cleardevice();//清屏 return 1; } else if (ch1 == 75 || ch1 == 107) //if input K or k { cleardevice();//清屏 return 2; } else if (ch1 == 27) { esc = 1; return 3; } } } //*************************************************************** //**************** 控制模块,读取鼠标或者键盘输入 *************** // 鼠标左键点击按钮区域 // 键盘使用 W A S D void contral_input(int flag) { //*********************鼠标模式************************ if (flag == 1) { //******* 支持esc退出,空格急停 ******* if (_kbhit()) { if (_getch() == 27) esc = 1; else if (_getch() == 32) { vx = 0; vy = 0; } else ; } //************************************** if (MouseHit())//左键按下触发 { MOUSEMSG m = GetMouseMsg(); switch (m.uMsg) { case WM_LBUTTONDOWN: if ((m.x > 100) && (m.x < 150) && (m.y < 330) && (m.y > 280)) //↑ { vy -= speed_val; } if ((m.x > 100) && (m.x < 150) && (m.y > 440) && (m.y < 470)) //↓ { vy += speed_val; } if ((m.x <= 220) && (m.x >= 170) && (m.y <= 400) && (m.y >= 350))//→ { vx += speed_val; } if ((m.x <= 80) && (m.x >= 30) && (m.y <= 420) && (m.y >= 350)) //← { vx -= speed_val; } break; } FlushMouseMsgBuffer(); } } //************************************************************* //**************************** 键盘 *************************** else if (flag == 2) { if (_kbhit()) //检测键盘有输入才进入判断,不影响运动流畅性 { switch (_getch()) { case 27 : esc = 1 ; //esc退出 case 119: vy -= speed_val; break; //↑ case 115: vy += speed_val; break; //↓ case 97 : vx -= speed_val; break; //← case 100: vx += speed_val; break; //→ case 32 : vx = 0; vy = 0; //急停 } } } } //*****************************************************************
//*************** 倒计时器 ******************** void countdown_timer(void) { char s[10] = { 0 }; sprintf_s(s, _T("%d"), limit_time); settextcolor(RED); outtextxy(20, 400, s); refresh_counter++; if ((refresh_counter * ref_time) >= 1000) //刷新次数 * 单次刷新时间 >= 1s { refresh_counter = 0; if (--limit_time == 0) win_flag = 0; else ; } else ; }
六、单元测试
1、建立单元测试工程文件,添加了资源附加依赖项, 编写单元测试程序
但由于本程序主要函数大量使用键鼠输入,并使用函数 _kbhit() 进行按键检测,模拟键盘输入的keybd_event()输入检测不到的问题没有解决,且easyx中的图像显示函数在单元测试中经常报错,单元检测未达到效果。
七、性能分析
本程序现阶段未加入复杂算法,电脑资源占用情况极低。
GPU使用情况 图八
CPU使用情况 图九
七、 PSP表格(估计值与真实值的区别)
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
|
|
· Estimate |
· 估计这个任务需要多少时间 |
1290 |
2370 |
Development |
开发 |
|
|
· Analysis |
· 需求分析 (包括学习新技术) |
60 |
60 |
· Design Spec |
· 生成设计文档 |
30 |
60 |
· Design Review |
· 设计复审 |
30 |
20 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
20 |
· Design |
· 具体设计 |
60 |
60 |
· Coding |
· 具体编码 |
800 |
1500 |
· Code Review |
· 代码复审 |
120 |
200 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
200 |
240 |
Reporting |
报告 |
|
|
· Test Repor |
· 测试报告 |
60 |
60 |
· Size Measurement |
· 计算工作量 |
30 |
30 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
60 |
60 |
|
合计 |
1680 |
2310 |
八、 软件工程学习日志
时间:2019-10-25晚 |
地点:寝室 |
人员:周杨威、吴原润 |
|
内容: 通过讨论,我们初步给出了我们的项目,就是做一款具有物理引擎的游戏。通过了解,我觉的周杨威的编程能力比我强,所以我们共同决定由周杨威主要负责代码的编写,我主要负责博客的编写。期间我们共同讨论内容和方案以及查找各种资料。 |
|
收获: 在一个团队项目里,合理的分工是非常重要的。并且还要勇于说出自己的想法,好的想法是在不同的idea之间碰撞产生的。 |
|
自我评价: 第一天我们对于自己的项目都很有激情,我相信我们会干好的。 |
时间:2019-10-29晚 |
地点:Z806 |
人员:周杨威、吴原润 |
|
内容: 我在网上查找资料,在CSDN上翻看各种有关博客。但收获甚微,对于其他的语言,我们没有时间临时学习。可能是我们查资料的能力有待提高,我们很难准确在这大量资料里找到我们想要的内容。所以在查到资料的基础上,我们只实现原定的部分功能。其余计划搁置。 |
|
收获: 如何利用网络去高效查找自己需要的资料是一件非常重要事情;要学会去利用网上的丰富资源;提出目标时,要考虑到自己的能力问题。 |
|
自我评价: 遇到困难有点泄气,效率有点不高。 |
时间:2019-10-31晚 |
地点:Z806 |
人员:周杨威、吴原润 |
|
内容: 考虑到原定目标无法实现,我们决定更改计划目标。为此我们展开热烈的讨论。我们尝试在现有实现的功能上找到新的方向。我们决定做一种避碰类的小游戏,控制人物在障碍物之间穿越。我们考虑设计不动障碍和运动障碍两种障碍类型。为此我们开始在网上查找,如何可以自动生成障碍物的代码。但考虑到时间紧任务重,我们决定首先通过手绘不动障碍实现游戏的功能实现。 |
|
收获: 有时遇到困难,可以换个思路绕过难关也未尝不可。 |
|
自我评价: 可能因为这是首次独立设计一款小游戏,总是不断的遇到困难,所以效率老是不高,总不能完成既定目标,有待加强。 |
时间:2019-11-2下午 |
地点:西区阅览室 |
人员:周杨威、吴原润 |
|
内容: 为了增加游戏的可玩性,我们想为什么不能动的障碍物变成迷宫的墙呢?走迷宫可比单纯的避碰有意思多了,首先我们把不动障碍物按照迷宫图纸摆成一个迷宫,通过控制任务在迷宫中行走。和避碰游戏最大的不同是,游戏人物在游戏中,触碰到迷宫壁不会使游戏停止。 |
|
收获: 灵感不是一下子迸发出来的,是在不断地实践中产生的。 |
|
自我评价: 革命尚未成功,同志仍需努力。 |
时间:2019-11-3 |
地点:西区阅览室 |
人员:周杨威、吴原润 |
|
内容: 今天我们的主要内容是找出一个可以自动生成迷宫的办法,我们在网上找到多种不同的迷宫生成代码。有些代码生成的迷宫根本没有出口,有些代码生成的迷宫难度无法控制,有些干脆无法运行。为此我对不同的代码进行反复的比较、修改、测试。 |
|
收获: 一串好的代码是在不断地比较、修改、测试中诞生的。 |
|
自我评价: 进过几次的磨合,我们的效率明显提高了。 |