Skip to content

Latest commit

 

History

History
439 lines (270 loc) · 26.2 KB

程序设计A课程实践项目报告 (修改2).md

File metadata and controls

439 lines (270 loc) · 26.2 KB

程序设计A课程实践项目报告

小组成员:刘隆邦、毕铭昊、蔡金潮

指导老师:李卫海

摘要

本项目运用面向对象的设计架构,使用C++语言和SDL库设计实现了一款塔防类游戏,供玩家在娱乐中感受程序设计带来的魅力。在项目完成的过程中,小组成员通力协作,克服困难,各方面的能力得到了显著提升。

关键词:塔防游戏,游戏设计,面向对象,C++,SDL

目录

[TOC]

项目介绍

项目概况

本项目为塔防类游戏设计,基于C++语言开发,使用SDL开发库。

项目分工

刘隆邦:游戏逻辑、敌人运动路径实现、塔攻击实现、框架搭建、模块测试

毕铭昊:人机交互接口、游戏菜单、界面优化

蔡金潮:项目报告整理、优化设计

项目目的

本项目旨在强化小组成员对C语言和C++语言工具的掌握与认知,提升小组成员对相关编程软件的熟练度,增强小组成员提出问题并解决问题的能力、成员间沟通与协作的能力;同时向玩家展示程序设计的魅力,激发玩家了解学习程序设计的相关语言及方法的兴趣。

项目目标

设计实现一款可玩性强、容易上手、界面友好的游戏。

项目内容

作为塔防类游戏,程序将引导玩家构筑防御体系,攻击从固定入口逐波出现并沿固定路径移动的敌人,以阻止敌人从固定出口逃逸并对玩家造成伤害。玩家需要选择合适的策略,充分利用有限资源在指定位置合理建塔拦截敌人,并利用打击敌人获得的资源完善防御体系直至失败或赢得胜利。

游戏将给予玩家若干难度选项,玩家可以通过设置敌人波数、每波敌人数目、敌人出现的时间间隔及敌人的移动速度来调整游戏的难度等级,从而获得更好的体验。

游戏功能/玩法

游戏场景中有固定的路径,每隔一定时间将有一定数量的敌人从入口进入,沿路径移动。玩家可在路径两侧指定位置建立多种攻击塔,攻击塔可对敌人产生伤害。游戏目标为阻止敌人通过路径从出口离开。

游戏可以选择不同的难度等级,敌人的出现间隔时间与速度以及游戏胜利目标的要求将随游戏难度进行调整。

概要设计

系统总体设计

塔防游戏主要由以下几部分组成

  • 游戏菜单(难度选择,开始,暂停,结束)

    image-20191222201119081image-20191222201241748image-20191222201423471image-20191222202350607

  • 地图(建塔位置、敌人移动路径)

    map_demo
  • 塔(类型,费用,攻击方式,攻击伤害)

    image-20191222202758703image-20191222202829203image-20191222203123207

  • 敌人(类型,移动速度,生命值)

    image-20191222203208416

  • 难度(间隔时间,敌人数)

  • 参数(生命值,金币数)

    image-20191222203336796

主要数据结构

class Game;
class Tower;
class Enemy;
class Bullet;
class Shell;

模块定义与接口

全局(Game)

class Game {
public:
    Game();
    void Restart();//重置
    void Enemy_Add(Enemy_Type n);//添加敌人
    void Enemy_Wave();//刷新敌人
    void Tower_Build();//建塔
    void Render();//显示
    void Tower_init();//塔的初始化
    void Detect();//侦测状态

    unsigned char Enemy_Num = 0;//敌人数量
    int Tower_Num = 0;//塔的数量
    unsigned char Bullet_Num = 0;//魔法塔数量
    unsigned char Shell_Num = 0;//炮塔数量

    Enemy *Enemy_Array[256];//敌人
    Tower *Tower_Array[Tower_point];//塔基
    Bullet *Bullet_Array[256];//魔法塔
    Shell *Shell_Array[256];//炮塔

