1.飞机大战
1.判断玩家飞机是否与敌人飞机发生碰撞
1.经过前几天的学习,我们已经可以编写出一个相当厉害的飞机大战游戏了(玩家飞机永不爆炸以及按下空格就会开挂…),那么接下来需要让玩家飞机可以被撞从而使游戏结束(这下游戏稍微正常点了喂…)。
2.在判断玩家飞机是否与敌人飞机发生碰撞的时候,我们仍然使用的点与矩形判别法(类似于之前子弹与敌人飞机碰撞的判断)。
1)为了简化判断的流程(正常情况下是将玩家飞机的每个像素与敌人飞机的每个像素进行判断,但是,emmmm…好麻烦),我们使用玩家飞机上的三个点来进行判断:(x+30,y)、(x+60,y+50)、(x,y+50);
2)当判断完成后,我们继续完成 IsGameOver 函数,遍历敌人飞机链表,敌人飞机每移动一次就调用一次,玩家飞机每移动一次,也调用一次,若撞上,则停止所有的定时器 killtimer,并弹出对话框 MessageBox;
3)主要代码:
1)大飞机与玩家飞机发生碰撞:
bool CBigFoePlane::IsHitPlayerPlane(const CPlayer& plane)
{
// 本次判断三个点,(x+30, y)、(x, y+50)、(x+60, y+50)这三个点
if(plane.x+30 > this->x && plane.x < this->x+108 && plane.y > this->y && plane.y < this->y+128
|| plane.x > this->x && plane.x < this->x+108 && plane.y+50 > this->y && plane.y+50 < this->y+128
|| plane.x+60 > this->x && plane.x+60 < this->x+108 && plane.y+50 > this->y && plane.y+50 < this->y+128)
{
return true;
}
return false;
}
2)中型飞机与玩家飞机发生碰撞:
bool CMidFoePlane::IsHitPlayerPlane(const CPlayer& plane)
{
// 本次判断三个点,(x+30, y)、(x, y+50)、(x+60, y+50)这三个点
if(plane.x+30 > this->x && plane.x < this->x+70 && plane.y > this->y && plane.y < this->y+90
|| plane.x > this->x && plane.x < this->x+70 && plane.y+50 > this->y && plane.y+50 < this->y+90
|| plane.x+60 > this->x && plane.x+60 < this->x+70 && plane.y+50 > this->y && plane.y+50 < this->y+90)
{
return true;
}
return false;
}
3)小飞机与玩家飞机发生碰撞:
bool CSmallFoePlane::IsHitPlayerPlane(const CPlayer& plane)
{
// 本次判断三个点,(x+30, y)、(x, y+50)、(x+60, y+50)这三个点
if(plane.x+30 > this->x && plane.x < this->x+34 && plane.y > this->y && plane.y < this->y+28
|| plane.x > this->x && plane.x < this->x+34 && plane.y+50 > this->y && plane.y+50 < this->y+28
|| plane.x+60 > this->x && plane.x+60 < this->x+34 && plane.y+50 > this->y && plane.y+50 < this->y+28)
{
return true;
}
return false;
}
4)游戏结束判断与调用:
// 游戏结束判断函数
bool CPlaneApp::IsGameOver()
{
list<CFoePlane*>::iterator ite = foe_plane_box.m_lstFoePlane.begin();
while (ite != foe_plane_box.m_lstFoePlane.end())
{
if((*ite)->IsHitPlayerPlane(player))
return true;
ite++;
}
return false;
}
// 游戏结束函数调用部分
void CPlaneApp::OnGameRun(WPARAM w_param)
{
if(w_param == PLAYER_MOVE_TIMER_ID)
{
if(::GetAsyncKeyState(VK_LEFT))
player.MovePlayer(VK_LEFT);
if(::GetAsyncKeyState(VK_RIGHT))
player.MovePlayer(VK_RIGHT);
if(::GetAsyncKeyState(VK_DOWN))
player.MovePlayer(VK_DOWN);
if(::GetAsyncKeyState(VK_UP))
player.MovePlayer(VK_UP);
if(IsGameOver())
{
::KillTimer(m_hwnd, BACK_MOVE_TIMER_ID); // 控制背景移动的定时器
::KillTimer(m_hwnd, PLAYER_MOVE_TIMER_ID); // 控制玩家飞机移动的定时器
::KillTimer(m_hwnd, GUNNER_MOVE_TIMER_ID); // 控制子弹移动的定时器
::KillTimer(m_hwnd, SEND_GUNNER_TIMER_ID); // 控制发射子弹的定时器
::KillTimer(m_hwnd, FOE_PLANE_CREATE_TIMER_ID); // 控制敌人飞机生成的定时器
::KillTimer(m_hwnd, FOE_PLANE_MOVE_TIMER_ID); // 控制敌人飞机移动的定时器
::KillTimer(m_hwnd, CHANGE_PLANE_SHOWID_TIMER_ID); // 控制飞机爆炸显示图的定时器
::MessageBox(0, "====BOOM====", "==菜狗==", MB_OK);
}
}
if(w_param == BACK_MOVE_TIMER_ID)
{
back.MoveBack();
}
if(w_param == SEND_GUNNER_TIMER_ID)
{
player.SendGunner(gun_box, m_h_ins);
}
if (w_param == GUNNER_MOVE_TIMER_ID) // 每次炮弹移动都需要进行判断是否击中了飞机
{
gun_box.AllGunnerMove();
this->GunnerHitFoePlane();
}
// 敌方飞机生成
if(w_param == FOE_PLANE_CREATE_TIMER_ID)
{
foe_plane_box.CreateFoePlane(m_h_ins);
}
// 敌方飞机移动
if(w_param == FOE_PLANE_MOVE_TIMER_ID)
{
foe_plane_box.AllFoePlaneMove();
if(IsGameOver())
{
::KillTimer(m_hwnd, BACK_MOVE_TIMER_ID); // 控制背景移动的定时器
::KillTimer(m_hwnd, PLAYER_MOVE_TIMER_ID); // 控制玩家飞机移动的定时器
::KillTimer(m_hwnd, GUNNER_MOVE_TIMER_ID); // 控制子弹移动的定时器
::KillTimer(m_hwnd, SEND_GUNNER_TIMER_ID); // 控制发射子弹的定时器
::KillTimer(m_hwnd, FOE_PLANE_CREATE_TIMER_ID); // 控制敌人飞机生成的定时器
::KillTimer(m_hwnd, FOE_PLANE_MOVE_TIMER_ID); // 控制敌人飞机移动的定时器
::KillTimer(m_hwnd, CHANGE_PLANE_SHOWID_TIMER_ID); // 控制飞机爆炸显示图的定时器
::MessageBox(0, "====BOOM====", "==菜狗==", MB_OK);
}
}
// 飞机爆炸显示图
if(w_param == CHANGE_PLANE_SHOWID_TIMER_ID)
{
blast_foeplane_box.ChangeShowID();
}
this->OnGameShow();
}
飞机大战告一段落
2.对于飞机大战的总结
1.所用的编程语言:C++
2.用到模板类,提高了程序的复用性和扩展性
3.用到的技术:链表list以及单一设计原则
4.碰到的问题:游戏刷新出现问题,需要用双缓冲来解决;移动不灵敏,可以通过再为移动添加一个定时器来控制刷新
3.拷贝构造
1.类中所拥有的默认构造函数
1.我们可以这样创建一个类对象:CPerson ps1(ps),这样的形式叫做拷贝构造。
#include <iostream>
using namespace std;
class CPerson
{
public:
int *a;
public:
CPerson()
{
a = new int(100);
}
// 默认的拷贝构造函数:是一个浅拷贝
CPerson(const CPerson& ps)
{
this->a = ps.a;
}
~CPerson()
{
delete(a);
a = 0;
}
};
int main()
{
CPerson ps;
CPerson ps1(ps);
system("pause");
return 0;
}
2.但是这样的拷贝构造函数是一个浅拷贝,因此会导致内存在回收时出错(程序结束时,大家可以自行运行一下上述程序看看结果)。
1)主要是由于连续删除了两次 a 所指向的位置;
2)解决办法,重写拷贝构造函数:
// 需要进行深拷贝以解决浅拷贝所引起的程序崩溃
CPerson(const CPerson& ps)
{
this->a = new int;
*(this->a) = *(ps.a);
}
2.类中默认 = 操作符
1.我们用一个示例来了解一下:
#include <iostream>
using namespace std;
class CPerson
{
public:
int *a;
public:
CPerson()
{
a = new int(100);
}
~CPerson()
{
delete a;
a = 0;
}
};
int main()
{
CPerson ps1;
CPerson ps2;
ps2 = ps1; // 当对 操作符= 不进行深拷贝时,默认为浅拷贝,会引起程序奔溃(删除了两次a的空间)
// 因此需要对其进行深拷贝以解决该问题
system("pause");
return 0;
}
2.与拷贝构造类似,当我们使用 ps2 = ps1 时没有错误发生,但是在程序结束回收内存时出现错误,这个原因也是因为 = 是进行的浅拷贝,而非深拷贝,因此我们需要重写 = 操作符。
#include <iostream>
using namespace std;
class CPerson
{
public:
int *a;
public:
CPerson()
{
a = new int(100);
}
~CPerson()
{
delete a;
a = 0;
}
public:
void operator=(const CPerson& ps)
{
delete a; // 进行深拷贝时,需要先删除 a ,之后再重新 new 一个空间出来给 a
a = new int(*(ps.a));
}
};
int main()
{
CPerson ps1;
CPerson ps2;
ps2 = ps1;
system("pause");
return 0;
}
3.总结
1.空类默认的函数:构造函数、析构函数、拷贝构造函数、operator=函数
2.什么是拷贝构造:是类中的一个构造函数,且第一个参数是当前这个类的const类型的引用。
3.拷贝构造的作用:从一个对象复制到另一个对象。
4.拷贝构造需要注意的问题:类中默认的拷贝构造是一个浅拷贝,浅拷贝会使得两个对象使用同一个空间,当程序结束时导致该空间被释放两次,从而引起内存错误。
5.解决默认拷贝构造函数的方法:深拷贝,或者参数用指针、引用,不存在对象间复制的过程也就没有拷贝。
6.拷贝构造与operator=之间的异同点:
1)共同点:默认方法都是浅拷贝;
2)不同点:拷贝构造创建对象初始化执行,operator=是当对象执行 = 时执行。
4.设计模式
1.属于面向对象的范畴,是指解决某个问题的解决方法。
2.单例:只能存在一个对象,将构造函数设置为 private,使用接口函数来获取对象,可以设置 bool 变量来记录是否有对象存在。
3.需要用静态函数来声明该接口函数。
#include <iostream>
using namespace std;
bool ps_flag = false;
class CPerson
{
private:
CPerson(){}
public:
static CPerson* GetCPerson()
{
if(!ps_flag)
{
ps_flag = true;
CPerson *ps = new CPerson;
return ps;
}
else
{
return NULL;
}
}
};
int main()
{
CPerson *p_func = CPerson::GetCPerson();
CPerson *p_func1 = CPerson::GetCPerson();
p_func->GetCPerson();
system("pause");
return 0;
}