【VC++游戏开发#三】2D篇 —— 游戏之一:空中大战(SpaceWar)

本文由BlueCoder编写   转载请说明出处:

http://blog.csdn.net/crocodile__/article/details/13505997

我的邮箱:bluecoder@yeah.net    欢迎大家和我交流编程心得

我的微博:BlueCoder_黎小华    欢迎光临^_^

 

 

前面两篇回顾了一些MFC编程要领(后期还会随带着讲一点儿,不过不是重点了),今天就进入游戏主题,搞一个小游戏


前段时间,看见Yorhom朋友用html5+Qt开发了一款游戏——SpaceWar,感觉挺有意思的——刚好最近一直在研究了MFC以及GDI+,可以着手将这款游戏移植到MFC中(本来可以提前几天发布的,但是前两天参加了一个竞赛,也就耽搁了一下)——这里还是很感谢Yorhom分享的素材,这位朋友很不错,html5游戏开发爱好者

 

 

接下来,我就一步一步介绍这款MFC版游戏的实现流程

 

————————————————————————————————————————————————————————————

————————————————————————————————————————————————————————————

 

一、游戏界面以及玩法的介绍

 

(1). 游戏开始界面

 

 

 

 

(2). 游戏界面

 

 

 

 

 

(3). 游戏结束界面

 

 

 

 

 

 

二、主要的实现细节分析

 

这里,我将详细讲解我在写这个游戏中用到的主要原理、思想

 

(1). 首先是游戏背景的移动(这个就是类似于幻灯片播放的原理), 我做了一个示意图,先来看看:

(ps: 红框区域的1、2分别表示在内存中绘制的两张连续的背景 , 蓝色区域表示窗口客户区)

如此循环,给人视角效果就是这两张背景在连续的变换

 

(2). 子弹的移动——要保证在不同方向上的移动速度相同, 因此需要对移动速度在水平方向(x轴)和垂直方向(y轴)求分量,这个是简单的物理问题,大家应该能明白:

(下图是示意图)

 

(3). 音效播放的处理

1>. 点击子弹以及爆炸音效都使用PlaySound,但是必须采用异步播放模式,以免游戏界面出现卡顿现象。因为如果采用同步播放,会播放完后才会返回;异步播放会直接返回,不会耽搁时间

2>. 游戏背景音乐要重复播放,除非玩家不再玩了

 

 

(4). 碰撞检测

首先要使用一个函数:PtInRect(CPoint pt)-->检测点pt是否在一个矩形区域Rect中

而对于子弹与怪物机的碰撞检测,就需要检测子弹的矩形区域的四个点(左上、下,右上、下),只要有一个点在怪物机的矩形区域内,就表示子弹射击到了一个怪物机

 

 

 

三、代码详解

 

(1). 类视图

 

 

(2). 先来浏览一下头文件SpaceWarView.h

 

//计时器ID
#define ID_BULLET	100//发子弹
#define ID_MONSTER	101//处理怪物
#define ID_MOUVEBK	102//移动背景

//英雄机和怪物机
typedef struct img
{
	CImage	png;
	CRect	rect;
	int		speed;
	bool	isOut;
}IMG;

//子弹
typedef struct blt
{
	CImage	png;
	CRect	rect;
	int		speed;
	bool	isOut;
	int		ix;//x移动坐标
	int		iy;//y移动坐标
}BULLET;

//显示状态:	  开始   游戏中   结束
enum	STATE{START, RUNNING, END};


class CSpaceWarView : public CView
{
protected: // 仅从序列化创建
	CSpaceWarView();
	DECLARE_DYNCREATE(CSpaceWarView)

// 属性
public:
	CSpaceWarDoc* GetDocument() const;

// 操作
public:

// 重写
public:
	virtual void OnDraw(CDC* pDC);  // 重写以绘制该视图
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:

// 实现
public:
	virtual ~CSpaceWarView();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif

protected:

//数据成员
private:
	//显示状态
	STATE	m_state;

	CDC		m_bufferDC;//缓冲DC
	CBitmap	m_bufferBmp;//缓冲Bmp

	CSize	m_sClient;//客户区大小
	CPoint	m_ptBltOrg;//子弹的起点

	//开始背景
	struct sbk
	{
		CImage bk;
		CImage normal;
		CImage selected;
		CRect  rect;
		bool   isSelected;
	}m_startBk;

	//游戏背景
	struct gbk
	{
		CImage	fir;//第一张背景
		CImage	sec;//第二张背景
		BOOL	isFir;//标记左边是否是第一张
		int		curx;//左边背景的x坐标
	}m_gameBk;

	//结束背景
	struct ebk
	{
		CImage bk;
		CImage gameover;
		CImage againNor;//重新开始
		CImage againSel;
		CImage exitNor;//退出
		CImage exitSel;
		CRect  rGo;//gameover的rect
		CRect  rRes;//restart的rect
		CRect  rExit;//exit的rect
		bool   isExitSel;
		bool   isResSel;
	}m_endBk;