    int money;//金钱数
    int life;//生命值
    int wave;//敌人波数
    int wavenum;//敌人总波数
private:
    int Interval = 2000;//间隔时间
};

塔基(Tower)

class Tower {
public:
    Tower(int n);
    void attack();//攻击
    void buildTower(TowerType);  //建塔
    void render();//显示刷新
    void Mtower_Init();//魔法塔初始化
    void Ctower_Init();//炮塔初始化
    TowerType type;//塔类型
    int tower_level;//塔等级
    Enemy **target;//攻击目标
    int attackX,attackY;//射击中心
    bool build_menu=false;//建塔菜单
    SDL_Point Center;//位置
private:
    int state;//状态
};

敌人(Enemy)

class Enemy {
public:
    Enemy(int n);
    float VEL;//移动速度
    void move(struct circle Circle);  //移动
    void move_control();//移动控制
    void render();  //显示刷新
    virtual void death();  //死亡操作
    bool overline(State state);//越线检测
    int Health;//生命值
    SDL_Rect Pos;//位置
private:
    int circle_num = 0;//存在区域
    bool in_circle = false;//判断存在区域
    bool exiting_circle = false;//存在区域状态
    bool flat_overed = false;//敌人自己有没有经过半圆的线
    double judgement = 0;
protected:
    double VelX, VelY=0;//分速度
    int life_cost=1;//死亡代价
};

魔法塔子弹(Bullet)

class Bullet {
public:
    Bullet(Enemy **tar, int x, int y);
    float VEL;//攻击速度    
    void render();//显示刷新
    void move();//攻击移动
    void nearest();//侦测目标
    bool exist = true;//存在状态
private:
    Enemy **ptarget;//目标
    Enemy *target;//目标
    SDL_Point Center;//位置
    double VelX,VelY;//分速度
};

炮弹(Shell)

class Shell {
public:
    Shell(Enemy **tar, int x, int y);
    float VEL;//攻击速度    
    void render();//显示刷新
    void move();//攻击移动
    void nearest();//侦测目标
    bool exist = true;//存在状态
private:
    Enemy **ptarget;//目标
    Enemy *target;//目标
    double VelX,VelY;//分速度    
    float angle=0;//炮弹旋转角度
    SDL_Point Center;//位置 
    SDL_Point Cen; //炮弹旋转中心
};

相关模块分别置于Game.h、tower.h、enemy.h、bullet.h、shell.h等头文件中。/*相关代码需要实时更新*/

开发语言和开发工具

本游戏使用C++语言开发,使用SDL开发库。

利用C++面向对象特性,可以将游戏逻辑封装于一个函数内,便于管理,也便于代码的复用。

SDL库是一个有一定历史的C/C++库,可胜任基本的图形界面显示与动画,基本满足开发需求。

主要使用了以下库文件:

#include <iostream>
#include <cmath>//数学函数
#include <SDL.h>//交互功能
#include <SDL_image.h>//显示

游戏开发采用CLion作为IDE,Clion在自动补全功能方面优势明显,让代码编写的工作变得更加流畅。

系统详细设计

主体功能设计

main模块

主要放置进程控制逻辑以及“游戏逻辑”无关的函数

首先是教程提供的init()函数用于初始化SDL, 以及close() 函数用于销毁窗口。

随后是载入单个图片文件地址为texture的函数loadTexture(),载入图片集合(就是字符串数组)为texture数组的loadTextures(),以及将所有需要载入的单个texture载入操作放在一起的loadSingle(),将所有需要载入的texture数组载入操作放在一起的loadArray().

WinMain()

主要处理玩家操作,实现人机交互功能。

