`

【面向对象】小游戏“终结者”程序的设计与实现

 
阅读更多

08年9月入学,12年7月毕业,结束了我在软件学院愉快丰富的大学生活。此系列是对四年专业课程学习的回顾,索引参见:http://blog.csdn.net/xiaowei_cqu/article/details/7747205


面向对象程序设计

这是我们学习程序设计的第一课。我也在这门课上第一次接触程语言、写代码。我们以影印版的《C++ Program Design : An Introduction to Programming and Object-Oriented Design》为教材。


现在想来那段“入门”的经历真是痛苦不堪,很多概念难以理解,就只能一点点把书中的代码一遍遍的敲。但每次跑出一点小东西也都觉着兴奋到不行。大一末的时候有个“大”的课程设计:两人一组编写一个小游戏。我和小琦一组,写了一个比较简略的闯关游戏,这次经历之后才真的对编程有了“入门”的感觉。


小游戏"YingMu"


【游戏功能需求说明】

本游戏是基于日本漫画《灌篮高手》而设计的,相信大家对游戏中的人物都相当熟悉。在游戏中我们采用了键盘上、下、左、右控制玩家的移动,空格键发射子弹。如果玩家碰到敌人,则游戏结束;消灭所有敌人,则通关。游戏共分为两关,每一关的地图是随机产生的,敌人的移动速度也逐渐加快。虽然功能看似简单,但其中乐趣无穷,是一款集娱乐、冒险为一体的游戏。
我们这次是在Microsoft Visual C++ 6.0, EzWindow library的开发环境下设计完成的,一些程序的功能我们一时没有想到好的方法来实现,所以在这款游戏中没有呈现给大家。相信在以后的学习中,我们能更好地掌握并对这款小游戏进行升级更新。

【游戏总类图】




【游戏中的关键类】

Player

enum Floor{FLoor1=0,Floor2,Floor3,Floor4};

class Player {
	public:
		//constructor
		Player(SimpleWindow &w);
	public:
    	//inspectors
	    SimpleWindow& GetWindow() const;//得到玩家所在窗口
		Position GetPosition() const;  //得到玩家当前位置
		Direction GetDirection() const;  //得到玩家当前的方向
		BitMap& GetBmp(const Direction &d,int i); //得到玩家在相应的方向及步子上位图
		const BitMap& GetBmp(const Direction &d,int i) const; 
		Floor GetFloor()const;  //得到玩家当前所在层数
		bool IsDying();   //检查玩家是否死掉
		bool AtRightEdge() const;    //检查玩家是否走到窗口边缘
		bool AtLeftEdge() const;

	    //Facilitators
		void Create();  //创建玩家(即将玩家“放”在游戏窗口中)
		void Kill();    //“杀死”玩家(从窗口中擦掉)
		void Move();    //使玩家在键盘操控下做相应的移动
		void Fire();    //发射子弹
		void OKUp();    //设置玩家是否可以在层之间跳跃
		void OKDown();
		void CannotUpDown();

        //mutators
		void SetPosition(const Position &p); //设置玩家当前所有位图的位置 
		void SetFloor(Floor &f);  //玩家跳跃之后改变层的数据成员
		void SetDirection(const Direction &d); //设置玩家当前的方向
	
		//data member 
		vector<Bullet*> bullets;   /****************************************/
			                       //方便GameController中检查子弹和敌人     //
		                           //状况,所以放在public域中,设置为可见   //
                                   /****************************************/
	private:
		//facilities
		void Draw();
		void Erase();  /****************************************************/
		               //用户不直接操纵图片,而是通过调用 Creat() 和 Kill() //
                       //所以定义为 private                                 //
		               /*****************************************************/
		// Data members
		SimpleWindow &Window;
		vector<vector<BitMap> > Bmp; 
		Direction CurrentDirection;
		Position CurrentPosition;
		bool bUpOk;
		bool bDownOk;
		Floor CurrentFloor;//当前所处的层                                  
          //游戏中得到的不是类Layer的层,而是有枚举定义的Floor的层
          //因为Layer继承自Map关键是一张图,没有什么特殊的属性
          //而枚举定义的Layer有初始化的作用,是每层有了自己的值
          //而相应GameController构造函数中分配的每层的敌人和篮球框Gap //的也都对应与向量中有自己的数值,所以在游戏控制器的一些函数中
		  //(如TestGap、TestEnemy)只检查当前层的篮球框和敌人,
		  //避免了跳跃效果的意外实现,也提高了检查的效率
		int Steps;  //玩家自游戏开始所走的步数
                    //用以连续轮流切换玩家位图,实现走动的效果

		
};

Enemy 和 Bullet

/***********************************************************/
// enemy.h                                                 //
//                                                         //
//类Enemy的声明与定义                                      //
//即游戏中的敌人,敌人在各自的Layer中不停的走动            //
/***********************************************************/
#ifndef ENEMY_H
#define ENEMY_H

const int EnemyBitMaps=2;
enum EnemyStatus {Alive,Dead,DeadAlready};
                           //DeadAlready 是保证enemy 不会重复被 Kill

class Enemy{
   public:
	   //constructor
     Enemy(SimpleWindow &W,const Position &p1=(0,0),const Position &p2=(0,0),double h=1 );
	 //需要提供一个窗口,敌人移动范围,以及每次位移大小(速度)
	 
   public:
	   //inspectors
	   SimpleWindow& GetWindow() const; //得到敌人所在窗口
	   Position GetPosition() const; //得到当前敌人位置
       Direction GetDirection() const; //得到当前敌人的方向
	   double GetHorizMovement() const;  //得到每次移动的水平位移
	   BitMap &GetBmp(const Direction &d); //得到相应方向位图
	   const BitMap &GetBmp(const Direction &d) const; //得到当前敌人的状态
	   EnemyStatus GetStatus()const;
	  
	  //mutators
	   void SetWindow(SimpleWindow &W);  //设置敌人窗口
	   void SetDirection(const Direction &d); //设置敌人的方向
	   void SetPosition(const Position &p); //设置敌人的位置
	   void SetStatus(EnemyStatus s);  //更改敌人的状态(在后面你会看到他的重要性)
	   void SetHorizMovement(double h); //设置每次水平位移的大小以更改移动速度
	   void SetP1P2(const Position &pp1,const Position &pp2); //设置敌人移动的范围

	   //Facilitators
	   void Create();	//创建敌人(画在游戏窗口中)
	   void Kill(); //杀死敌人(从当前窗口擦除)
	   void Move(); //使敌人在timer下不停地移动
	   void Draw(); //画出当前位图
	   void Erase(); //擦掉
	   Position NewPosition() const; //设置下一刻敌人的方向
	   void ChangeDirection(); //检查如果超出移动范围,则更改方向
	   
   private:     
	   //data members
	   SimpleWindow& Window;
	   vector<BitMap> Bmp;
	   double HorizMovement;
       Direction CurrentDirection;
	   Position CurrentPosition;
	   Position P1,P2;
	   EnemyStatus CurrenStatus;

};
#endif

/***************************************/
//derive class Enemy_1                  //
//分别为第一关、第二关的敌人            //
//均派生在类Enemy 只是位图不同,速度不同//
/***************************************/
         
#ifndef ENEMY_1_H
#define ENEMY_1_H
#include "enemy.h"
class Enemy_1 : public Enemy{
    public:
	  Enemy_1(SimpleWindow &W,const Position &p1=(0,0),
		  const Position &p2=(0,0) ,double h=0.5);
};
#endif

/********************************/
//derive class Enemy_2          //
/********************************/
#ifndef ENEMY_2_H
#define ENEMY_2_H
#include"enemy.h"
class Enemy_2 : public Enemy{
   public:
       Enemy_2(SimpleWindow &W, const Position &p1=(0,0),
		   const Position &p2=(0,0),double h=0.8 );
};
#endif

/***************************************************/
//derive class Bullet
//即游戏中樱木发射的子弹                           //
//自己在一个方向上水平自动移动,类似敌人           //
/***************************************************/
#ifndef BULLET_H
#define BULLET_H
#include "enemy.h"
class Bullet : public Enemy{
   public :
     Bullet(SimpleWindow &W,Direction d ,
		 const Position &p1=(0,0), const Position &p2=(0,0));
	 bool AtRightEdge() const;  //子弹走出移动范围之后应被消灭
	                            //所以增加相应的判断函数返回布尔值
	 bool AtLeftEdge() const;
     void B_Creat();  //制造子弹
	  
};
#endif

GameController

/***********************************************************/
// GameCoroller.h                                          //
//    游戏控制台,控制检查游戏中各个角色的状态以及不同     //
//角色之间的交互,是游戏中最重要最也最操劳的部分           //
/***********************************************************/

#ifndef GAMECONTROL_H
#define GAMECONTROL_H

enum GameLevel { One, Second, Done };

class GameController {
	public:
		//constructor
		GameController(const string &Title = "终结者(樱木花道版)",
		 const Position &WinPosition=Position(2.0,2.0),
		 const float WinLength =16,	 const float WinHeight = 13);
		 //标题、位置、窗口宽、高 用以初始化游戏窗口
		 
		// destructor
		~GameController();

		//inspectors
		SimpleWindow *GetWindow();
	    GameLevel CurrentLevel() const; //检查器 分别得到窗口和当前所在关卡

		//facilitators
		void Play(const GameLevel Level); //设置游戏所在关卡
		void TestGap(Player* player,vector<Gap*> gap);  
		     //检查游戏中玩家是否走到篮筐下(如果在篮筐下可以跳跃,走出篮筐不可以)
		
		void TestBullet(vector<Enemy_1*> e,vector<Bullet*> b);
		void TestBullet(vector<Enemy_2*> e,vector<Bullet*> b);
		     //检查游戏中的子弹是否达到敌人,如果碰到则将子弹和敌人都从窗口中擦掉。
	    bool TestPlayer(Player* player,vector<Enemy_1*> e);
		bool TestPlayer(Player* player,vector<Enemy_2*> e);
		     //检查游戏中玩家是否被敌人捉到(如果敌人碰到玩家,游戏失败)
		int  TimerTick();
		     //是TimerCallback调用的函数
             //游戏窗口中玩家,敌人,子弹的走到效果都是由他实现的游戏关卡跳跃也是在这里实现的。
             //但因为这个函数不断被调用,而我们想在第一关通过之后加一个小小的提示(Message)
             //结果就是Message不段被弹出,所以用了一个全局变量PlayOne,
             //保证提示的函数只执行一次,这个平白出来的“魔数”也许增加了代码阅读的困难

	private:
	    //data members
		SimpleWindow *GameWindow;
		GameLevel Level;
		
		//游戏中各种角色为GameController控制,
		//所以作为控制台的数据成员包含在GameController的属性中
		Player *player;
		vector<Gap*> gaps;
		vector<Layer*> layers;
		vector<Enemy_1*> enemy1;	
		vector<Enemy_2*> enemy2;


};
#endif

全局的类:Global 和 Welcome

/********************************************************************/
//resources.h                                                        //
//                                                                   //
//游戏结束是显示界面的资源                                           //
//本想和 welreources.h 放在一起,因为其窗口都是独立于游戏窗口的      //
//并不作为GameControl的数据成员,而是一全局变量使用                  //
//但放在一起可能是因为game.cpp和gamecontroll.cpp都要包含,会出错     //
/*********************************************************************/

#ifndef  WEL_H
#define  WEL_H

//包含在game.cpp中相当于全局变量
//保证函数 int TimerCallBack(void);  
//和 int MouseClick(const Position &MousePosition);可以方便的使用

SimpleWindow End("终结者(樱木花道版)",15.0,12.0,Position(2.0,2.0));
BitMap WellDone(End);
BitMap Quit(End);
BitMap Fail(End);

void SetWindows(){
	Fail.Load("bmp\\fail.bmp");
	assert(Fail.GetStatus()==BitMapOkay);

	WellDone.Load("bmp\\done.bmp");
	assert(WellDone.GetStatus()==BitMapOkay);

	double width=Fail.GetWidth();
	double height=Fail.GetHeight();

	Quit.Load("bmp\\quit.bmp");
	Quit.SetPosition(Position(0.6*width,0.8*height));
	assert(Quit.GetStatus()==BitMapOkay);

}

int MouseClickEnd(const Position& MousePosition){
	if(Quit.IsInside(MousePosition)){
		End.Close();
	}
	return 1;
}
#endif

welcome.h

/*******************************************************/
//welresources.h                                        //
//并不是一个单独的类,是玩家开始进入游戏时的欢迎界面    //
//放在 *.h文件包含在 game.cpp中作为全局变量             //
/*******************************************************/

#ifndef WELCOME_H
#define WELCOME_H
#include "assert.h"

SimpleWindow Welcome("终结者(樱木花道版)",15.0,12.0,Position(2.0,2.0));
BitMap PlayButton(Welcome);
BitMap InstructionButton(Welcome);
BitMap Instruction(Welcome);

void SetWelcomeWindows(){
	BitMap WelcomeBmp(Welcome);
	WelcomeBmp.SetPosition(Position(0,0));
	WelcomeBmp.Load("bmp\\hello.bmp");
	WelcomeBmp.Draw();
	assert(WelcomeBmp.GetStatus()==BitMapOkay);

	double width=WelcomeBmp.GetWidth();
	double height=WelcomeBmp.GetHeight();

	PlayButton.SetPosition(Position(0.1*width,0.85*height));
	PlayButton.Load("bmp\\play.bmp");
	assert(PlayButton.GetStatus()==BitMapOkay);

	Instruction.SetPosition(Position(0,0));
	Instruction.Load("bmp\\in.bmp");
	assert(Instruction.GetStatus()==BitMapOkay);

	InstructionButton.SetPosition(Position(0.1*width,0.35*height));
	InstructionButton.Load("bmp\\inbutton.bmp");
	assert(InstructionButton.GetStatus()==BitMapOkay);
	InstructionButton.Draw();

}

#endif

【游戏控制逻辑框图】




【游戏说明】

本游戏无需安装。打开文件夹“YingMu”,双击“YingMu.exe”可直接开始游戏。
打开会出现游戏的进入窗口——显示有灌篮高手剧照图,单击窗口中的“Instruction”图标可以看到具体的游戏操作说明,之后单击“Play”图标即可开始游戏。
游戏开始后,玩家可以看到左下角的樱木花道以及三个在窗口中不停移动的敌人,玩家以键盘的左右键控制樱木花道在窗口中移动,当走到篮筐下时,可以有上下键控制实现层的跳跃。游戏中,玩家按空格键发射子弹(子弹的数目没有限制),子弹遇到敌人即可消灭敌人。当消灭玩所有的敌人,游戏会弹出对话框提示玩家进入下一关。第二关所有敌人被消灭后,游戏胜利。玩家会看到恭喜的窗口,单击左下角的“Quit”图标即可退出游戏。如果游戏中,玩家不小心被敌人捉到,游戏失败,会出现提示游戏失败的界面,单击“Quit”图标退出游戏。


【游戏截图】




【项目总结】

通过大一的C++语言学习后,我们小组完成了第一个项目编程—终结者(樱木花道版)。

接到任务,我们迅速开始……

我们首先上网查阅了相关资料,并下载了一些相关的小游戏进行试玩,揣摩游戏的设计架构及功能实现,汲取别人的长处。接着我们进行课程设计“终结者”的构思,我们最初的设计是界面分为几层,玩家可以在每一层上走动,通过方向键左右移动,遇到楼梯可以上下爬动,空格键发射子弹,并设有各种不同的食物,玩家控制小人走过去“吃”,不同的食物增加或减少相对应的属性(如:生命值、速度等)。然而在真正动手的时候,才发现敲代码远远不像读代码那么简单。我们分工合作,两个人分别编写不同的类,几乎是白手起家,决定和实现每个类有怎样的功能显得异常的生疏和困难,尤其在派生的时候才发现总结出对象必要的属性、行为是多么重要。而这只是困难的开始,当我们把零零散散的代码拼在一起时,终于见识到所谓的“面向对象”并没有我们想象的那么简单。虽然都认真的开了书中的BugHunt程序,可实际上我们很多地方并没有理解到位,尤其是GameController的重要——把一个个孤立的对象联系起来,可以相互“交流”的才是真正“活”的对象,才组成了生动的游戏。我们熬了几个通宵,重写了很多次,删减了许多一开始野心勃勃想要实现的内容,于是有了想在这个终结者的雏形。
之后的时间是我们一起小心翼翼的在游戏中添加一些功能,比如发射子弹,樱木的“走动”效果,以及图片的处理等等……游戏进入和退去的界面也是在这个时期完成的。
在游戏最终出品前期,我们在实验室进行试玩,却发现一个问题:不同的屏幕和分辨率造成了窗口及图片坐标的偏移,我们最初是在自己的电脑上设定的坐标,因为偷懒而直接使用的数字——迅速显出弊端,而在实验室的电脑上,图片甚至发生了重叠。我们随即想到的是把游戏做成两个版本(标屏和宽屏),但后来我们又发现分辨率也会对坐标产生影响,所以两个版本的计划无法实施。我们也去请教了别人查阅了资料,最后用图片的大小来调整层之间的坐标(因为似乎不同的屏幕图片还是不变的),但问题也没有得到根本的解决,结果没有预期的好,这让我们尤其以为可以自慰的地方——游戏的整体美观度大打折扣,希望在以后的学习中能更好的解决该问题,进行游戏的升级与更新。

游戏问世,不是期望的样子……

现在再回头看我们自己编写的第一个游戏,即使抱着“敝帚自珍”的心态,依旧觉得她有那么多不尽人意的地方,毕竟最终的成果有点简单,和当初的设想差得太远,许多功能都没有实现——因为ezwin的功能有限,添加游戏背景甚至是在“梯子”上的走动,都有很明显的闪图,就改成了跳跃;死掉的子弹和敌人都被我们投机取巧的藏在游戏图中,没有真正的delete以释放内存;时间有限,也没有再去在界面中做“生命值”、“速度”等……应该是在开始进行程序架构时,我们就缺乏严谨的逻辑思考,想法太杂太乱,导致有些功能实现起来太过复杂,而使得程序显的臃肿且不利于阅读。

一月奋战,我们还是收获了很多……

这次课程设计中,我们深切的体会到调试的重要,很多问题都是在编译、连接中发现的,看着自己编写的程序一编译出现那么多的错误,信心肯定受到打击,但是通过一次次的修改,看着错误数量的减少,还是很有成就感的。总的来说,我们用于调试的时间远远多与编写代码的时间。
通过课程设计,我们不仅锻炼了自己的实践能力,更培养了团队合作能力。遇到问题时,大家一起互相讨论,一起请教别人,最终找到解决方法,我想这应该是最好的学习过程,在实践中提高自己,比看书的效率高太多了。同时我们也意识到书本上的那些知识对于编程是远远不够的,教科书只是介绍一些皮毛而已,它只是引导你入门而已。我们更多的是要主动的学习,一起做项目就是一种好的方法。

我们知道,我们差的太远太远,要走的路还很长很长……

这次我们小组出品的游戏——终结者(樱木花道版)虽然有些粗糙,功能也十分有限,但是做为这款游戏的整个框架与体系,它实现了我们利用面向对象思想设计游戏的初衷。而在编写以及后来的程序调试与排错的过程中也练习了我们使用C++语言的熟练度以及对常见问题解决的经验积累。更主要的是,他使我们深深地看到自己的差距——学习了一年,还没用过MFC;不能短时间内迅速掌握HGE;即便是参考别人的代码,还要一个字一个字的读……这个挺普通的程序也许是对我们贫乏知识的真实写照,但,这绝不是终点,我们会在今后的学习中努力提高自己的水平,努力创造出优秀的作品。

这是我们软件学习上的第一次试炼,也是我们大一面向对象程序设计学习的成果,相信我们每个人都将终生难忘……


转载请注明出处:http://blog.csdn.net/xiaowei_cqu/article/details/7747650



分享到:
评论

相关推荐

    C++终结者课程设计

    C++终结者课程设计:小游戏“终结者”程序的设计与实现。该课程设计在软件学院教学实验中心进行。学生应独立完成该课程设计的游戏程序 - 终结者(Terminator)软件项目。游戏程序采用面向对象的设计与编程方法,...

    金旭亮《C#面向对象程序设计》教案7_9:终结版

    本资源包容金旭亮《C#面向对象程序设计》教案的最后3讲:7 对象集合与对象组合;8 泛型编程;9 对象间的协作与信息交换。包容相关PDF文档及VS2010示例源码。请关注金旭亮博客以获取更多技术资源:...

    p2p 终结者 网管

    p2p 终结者 p2p 终结者p2p 终结者 p2p 终结者 p2p 终结者 p2p 终结者p2p 终结者 p2p 终结者 p2p 终结者p2p 终结者 p2p 终结者 p2p 终结者 p2p 终结者p2p 终结者p2p 终结者 p2p 终结者p2p 终结者 p2p 终结者 p2p 终结...

    网吧终结者网吧终结者网吧终结者

    网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者网吧终结者

    广告窗口终结者VC源程序

    一个终结程序广告窗口的VC源程序.广告窗口 终结者 源码 VC

    P2P终结者 4.04P2P终结者 4.04P2P终结者 4.04

    P2P终结者 4.04P2P终结者 4.04P2P终结者 4.04P2P终结者 4.04P2P终结者 4.04P2P终结者 4.04

    灰色按钮终结者 灰色按钮终结者

    灰色按钮终结者 灰色按钮终结者 灰色按钮终结者 灰色按钮终结者 灰色按钮终结者 灰色按钮终结者 灰色按钮终结者

    用java实现的仿p2p终结者软件

    java版仿p2p终结者,使用arp欺骗实现,另附源码在jar文件中

    驱动程序-其它驱动-达尔优凌豹终结者键盘驱动程序.zip

    驱动程序-其它驱动-达尔优凌豹终结者键盘驱动程序.zip

    p2p终结者.exe

    p2p终结者.exe

    p2p终结者4.34

    P2P终结者基于底层协议分析处理实现,具有很好的透明性。软件可以适应绝大多数网络环境,包括代理服务器、ADSL路由器共享上网,Lan专线等网络接入环境。它可以让您轻松地、傻瓜化地管理局域网中BT、电驴等大量占用...

    驱动防火墙终结者 驱动防火墙终结者

    驱动防火墙终结者 驱动驱动防火墙终结者 防火墙终结者 驱动防火墙终结者 驱动防火墙终结者

    终结者文件加密大师

    终结者文件夹加密大师软件是一款永久免费的文件夹加密程序,程序采用100%纯汇编语言编写,实现了文件夹加/解密功能,程序体积小巧,加密速度快,支持U盘/移动硬盘上面的文件夹加密,方便安全,终结者文件夹加密大师...

    五子棋终结者2.0 五子棋终结者是一个计算机执黑必胜的五子棋小程序,严格地、完全地毯式地终结了自由规则下的五子棋!

    五子棋终结者是一个计算机执黑必胜的五子棋小程序,严格地、完全地毯式地终结了自由规则下的五子棋!

    图标终结者ICO

    图标终结者

    易游还原终结者易游还原终结者易游还原终结者

    易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游还原终结者易游...

    p2p终结者4.03

    P2P终结者是由Net.Soft工作室开发的一套专门用来控制企业网络P2P...而且,P2P终结者开发人员将不断跟踪最新P2P协议发展动态,以实现软件更完善的控制功能,同时也会吸取用户的反馈意见,以把P2P终结者软件做得更加完善

    p2pover 终结者

    p2pover 终结者p2pover 终结者p2pover 终结者

    终结者木马专杀工具 终结者木马专杀工具

    终结者木马专杀工具 终结者木马专杀工具 终结者木马专杀工具终结者木马专杀工具终结者木马专杀工具 终结者木马专杀工具

    P2P终结者 4.14 

    而且,P2P终结者开发人员将不断跟踪最新P2P协议发展动态,以实现软件更完善的控制功能,同时也会吸取用户的反馈意见,以把P2P终结者软件做得更加完善。 4.03 [2009-01-18] +增加主机配置备份功能 +增加主机配置...

Global site tag (gtag.js) - Google Analytics