上回书我们说到将飞机和背景都贴到了窗口上,子弹也贴成功了,那么现在就需要让子弹能够自主移动了。
1.飞机大战
1.在上一次的飞机大战设计中,我们发现了一个问题:飞机移动会一卡一卡的,这是因为我们在不同的刷新周期进行贴背景图和贴飞机图导致的,需要一个有效的方法进行解决,这里我们引入 双缓冲 的概念。
1) 双缓冲 是指我们在将某些东西(如五本书)从A处移动到B处时,正常的做法是一本一本移动,而为了省时省力,我们找一辆小车,将所有的书都装入小车,我们将小车从A移到B也能够完成任务;
2)这里我们将所有的图片都先贴到一个中间dc上,再将该dc拷贝到主dc上。
1.实现双缓冲
1)首先在GameApp中定义一个中间位图句柄 HBITMAP di_san_fang_tu,以及一个中间dc句柄 HDC di_san_fang_dc;
2)在 PlaneApp 中的 OnCreateGame 函数中获取主窗口句柄的dc,根据该dc创建 di_san_fang_tu 以及 di_san_fang_dc;
3)在 PlaneApp 中的 OnGameShow 函数中将 di_san_fang_tu 作为中间dc来将所有的图片都贴到其上,再将 di_san_fang_tu 拷贝到dc上完成双缓冲。
4)程序如下:
void CPlaneApp::OnCreateGame()
{
dc = GetDC(m_hwnd);
di_san_fang_dc = ::CreateCompatibleDC(dc);
di_san_fang_tu = ::CreateCompatibleBitmap(dc,380, 550);
// 1.初始化背景
back.InitBack(m_h_ins);
// 2.初始化玩家飞机
player.InitPlayer(m_h_ins);
// 3.启动所有的定时器
::SetTimer(m_hwnd, BACK_MOVE_TIMER_ID, 100, 0);
::SetTimer(m_hwnd, PLAYER_MOVE_TIMER_ID, 1, 0);
}
void CPlaneApp::OnGameShow()
{
::SelectObject(di_san_fang_dc, di_san_fang_tu);
back.ShowBack(di_san_fang_dc);
player.ShowPlayer(di_san_fang_dc);
gun_box.AllGunnerShow(di_san_fang_dc);
foe_plane_box.AllFoePlaneShow(di_san_fang_dc);
::BitBlt(dc, 0, 0, 380, 550, di_san_fang_dc, 0, 0, SRCCOPY);
}
2.继续完成Gunner类
1.在设计Gunner类的初始化函数 InitGuuner 时,我们多给这个函数传入两个int型的变量,分别表示新的x值和新的y值。
2.MoveGunner 函数,由于子弹移动时只有y轴上的变化,因此只需要改变y值。
3.ShowGunner 函数完成贴图。
4.主要代码
#include "Gunner.h"
CGunner::CGunner(void)
{
m_hBmpGunner = 0;
x = 0;
y = 0;
}
CGunner::~CGunner(void)
{
::DeleteObject(m_hBmpGunner);
}
void CGunner::InitGuuner(HINSTANCE hIns, int new_x, int new_y)
{
m_hBmpGunner = ::LoadBitmap(hIns, MAKEINTRESOURCE(IDB_GUNNER));
x = new_x;
y = new_y;
}
void CGunner::MoveGunner()
{
y -= 10;
}
void CGunner::ShowGunner(HDC dc)
{
HDC map_dc = ::CreateCompatibleDC(dc);
::SelectObject(map_dc, m_hBmpGunner);
::BitBlt(dc, x, y, 6, 9, map_dc, 0, 0, SRCCOPY);
::DeleteDC(map_dc);
}
3.子弹盒子类GunnerBox
##### 1.我们考虑一个问题:子弹是每一个间隔发射一次,那么我们对每一个子弹进行单独的操作的复杂度是最大的,因此我们将发射出去的子弹统一放入一个链表中,该链表作为一个类的成员来保存。
2.既然GunnerBox类是保存所有子弹的,那么它需要能够同时将这些子弹进行移动(通过遍历链表),并且将这些子弹都显示出来,因此该类的成员函数有:AllGunnerMove 以及 AllGunnerShow。
3.主要代码:
#include "GunnerBox.h"
CGunnerBox::CGunnerBox(void)
{
}
CGunnerBox::~CGunnerBox(void)
{
list<CGunner*>::iterator ite = m_lstGunner.begin(); // m_lstGunner是保存所有子弹的链表,声明为:list<CGunner*> m_lstGunner
while (ite != m_lstGunner.end())
{
delete(*ite);
ite = m_lstGunner.erase(ite);
}
}
void CGunnerBox::AllGunnerMove()
{
list<CGunner*>::iterator ite = m_lstGunner.begin();
while (ite != m_lstGunner.end())
{
if((*ite)->y < 0) // 当子弹的y<0,此时代表着子弹到达了窗口外,在窗口中无法看到,因此将其删掉
{
delete(*ite);
ite = m_lstGunner.erase(ite);
}
else // 否则调用子弹自己的移动函数 MoveGunner
{
(*ite)->MoveGunner();
ite++;
}
}
}
void CGunnerBox::AllGunnerShow(HDC dc)
{
list<CGunner*>::iterator ite = m_lstGunner.begin();
while (ite != m_lstGunner.end())
{
(*ite)->ShowGunner(dc);
ite++;
}
}
4.当创建完子弹盒子之后,我们需要生成子弹,而不能再像之前那样在 PlaneApp 中定义对象来显示子弹了,因为那样一来需要定义许多许多的子弹对象
1.我们定义子弹对象是在 Player 中的 SendGunner 函数里实现的。
void CPlayer::SendGunner(CGunnerBox& gunnerBox, HINSTANCE hIns)
{
CGunner *new_gunner = new CGunner; // new一个子弹对象
new_gunner->InitGuuner(hIns, x+26, y-7); // 初始化子弹对象时将飞机的x与y传入找到飞机头的位置作为子弹的位置
gunnerBox.m_lstGunner.push_back(new_gunner); // 添加该子弹到链表
}
5.完成敌人飞机的接口类
1.因为敌人飞机有三种,一类小飞机,一类中飞机,一类大飞机,虽然有三个类型的敌人飞机,但是这三个敌人飞机的成员、成员函数都一样,只是具体的类型有具体的方法,因此我们需要设计一个接口类来链接这些敌人飞机。
2.设计敌人飞机的接口类FoePlane
1)首先思考,敌人飞机会和子弹、玩家飞机发生碰撞,因此需要导入子弹和玩家飞机的头文件;
2)对于敌人飞机,有规定的血量来区分三种类型的飞机;
3)当敌人飞机爆炸时,我们需要改变显示的飞机图片的位置;
4)可以确定该类有以下成员变量:
#include "CommonInclude.h"
#include "Gunner.h"
#include "Player.h"
class CFoePlane
{
public:
HBITMAP m_hBmpFoePlane;
int m_nBlood;
int m_nShowID;
int x;
int y;
public:
CFoePlane(void);
virtual ~CFoePlane(void);
}
3.设计FoePlane类的成员函数
1)首先是与背景类、player类等类似的函数:InitFoePlane、FoePlaneMove、FoePlaneShow函数;
2)其次,敌人飞机是否爆炸,需要进行判断,因此设计一个内敛布尔函数:inline bool IsBoom;
3)然后,敌人飞机是否被子弹打中,设计函数:virtual bool IsGunnerHitForPlane;
4)敌人飞机若被打中,则需掉血,设计函数:inline void DownBlood;
5)判断敌人飞机是否和玩家飞机发生了碰撞,设计函数:virtual bool IsHitPlayerPlane;
6)主要代码:
#pragma once
#include "CommonInclude.h"
#include "Gunner.h"
#include "Player.h"
class CFoePlane
{
public:
HBITMAP m_hBmpFoePlane;
int m_nBlood;
int m_nShowID;
int x;
int y;
public:
CFoePlane(void);
virtual ~CFoePlane(void);
public:
inline bool IsBoom()
{
if(m_nBlood == 0)
{
return true;
}
return false;
}
virtual bool IsGunnerHitForPlane(const CGunner* pGunner)=0;
virtual bool IsHitPlayerPlane(const CPlayer& plane)=0;
virtual void FoePlaneMove()=0;
virtual void FoePlaneShow(HDC dc)=0;
virtual void InitFoePlane(HINSTANCE hIns)=0;
inline void DownBlood()
{
m_nBlood--;
}
};
4.设计大型飞机 BigFoePlane 类
1)由于所需成员都在接口函数 FoePlane 中,因此只须完成六个虚函数(外加一个虚析构);
2)初始化飞机InitFoePlane,我们需要加载飞机位图,给定该类型飞机的血量以及要显示的位图的编号(其实每一种敌人飞机位图都是一张含有竖着的几个飞机的位图,那么我们需要对这些飞机进行编号,来显示爆炸时的飞机和没爆炸的飞机);
3)我们需要在随机位置产生飞机,因此将飞机的x设置为一个在窗口宽度以内的随机数(这个“窗口宽度”是指减去敌人飞机宽度后的窗口宽度,因为飞机不可能显示为半个飞机);
4)FoePlaneShow 函数用来显示飞机;
5)FoePlaneMove 函数用来控制敌人飞机向下移动的距离;
6)其余两个虚函数暂时不做处理;
6)主要代码:
#include "BigFoePlane.h"
CBigFoePlane::CBigFoePlane(void)
{
}
CBigFoePlane::~CBigFoePlane(void)
{
::DeleteObject(m_hBmpFoePlane);
m_hBmpFoePlane = 0;
}
bool CBigFoePlane::IsGunnerHitForPlane(const CGunner* pGunner)
{
return false;
}
bool CBigFoePlane::IsHitPlayerPlane(const CPlayer& plane)
{
return false;
}
void CBigFoePlane::FoePlaneMove()
{
y += 2;
}
void CBigFoePlane::FoePlaneShow(HDC dc)
{
HDC map_dc = ::CreateCompatibleDC(dc);
::SelectObject(map_dc, m_hBmpFoePlane);
::BitBlt(dc, x, y, 108, 128, map_dc, 0, (3 - m_nShowID)*128, SRCAND);
::DeleteDC(map_dc);
}
void CBigFoePlane::InitFoePlane(HINSTANCE hIns)
{
m_hBmpFoePlane = ::LoadBitmap(hIns, MAKEINTRESOURCE(IDB_BIG));
m_nBlood = 5;
m_nShowID = 3;
x = rand()%(380-108);
y = -128;
}
5.其余两种飞机与该飞机基本完全一致,只是在飞机的大小上略有不同。
其余两种飞机的实现文件:
1)中型飞机:
#include "MidFoePlane.h"
CMidFoePlane::CMidFoePlane(void)
{
}
CMidFoePlane::~CMidFoePlane(void)
{
::DeleteObject(m_hBmpFoePlane);
m_hBmpFoePlane = 0;
}
bool CMidFoePlane::IsGunnerHitForPlane(const CGunner* pGunner)
{
return FALSE;
}
bool CMidFoePlane::IsHitPlayerPlane(const CPlayer& plane)
{
return false;
}
void CMidFoePlane::FoePlaneMove()
{
y += 5;
}
void CMidFoePlane::FoePlaneShow(HDC dc)
{
HDC map_dc = ::CreateCompatibleDC(dc);
::SelectObject(map_dc, m_hBmpFoePlane);
::BitBlt(dc, x, y, 70, 90, map_dc, 0, (2 - m_nShowID)*90, SRCAND);
::DeleteDC(map_dc);
}
void CMidFoePlane::InitFoePlane(HINSTANCE hIns)
{
m_hBmpFoePlane = ::LoadBitmap(hIns, MAKEINTRESOURCE(IDB_MID));
m_nBlood = 3;
m_nShowID = 2;
x = rand()%(380-70);
y = -90;
}
2)小型飞机
#include "SmallFoePlane.h"
CSmallFoePlane::CSmallFoePlane(void)
{
}
CSmallFoePlane::~CSmallFoePlane(void)
{
::DeleteObject(m_hBmpFoePlane);
m_hBmpFoePlane = 0;
}
bool CSmallFoePlane::IsGunnerHitForPlane(const CGunner* pGunner)
{
return FALSE;
}
bool CSmallFoePlane::IsHitPlayerPlane(const CPlayer& plane)
{
return false;
}
void CSmallFoePlane::FoePlaneMove()
{
y += 10;
}
void CSmallFoePlane::FoePlaneShow(HDC dc)
{
HDC map_dc = ::CreateCompatibleDC(dc);
::SelectObject(map_dc, m_hBmpFoePlane);
::BitBlt(dc, x, y, 34, 28, map_dc, 0, (1 - m_nShowID)*28, SRCAND);
::DeleteDC(map_dc);
}
void CSmallFoePlane::InitFoePlane(HINSTANCE hIns)
{
m_hBmpFoePlane = ::LoadBitmap(hIns, MAKEINTRESOURCE(IDB_LITTLE));
m_nBlood = 1;
m_nShowID = 1;
x = rand()%(380-34);
y = -28;
}
------------------------------------------未完待续----------------------------------