整个主函数大致为3个状态,程序开始时SDL初始化,随后载入各种图形,启动主窗口,进入开始界面选择难度循环,侦测事件为点击右上“X”,Esc键,还有鼠标选择难度。选择的难度对game实例的金钱数,生命值,游戏速度,以及刷怪间距都有影响。随后进入游戏循环,除了侦测上述两个退出信号外,主要检测鼠标点击位置,并执行游戏逻辑刷新函数。鼠标可以与防御塔点交互,还可以提前刷新下一波,或者按暂停。第三状态是暂停状态,正常暂停或者胜利失败都会触发,暂停显示暂停键,胜利失败显示重来或者退出按钮。重来时调用restart()函数删除所有对象,重置所有变量实现再次初始化。

与其配套的main.h则集中了几乎所有的文件路径字符串,texture对象(数组)变量,以及图片应该放置的位置定位。另有全局变量game实例创建,由于最开始设计理念有偏差,全局变量还有Quit,pause,menu_open等标志位标识游戏状态。

game_logic模块

只有一个cpp文件,放置了游戏的刷新函数,数字的显示函数,与胜负的显示函数。

//以下模块均围绕一个C++类进行

Game模块

加载了一个game类,封装游戏逻辑所需的所有函数并提供对象管理功能。具体如上面模块部分介绍。

tower模块,bullet模块,enemy模块等等,都如上面函数及注释介绍。//

技术难点与解决方案

在开发过程中,由于是接触一个崭新的领域,我们早已预料到会遇到许多困难,好在最终都有了解决方案。

以下列出的,是还能回忆起来的诸多问题的解决过程

SDL库的选定

最开始并不是选用SDL库,而是搜索C++图形界面后安装了QT,但在社团大佬的提醒下意识到QT是应用界面开发库,不十分适用于游戏开发。于是在开始计划一周后更改为SDL,首先的环境安装便需要很多时间,而且一位组员至今没能以链接动态库的方式编译成功,对团队开发有着不小的打击。

SDL库的正常工作

起初采取的方式是复制教程的代码,并逐渐修改成自己的。然而在一次将常量分离到头文件后,程序无法运行,在main函数breakpoint后发现程序运行时根本没有进入main函数,无法查证哪个代码出了问题。在耗费近3个小时后决定重新以教程为模板重新修改,结果发现在main.cpp中的一个图像数组如果移到main.h中,就会在main函数运行前闪退。虽然现在早已不再使用这个数组,问题原因也没有找到,但当时的心情仍然是十分兴奋的。

敌人运动动画

最开始教程告诉我们的方法只能显示静止的图片,完全无法运动,更不用说动画了。而如果我们让一个对象运动并渲染图像到屏幕上,那么运动时一定会留下之前的影子无法清除。最终我们发现应当使用整屏重绘的方案,先显示地图和敌人,然后敌人图和整个地图一起清除,地图重绘,敌人重绘,才能让敌军运动起来。那么敌人不可能平移,移动一帧要换一副图。于是使用一个texture对象的指针数组,在enemy类里内置一个变量对图循环访问,最终实现了跃动前进的效果。

图片寻找

由于预先已经确定要复刻一个王国保卫战一样的塔防,于是我们的图片也是借用的原版。然而要从两万多张图片中找到目标图片实在不易,无奈之下只得在真实游戏中截图,用PS裁剪下来保存。当然在这方面毕铭昊同学做了主要工作。

image-20191222203446180

塔及相关坐标确定

在PS中测量好建塔位置(是未建塔之前图标右上角的位置)之后,对于第一个魔法塔,需要确定修建塔之后坐标的位置,塔中法师的位置和发弹点的位置,主要是使用相对运算的方式,测量好建塔之后图标相对于建塔之前图标右上角的位置,然后用加减法算法师位置和发弹点位置,发出建塔命令后开始计算并储存这些量,以便渲染图片时位置用。

image-20191222203708171

塔,敌军,子弹对象管理

这三个对象在游戏过程中有着各自的工作,塔要检测敌人,敌人要前进,子弹要跟踪敌人位置,那么就需要有一个连续的访问方式,使每步动作都能以循环的方式实现。tower数组长度为7,代表7个塔。敌军和子弹的长度均为256,开始时值初始化为nullptr(都是指针数组),随后每产生一个对象,会查询空指针位置,并分配一个对象的地址给过来。而平常的行为动作则需要轮番遍历数组,对每一个对象进行操作。