	//暂停与继续
	struct sg
	{
		CImage	img;
		BOOL	isStop;
		CRect	rect;
	}m_stopGoOn;

	IMG				m_hero;//英雄机
	vector<BULLET>	m_bullet;//子弹
	vector<IMG>		m_vecMons;//怪物机

	size_t		m_score;//存储分数-每射击成功一次, 加5分

	const int	m_max;//标记怪物最多个数

//成员函数
private:
	//游戏的三种状态
	void StartUI();//开始
	void RunningUI();//游戏中
	void EndUI();//结束

	//开始游戏
	void StartGame();
	//结束游戏
	void EndGame();
	//暂停游戏
	void PauseGame();
	//继续游戏
	void GoOnGame();

	//由于调用次数很多, 设为内联函数
	inline void StickBk();//贴背景
	inline void StickBullet();//贴子弹
	inline void BulletOut();//排除出界的子弹
	inline void MoveMonster();//移动怪物机
	inline void MoveBullet();//移动子弹
	inline int  Beat(int i);//返回子弹i射击到的怪物机j

	//重新启动怪物机
	inline void RestartMonster(bool isStart, int i);
	//为索引为i的子弹求水平、垂直速度
	inline void GetXY(CPoint org, CPoint end, int i);

// 生成的消息映射函数
protected:
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	afx_msg void OnDestroy();
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
};

 


 

(3). 通过头文件可以看见,我定义了一个枚举变量,用来控制游戏界面的状态

//显示状态:	  开始   游戏中   结束
enum	STATE{START, RUNNING, END};


根据游戏状态绘制游戏界面

void CSpaceWarView::OnDraw(CDC* pDC)
{
	CSpaceWarDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: 在此处为本机数据添加绘制代码

	//创建内存位图、内存DC以便在内存中绘图, 实现双缓冲
	m_bufferDC.CreateCompatibleDC(NULL);
	m_bufferBmp.CreateCompatibleBitmap(pDC,
		m_gameBk.fir.GetWidth() * 2, m_gameBk.fir.GetHeight());

	m_bufferDC.SelectObject(m_bufferBmp);

	switch(m_state)
	{
	//绘制开始界面
	case START:
		StartUI();
		break;

	//绘制游戏界面
	case RUNNING:
		RunningUI();
		break;

	//绘制结束界面
	case END:
		EndUI();
		break;
	}

	//将内存中的图贴到客户区中
	pDC->BitBlt(0, 0, m_sClient.cx, m_sClient.cy, &m_bufferDC, 0, 0, SRCCOPY);

	//释放内存位图和内存DC
	m_bufferBmp.DeleteObject();
	m_bufferDC.DeleteDC();
}


 

 

(4). 动态贴游戏背景图片

 

//贴背景
void CSpaceWarView::StickBk()
{
	//如果到了左边界,就换图
	if(m_gameBk.curx == -m_gameBk.fir.GetWidth())
	{
		m_gameBk.curx	= 0;
		m_gameBk.isFir	= m_gameBk.isFir? 0 : 1;
	}

	int width = m_gameBk.fir.GetWidth();

	//如果第一张图在前面,就先绘制第一张
	if(m_gameBk.isFir)
	{
		m_gameBk.fir.BitBlt(m_bufferDC, 
			m_gameBk.curx, 0, SRCCOPY);

		m_gameBk.sec.BitBlt(m_bufferDC, 
			m_gameBk.curx + width, 0, SRCCOPY);
	}
	//第二张图在前面,先绘制第二张
	else
	{
		m_gameBk.sec.BitBlt(m_bufferDC, 
			m_gameBk.curx, 0, SRCCOPY);

		m_gameBk.fir.BitBlt(m_bufferDC, 
			m_gameBk.curx + width, 0, SRCCOPY);
	}
}


 

(5). 对速度求解x、y轴方向的速度分量

 

//为索引为i的子弹求水平、垂直速度
void CSpaceWarView::GetXY(CPoint org, CPoint end, int i)
{
	//求得子弹的中心位置
	org.x += m_bullet[i].png.GetWidth();
	org.y += m_bullet[i].png.GetHeight();

	//如果两点在一条垂直直线上
	if(org.x == end.x)
	{
		m_bullet[i].ix = 0;
		m_bullet[i].iy = m_bullet[i].speed;

		if(org.y < end.y)
			m_bullet[i].iy = - m_bullet[i].iy;
	}
	//如果两点在一条水平直线上
	else if(org.y == end.y)
	{
		m_bullet[i].iy = 0;
		m_bullet[i].ix = m_bullet[i].speed;

		if(org.x > end.x)
			m_bullet[i].ix = - m_bullet[i].ix;
	}
	else
	{
		//求斜率k(正切)、倾斜角angle、cos(余弦)、sin(正弦)
		double k		= (org.y - end.y) * 1.0 / (org.x - end.x);
		double angle	= atan(k);
		double cosine	= cos(angle);
		double sine		= sin(angle);

		/*
			将直线上的速度speed分解为x、y轴上的分量
			分别表示x上的移动速度、y上的移动速度
		*/
		m_bullet[i].ix = (int)fabs(m_bullet[i].speed * cosine);

		if(org.x > end.x)
			m_bullet[i].ix = - m_bullet[i].ix;

		m_bullet[i].iy = (int)fabs(m_bullet[i].speed * sine);

		if(org.y > end.y)
			m_bullet[i].iy = - m_bullet[i].iy;
	}
}


 

