C++学习 2018-12-10

上回书我们说到将飞机和背景都贴到了窗口上,子弹也贴成功了,那么现在就需要让子弹能够自主移动了。

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;
}

------------------------------------------未完待续----------------------------------

猜你喜欢

转载自blog.csdn.net/weixin_42896619/article/details/85852515