子弹类的建构/子弹跟踪方式/防御塔选定目标为离终点最近的敌军

子弹是由防御塔发出,到敌军截止,并对敌军造成影响的对象,而首先要确定的就是子弹从哪个防御塔发出。为此,防御塔有自己的检测半径(即射程),检测半径内有敌军,则可以发射子弹,那么子弹到底打谁?最开始我们认为只要是正在远离塔并且离塔最远的,就可以选定为目标。

但是后来发现当一个塔处于拐弯处时,并不会锁定最前的敌人。于是我们想到用路程来计量,谁走过的路程多攻击谁,最后成功实现。

子弹/敌军的销毁

子弹打中了应当消失,而敌军生命值为0或者为负也应当消失,敌军有死亡动画,这些消失的对象应当释放内存,将位置留给下一个同类对象。所以敌军的消失方式是设置一个标志位标明是否为死亡状态,如果是则开始顺序显示死亡动画,随后将自己在数组中的记录值为nullptr,释放自己的内存delete this。子弹没有消失动画,于是直接在move运动函数中判断是否已经不存在了。

子弹飞到图外,随后游戏非正常退出

这是当时游戏中最严重的bug,起因是子弹跟踪的对象提前被其他攻击干掉之后,敌人执行了自毁命令,而子弹一直储存目标对象的地址,还不知道对象已经消失。所以子弹访问到的敌军位置是一个随机数据,子弹会向那个虚拟坐标飞过去,到位置之后还会让目标的Hp减10,由于此时该内存无写权限,造成非法访问。思前想后,我们决定给子弹的跟踪加一个限制条件,如果跟踪坐标在地图之外,则原地销毁(随后还进行了一个改进,子弹会自动寻找离自己最近的敌军,并选定为目标,当然这样做的后果是子弹的运动可能远远超过防御塔的范围)

敌军沿路线前进

map_demo image-20191222114341219

这可能是游戏中核心的技术难点,起初我们的最低要求是走折线,但是肯定与地图的实际情况不符。同时我们发现地图的转弯处是一个标准的圆,应该用圆模拟路径。随后想起数学课上提及过微分画圆法,用微分方程中的速度关系,推导出下一步的方向。在随后的实验中,终于实现了转圆圈的方案。随后发现不仅仅是转圈,还需要转圈与走直线的转换。于是敌人的运动又分为两个状态,一个是沿直线前进,一个是转圈,并设置状态标志位in_circle。状态之间的转换使用直线方程来判定,具体就是在地图上用直线画出直线与圆的交界,得到直线方程,带入敌人当前坐标,储存值,如果刚好压线或者过线,这次带入方程的结果与上次的乘积就会小于等于0,从而触发状态的改变,即敌人由直线改为圆弧或者由圆弧改成直线。同样需要一个数组储存所有圆心以及附带的两条分界线。所以路径信息由下图所示:

image-20191222120109464

(圆弧要规定敌人是顺时针还是逆时针转,随后是圆心坐标。实际测试发现圆弧并不围绕这里的圆心转,但转出来的的确是圆弧,这是因为速度不能无限小。所以圆心坐标需要多次调试。随后是直线方程aX + bY + c = 0中a和b的值两对,c由圆心坐标带入确定。最后是是否超过平角,如果超过跳过一次出圈检验)

击中死亡敌军后游戏卡退

上次改动后,我们仍然发现敌军死亡时,还有概率直接退出游戏,甚至飞弹问题并没有得到圆满解决,于是我们想如果能在访问目标敌人之前先检查一下是否存在就好了。而目标不存在的唯一判据就是数组中的nullptr,所以必须通过访问原数组才能够知道自己的目标已经没有记录了。原先的数组是储存一堆enemy对象指针的,于是我们需要改变策略,使用enemy对象二级指针指向数组的地址,这样在访问之前就可以先访问指针储存的位置,如果是nullptr,就找下一个目标。