(6). 点击鼠标左键发送子弹

 

//如果没有暂停游戏
if(!m_stopGoOn.isStop)
{
	//按下鼠标左键, 发送子弹
	for(size_t i=0; i<m_bullet.size(); i++)
	{
		if(m_bullet[i].isOut)
		{
			m_bullet[i].speed = 20;

			int x = m_ptBltOrg.x;
			int y = m_ptBltOrg.y;
			m_bullet[i].rect.SetRect(x, y, 
				m_bullet[i].png.GetWidth() + x,
				m_bullet[i].png.GetHeight() + y);

			GetXY(m_ptBltOrg, point, i);

			m_bullet[i].isOut = false;
			break;
		}
	}
	//异步播放射击音乐
	PlaySound (TEXT ("res\\music\\attack.wav"), 
		NULL, SND_FILENAME | SND_ASYNC);
}


 

(7). 在移动怪物机时检测怪物机是否出了边界

 

//在移动怪物机过程中判断怪物机是否出了边界
void CSpaceWarView::MoveMonster()
{
	for(size_t i=0; i<m_vecMons.size(); i++)
	{
		//水平移动怪物机只需要变化rect的left和right
		m_vecMons[i].rect.left -= m_vecMons[i].speed;

		//如果怪物机出了边界, 进入游戏结束状态
		if(m_vecMons[i].rect.left < -m_vecMons[i].rect.Width())
		{
			m_state = END;

			EndGame();

			InvalidateRect(NULL, FALSE);
			break;
		}

		m_vecMons[i].rect.right -= m_vecMons[i].speed;
	}
}

 

 

(8). 检测子弹是否射击到了怪物机

 

//返回子弹i射击到的怪物机j(-1表示未射击到)
int CSpaceWarView::Beat(int i)
{
	CRect r = m_bullet[i].rect;

	for(size_t j=0; j<m_vecMons.size(); j++)
	{
		if(m_vecMons[j].rect.PtInRect(CPoint(r.left, r.top)) ||
			m_vecMons[j].rect.PtInRect(CPoint(r.right, r.top)) ||
			m_vecMons[j].rect.PtInRect(CPoint(r.left, r.bottom)) ||
			m_vecMons[j].rect.PtInRect(CPoint(r.right, r.bottom)) )
		{
			return j;//射击到了索引为j的怪物机
		}
	}
         //未射击到怪物机
	return -1;
}


 

(9). 在移动子弹的时检测是否射击到了怪物机

 

//移动子弹
void CSpaceWarView::MoveBullet()
{
	for(size_t i=0; i<m_bullet.size(); i++)
	{
		if(!m_bullet[i].isOut)
		{
			//返回当前子弹射击到的怪物机索引
			int index_beat = Beat(i);

			//如果子弹射击到了怪物机
			if(index_beat != -1)
			{
				//计分
				m_score += 5;

				//重新启动当前怪物机
				RestartMonster(false, index_beat);

				//标记当前子弹出界了
				m_bullet[i].isOut = true;

				//异步播放爆炸音乐
				PlaySound (TEXT ("res\\music\\die.wav"), NULL, SND_FILENAME | SND_ASYNC);
				continue;
			}

			//移动子弹
			m_bullet[i].rect.left += m_bullet[i].ix;
			m_bullet[i].rect.right += m_bullet[i].ix;
			m_bullet[i].rect.top += m_bullet[i].iy;
			m_bullet[i].rect.bottom += m_bullet[i].iy;
		}
	}
}


 

(10). 释放游戏所使用的所有资源

 

//善后工作
void CSpaceWarView::OnDestroy()
{
	CView::OnDestroy();

	/*
	程序退出之前, 释放所有内存资源
	*/

	EndGame();

	//释放开始界面资源
	m_startBk.bk.ReleaseGDIPlus();
	m_startBk.normal.ReleaseGDIPlus();
	m_startBk.selected.ReleaseGDIPlus();

	//释放游戏界面资源
	m_gameBk.fir.ReleaseGDIPlus();
	m_gameBk.sec.ReleaseGDIPlus();

	m_hero.png.ReleaseGDIPlus();

	for(size_t i=0; i<m_bullet.size(); i++)
	{
		m_bullet[i].png.ReleaseGDIPlus();
	}
	
	for(size_t i=0; i<m_vecMons.size(); i++)
	{
		m_vecMons[i].png.ReleaseGDIPlus();
	}

	//释放结束界面资源
	m_endBk.bk.ReleaseGDIPlus();
	m_endBk.gameover.ReleaseGDIPlus();
}


 

 

 

四、免费资源下载

点击下载源代码

点击下载游戏安装程序 

  • 48
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 65
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 65
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值