FunCode程序设计实验教材系列
C++面向对象课程设计
实验指南
课程设计三 太空战机
一、游戏介绍
太空战机是玩家用键盘控制战机移动并发射子弹,消灭敌方的战机。敌方战机从右到左移动,同时上下浮动。
二、实验目的
综合应用C++语言和面向对象的知识开发一款小游戏。
三、实验内容
在外星球上,玩家通过键盘WSAD键控制己方战机,消灭外星球的邪恶战机。
要求如下:
1、 游戏运行时,初始界面如下图。
2、 按下空格键,游戏开始,玩家通过WSAD键控制己方战机移动;己方战机不能超出世界边界。
3、 玩家战机每隔0.3秒发射一发子弹;
4、 添加敌方战机,每隔5秒创建一架敌方战机;
5、 敌方战机每隔3秒发射一发子弹;
6、 记录游戏的最高分。
游戏初始界面
四、实验指南
实验一 游戏开始和控制我方战机移动
【实验内容】
1、 按空格键,游戏开始,“空格开始”字样消失。
2、 创建CMyFighter类,并创建对象实例玩家控制的战机。
3、 战机碰到世界边界时,静止。
4、 游戏开始后,通过键盘WSAD键控制战机移动。
5、 战机左右运动的速度为30,上下运动的速度为15。
6、 在游戏中显示游戏的当前积分和最高积分。
【实验思路】
按空格键开始游戏,属于键盘按下事件,我们在OnKeyDown函数中编写代码。
在游戏中,我们运用面向对象的知识将战机看成一个对象,并为这个对象添加一个类叫CMyFighter。类具有属性和方法,要控制战机能在各个方向上自由的游动,我们为CMyFighter类添加上下左右四个方向的速度,并且我们为战机添加OnMove方法控制战机的游动状态。
【实验指导】
1、 在CGameMain中定义类成员变量:
CSprite* m_pBeginSprite ; //GameBegin为“空格开始”精灵 CTextSprite* m_pCurScoreText;//显示当前积分 CTextSprite* m_pMaxScoreText;// 显示最高分
2、 在CGameMain类的构造函数中添加代码,对变量进行初始化。
m_pBeginSprite = new CSprite("GameBegin"); m_pCurScoreText = new CTextSprite("CurScoreText"); m_pMaxScoreText = new CTextSprite("MaxScoreText");
3、 在OnKeyDown中,当按下的按键为空格键并且此时的游戏状态为0,则设置游戏的状态为1。0表示此时游戏为等待状态,未开始。1表示游戏进行初始化,2表示初始化后会进入游戏运行状态。
// 按下空格,游戏开始 if( KEY_SPACE == iKey && 0 == GetGameState() ) { SetGameState( 1 ); }
4、 在游戏初始化函数GameInit中隐藏 "按空格开始游戏"图片。
m_pBeginSprite->SetSpriteVisible( false );
5、 通过类向导创建CMyFighter类,其继承于CSprite类。以VC++ 6.0为例:
第一步、点击菜单“插入”–〉“新建类”。
第二步、在“New Class”对话框中输入类名和父类名。
第三步、点击“更改”按钮,在新对话框中修改CMyFighter类的头文件和cpp文件的路径。将头文件保存到项目文件夹的\SourceCode\Header文件夹中,将cpp文件保存到项目文件夹下的\SourceCode\Src文件夹中。
这里需要特别注意的是创建文件路径的问题,所有的.h头文件应该在项目文件夹\SourceCode\Header中,所有的.cpp源文件应该放在项目文件夹下的\SourceCode\Src文件夹中。
(1)CMyFighter的父类是CSprite类(具体声明查看CommonClass.h),构造函数为CSprite( const char *szName )。
在MyFighter.cpp的首部包含“CommonClass.h”。
将系统自动生成构造函数CMyFighter()改为:
CMyFighter (const char* szName)。 CMyFighter:: CMyFighter (const char* szName):CSprite(szName) //对构造函数进行实现 { }
子类对象创建时,要先调用父类的构造函数完成父类部分的构造。如果父类没有默认构造函数,子类的构造函数必须显示调用父类的构造函数。CMyFighter构造函数调用CSprite类构造函数,并将参数szName的值传递给它,从而将名称为szName的精灵图片与CMyFighter对象绑定起来。
(2)为CMyFighter类添加m_fVelocityLeft,m_fVelocityRight,m_fVelocityUp,m_fVelocityDown四个成员变量,分别表示飞机上下左右的速度,权限为private。
本文档的命名采用匈牙利命名法,m_表示类成员变量,i表示整型,f表示float型,sz表示字符指针,g_表示全局变量等。
6、 在CMyFighter类的构造函数中,首先初始化4个方向的速度为0。
m_fVelocityLeft = 0.f; m_fVelocityRight = 0.f; m_fVelocityUp = 0.f; m_fVelocityDown = 0.f;
7、 添加成员函数OnMove控制战机的游动,其参数bKeyDown表示键盘按键是否按下,iKey表示相应的是哪个按键。
void OnMove(bool bKeyDown, int iKey);
8、 编写CMyFighter类OnMove方法代码。首先判断当前按键是按下还是松开的,其次判断是哪个按键的消息,根据这两个判断,为4个方向的速度矢量赋值。再次算出X后和Y轴上的速度,并设置战机的速度。
void CMyFighter::OnMove(bool bKeyDown, int iKey) { if(bKeyDown) { switch(iKey) { case KEY_A: // 左 m_fVelocityLeft = 30.f; break; case KEY_D: // 右 m_fVelocityRight = 30.f; break; case KEY_W: // 上 m_fVelocityUp = 15.f; break; case KEY_S: // 下 m_fVelocityDown = 15.f; break; } } else { switch(iKey) { case KEY_A: // 左 m_fVelocityLeft = 0.f; break; case KEY_D: // 右 m_fVelocityRight = 0.f; break; case KEY_W: // 上 m_fVelocityUp = 0.f; break; case KEY_S: // 下 m_fVelocityDown = 0.f; break; } } float fVelX = m_fVelocityRight - m_fVelocityLeft; float fVelY = m_fVelocityDown - m_fVelocityUp; SetSpriteLinearVelocity( fVelX,fVelY ); }
9、 在CGameMain类中
(1)首先添加一个成员变量,代表玩家战机对象的指针m_pMyFighter。
CMyFighter* m_pMyFighter; //玩家战机
注意需要包含头文件:
#include "MyFighter.h"
(2)在构造函数中将m_pMyFighter赋予NULL的初始值。
(3)在GameInit方法中初始化m_pMyFighter,并设置和世界编辑的碰撞属性为WORLD_LIMIT_STICKY,当碰到世界边界时,战机静止不动。
// 创建玩家控制的Sprite if( NULL == m_pMyFighter ) { m_pMyFighter = new CMyFighter("ControlSprite"); m_pMyFighter->SetSpriteWorldLimit(WORLD_LIMIT_STICKY,CSystem::GetScreenLeft()-10.f,CSystem::GetScreenTop(),CSystem::GetScreenRight(),CSystem::GetScreenBottom()); }
因为用new方法创建了m_pMyFighter对象,分配了内存,所以在CGameMain类的析构函数中需要调用delete方法将m_pMyFighter使用的内存释放掉。
10、 在OnKeyDown和OnkeyUp中响应战机OnMove方法,它们的区别只是第一个参数的值不同。下面是OnKeyDown方法中的调用。
if( 2 == GetGameState() ) //当游戏状态为2时 { m_pMyFighter->OnMove(true,iKey); }
在OnKeyUp中调用
if( 2 == GetGameState() ) { m_pMyFighter->OnMove(false,iKey); }
11、 在CGameMain类的GameInit方法中显示当前积分和最高积分(初值为0)。
m_pCurScoreText->SetTextValue(0); m_pMaxScoreText->SetTextValue(0);
实验二 添加子弹类,实现战机开炮
【实验内容】
1、 创建子弹类CBullet;
2、 通过空格键控制飞机发射子弹;
3、 当空格键按下时,飞机每隔0.3秒发射一发子弹;
【实验思路】
运用面向对象的知识,我们将游戏中的元素都看为一个对象,因此我们将子弹对象抽象为CBullet类。当子弹与世界边界碰撞时,子弹消失。
当空格键按下时飞机每隔0.3发射一发子弹,因此我们在飞机类中增加一个bool型的属性m_bCanFire,控制子弹是否发射。然后增加方法OnFire,参数为游戏循环一次的时间间隔,当时间间隔大于0.3时并且m_ bCanFire为true时,飞机发射一发子弹。飞机发射的子弹,我们在CGameMain类中进行创建。
在游戏循环的GameRun函数中调用飞机的OnFire方法,实现飞机每隔三秒发射一发子弹。
在CGameMain类中添加一个创举子弹的方法,当战机发射子弹时,调用此方法。
【实验指导】
1、 仿照创建我方战机的方法创建CBullet类;
class CBullet : public CSprite { public: CBullet( const char *szName); ~CBullet(); };
2、 为CMyFighter增加控制是否发射子弹的变量,权限为private。
bool m_bCanFire;
并添加SetCanFire方法设置其值,权限为public:
void SetCanFire( const bool bCan ) { m_bCanFire = bCan; }
添加GetCanFire方法获取其值:
bool GetCanFire(){return m_bCanFire;}
3、 在CGameMain类中添加m_iCreatedBulletCount属性,表示游戏中发射子弹的数目注意在构造函数中初始化为0。
4、 在CGameMain类中添加CreateBullet()函数,参数为子弹的X轴和Y轴坐标。并在该方法中创建一个子弹类,并设置子弹的位置、速度、碰撞方式。因为模板子弹的方向是朝左,所以需要设置子弹翻转朝右。
void CGameMain::CreateBullet( const float fPosX, const float fPosY ) { char szName[MAX_NAME_LEN];// MAX_NAME_LE为CommonClass.h中宏定义 值为128 sprintf( szName, "Bullet1_%d", m_iCreatedBulletCount); m_iCreatedBulletCount++; CBullet *pBullet = new CBullet(szName); pBullet->CloneSprite( "Bullet1_Template" ); pBullet->SetSpritePosition( fPosX, fPosY ); pBullet->SetSpriteFlipX(true); pBullet->SetSpriteLinearVelocityX( 60 ); pBullet->SetSpriteWorldLimit(WORLD_LIMIT_NULL,CSystem::GetScreenLeft()-10.f,CSystem::GetScreenTop(),CSystem::GetScreenRight() + 200.f, CSystem::GetScreenBottom()); pBullet->SetSpriteCollisionActive(true,true); }
这里用到CBullet类,所以应该在LessonX.h中包含头文件:
#include"Bullet.h"
5、 在CMyFighter中添加成员变量m_fBulletCreateTime,表示子弹的发射间隔,注意在构造函数中将其初始化为0.3。
6、 在CMyFighter中添加成员函数OnFire(),处理玩家战机子弹的发射。参数为游戏的时间间隔;
// 处理子弹的发射 void CMyFighter::OnFire( float fDeltaTime ) { m_fBulletCreateTime -= fDeltaTime; if( m_fBulletCreateTime <= 0.f && m_bCanFire==true ) { // 固定发射时间 m_fBulletCreateTime = 0.3f; g_GameMain.CreateBullet(GetSpritePositionX(), GetSpritePositionY() ); } }
这里用到了g_GameMain这个全局对象,所以在CMyFight.cpp中应该包含头文件:
#include "LessonX.h"
7、 在CGameMain类GameRun方法中,调用CMyFighter类的OnFire方法,控制玩家战机发射子弹;
void CGameMain::GameRun( float fDeltaTime )
{
// 执行我方战机的循环函数 if( m_pMyFighter ) m_pMyFighter->OnFire( fDeltaTime );
}
8、 玩家发射子弹需要有当空格键按下,设置CMyFighter的m_bCanFire值为true。在OnKeyDown方法中添加如下代码;
// 游戏进行中,按下空格发射子弹 if( 2 == GetGameState() && KEY_SPACE == iKey && NULL != m_pMyFighter ) m_pMyFighter->SetCanFire( true );
9、 同理在OnKeyUp中设置m_bCanFire值为false。
实验三 敌方战机
【实验内容】
1、 创建一个敌方战机类CEnemyFighter;
2、 战机以编辑器中HorizontalSprite_Template精灵为模板;
3、 每隔几秒创建一架敌方战机;
【实验思路】
运用面向对象知识,创建CEnemyFighter类。该类具有点方战机的属性,战机隔一定的事件被复制出来,然后上下浮动,开始向我方战机发射子弹,这样有助于增加敌方战机的杀伤力,也增加了游戏的趣味性。
【实验指导】
1、 仿照方便的方法创建CEnemyFighter类,其继承与CSprite类,修改其构造函数。
2、 为类增添两个静态变量,一个表示表示创建敌机的数量,一个表示创建敌机的时间;
static float m_fCreateTime; // 创建敌机的时间间隔 static int m_m_iCreatedEnemyCount;//表示创建战机数量
并在EnemyFighter.cpp文件最后进行初始化:
float CEnemyFighter::m_fCreateTime = 0.f; int CEnemyFighter:: m_iCreatedEnemyCount= 0;
3、 为CEnemyFighter类添加一个创建敌方战机的静态方法createEnemyFighter (float fDeltaTime)。
1)在EnemyFighter类中添加函数的声明:
void static createEnemyFighter ( float fDeltaTime );
2)其参数为游戏的时间间隔。当创建战机的时间间隔递减为0时,创建战机。并重新设置时间间隔时间为1到3