自动换目标

主要是通过遍历所有的敌军对象,看哪个最近,重新选定目标,而全屏没有目标时就可以销毁自身。

一次修正着弹点的意外收获

最开始子弹击中敌人的判定,是距离敌人图片右上角一定距离(一般与子弹设定的速度有关)就可以生效,但是这样在运行时看来就是有时候打中了子弹消失,有时候还差一定距离才能打中子弹却已经判定击中了。于是我起初想将子弹消失之前最后一帧图片定格在与敌人右上角重合。然而显示函数是从敌人图片显示那里粘贴过来的,参数没有改全,结果一显示,反而是个比敌人大一圈的蓝色球。顿时感觉这还不错,就作为特效保留了下来。image-20191222204520145

选塔菜单

侦测对塔位置的点击,如果没有塔就显示菜单,选择两种防御塔,点击其他位置会关闭。同样使用PS定位以及相对定位方式完成。

image-20191222204616488image-20191222204632120

敌军开始速度快,转弯时慢下来

这是最后一个奇怪的bug,发现速度越大,差距越明显,最终通过变量跟踪发现,开始速度忘了开根号。

开发进程

image-20191222105601071

此为git维护的各版本更新与合并的一部分记录。

bug测试小记

使用gdb调试工具,配合clion变量显示,解决各种一般性错误。有时还需要使用监测点手段,甚至需要将变量赋值给全局变量才能监测。在调试路径点时,不得不使用条件断点与文件定位结合,将程序停止在需要的位置。git版本回溯功能也帮了许多忙。

系统测试概述

系统测试环节伴随开发过程。我们选择不同的参数调节游戏难度,分别进行测试,并从中选择出适宜的参数,以增加游戏的可玩性和策略性。在测试过程中引入了随机参数以增加游戏的趣味性。 资源占用情况:

  • CPU :2.60GHz 15%占用率

  • RAM:35MB~40MB

  • Intel集显:开始菜单和defeat会占用100%,其余时间1%~3%

“下一步计划”

这里写下未完成的心愿。

首先是计划赋予敌军特殊能力,比如某种敌军可以为一定半径范围内的同伴增加防御,或者移动速度,或者回复血量,还可以专门吸收防御塔子弹。同样作为防御塔也可以加入特殊能力,比如靠近一定半径减速,或者施加debuff,还可以在特效上改良,比如能不能产生一种攻击气泡,将敌人送上天。。。当然更长远一些的计划包括如何在2D平面绘制3D的抛物线投影,这样便可以复制真正的炮塔了。还有近战体系,支援技能体系的建立。只有游戏完成到这一步才能使塔防类游戏具备真正的可玩性。

也许由于逻辑思路上的选择问题,即使有抛体防御塔,也无法预判敌人下一步的行动,尤其是在拐弯的时候。真的要完成这一动画,或许路径点的逻辑层面要大改。

当然,我们知道如果完善程序的细节,工作量最大的还是素材的提供,对于一些动画的分帧图,顺序是极难辨别的,位置的确定也要费一番功夫,可能大多数的时间成本都投入在素材的处理与显示上,与程序逻辑本身并无多大关系。

结论和体会

A:经过以上各阶段的开发,塔防游戏的模型建设基本完成。由于塔防游戏较类似于STG类游戏不同,为使玩家获得更多的参与度需要投入更多的特效与动画开发。然而一来素材的编辑是一种非编程的重复性工作,二来为了完成动画特效需要更多的时间,所以我们的开发截止于游戏模型的构建。但是在这样一个粗浅的项目的建构中,我们也收获了很多,从开发环境的搭建,动态库的安装,到版本控制的应用,再到成员间代码的协作方式,从一无所知到略知一二,为以后的程序建构打下了基础。同时,我们更加意识到程序结构的重要性,整个代码体系按照结构划分为7个.cpp与7个.h,分别模块化管理,方可各司其职。即使如此,还是遇到了找函数或者变量缓慢的问题,这可以归结为对函数的命名没有记忆,一直在找。同样也因为没有统一一个命名规范,虽然命了有意义的名称,还是不便于寻找。当然,代码结构设计还是有一定的成功之处的,比如类的复用继承理念极大的节省了新对象的开发时间,数据,结构与逻辑分离方便了逻辑分析与变量修改,这些是在分析他人代码并总结应用得到的经验。当然还锻炼了我们的debug跟踪能力,以及对IDE的熟练程度。总之,合作愉快。

B:在短短几个星期的课程实践中,我们小组的成员都经历了很多,收获了很多。项目开始的挫折锻炼了我们的沟通能力和协作能力,项目中期构建对象的困难让我们意识到顶层设计的必要性。尽管碰到了许多问题,也曾一度怀疑想要放弃,但当最后作品完成的那一刹那,我们心中的成就感和自豪感油然而生。在解决困难回顾总结的过程中,我们的编程能力得到了显著提升。

参考文献

贾伯琪、顾为兵、张四海等. 计算机程序设计:C语言版[M]. 北京:机械工业出版社,2011

Stephen Prata. C Primer Plus[M]. 北京:人民邮电出版社,2016

刘汝佳. 算法竞赛入门经典(第二版)[M]. 北京:清华大学出版社,2014

Lazy Foo'. Lazy Foo' Productions - Beginning Game Programming v2.0[DB/OL].http://lazyfoo.net/tutorials/SDL/index.php,2019.6.24

satanzw. Qt版本-塔防游戏实现一[DB/OL].https://blog.csdn.net/satanzw/article/details/10418063,2013.8.29

satanzw. Qt版本-塔防游戏实现二[DB/OL].https://blog.csdn.net/satanzw/article/details/10441375,2013.8.28

satanzw. Qt版本-塔防游戏实现三[DB/OL].https://blog.csdn.net/satanzw/article/details/10476669,2013.8.29

南杉(Necromanov).【塔防分析】塔防游戏的形成以及基本乐趣[DB/OL].http://news.4399.com/cltf/xinde/m/620800.html,2016.4.12

相关链接

SDL中文教程 - 游戏编程入门.http://tjumyk.github.io/sdl-tutorial-cn (https://kelvmiao.info/sdl-tutorial-cn),2014.2.17

C++ reference.http://www.cppreference.com,2019.9.29

C++参考手册.https://zh.cppreference.com,2019.9.30

MinGW.http://www.mingw.org

CLion.http://www.jetbrains.com/clion

Defense-Design.https://github.com/psi-cmd/Defense-Design,2019.11.21

致谢

感谢李卫海老师对程序设计相关知识以及项目任务分配等方面的指导,是您给了我们这个机会展示自己的才华锻炼自己的能力;

感谢陈鸿洛助教和王超助教对相关软件的安装和系统配置的协助和指导,感谢吴昊杰同学对软件配置问题提供的帮助,我们作品的完成也有你们出的一份力;

感谢USTC Linux User Group(LUG)为相关软件的下载和使用提供的指导和支持,让我们的工作变得更加容易;(包括但不限于给予函数库的建议,帮助配置动态库,询问相关的策略等)

感谢JetBrains公司免费提供项目实现过程中使用的 C/C++ 跨平台集成开发环境 Clion,感谢GitHub提供的分布式代码托管平台,这为我们作品的完成和优化提供了很大的便利;(包括但不限于debug变量查看,git集成,文件比对等提升开发效率的工具)

感谢其他小组的成员,你们精美高端的作品是我们努力优化完善项目的最大动力;

感谢我们的家长的支持和鼓励,冬日里你们无私地给予我们温暖,助力我们以更好的状态迎接挑战和机遇;

感谢我们强大的祖国,让我们能怀着自豪,幸福并感激着在安全和平的环境里把代码编辑;

感谢为本项目的完成与完善直接或间接提供了帮助的所有人,请你们看,我们没有辜负你们的鼓励。