【VC++游戏开发#九】2D篇 —— 粒子系统(二):平安夜特别版——星光四射

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

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

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

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




Hi,大家好,I'm here to see you again:)

今晚是一个特别而美好的日子哈——所以呢,BlueCoder在这里祝大家圣诞快乐Merry Christmas~


……


Ok,我们继续聊聊2D游戏效果的那些事儿~


今天呢,在这个美好的夜晚,BlueCoder为大家敬献一个漂亮的程序:粒子系统高级应用之星光四射,下面进入今晚的效果模拟的殿堂^.~


你是否迫不及待看看程序的效果呢?呵呵——Then, go ahead……



一、效果演示


运行效果截图:










可以发现:星星想四面八方移动,并且随着移动时间增长,星星也越变越大,还是比较炫吧,O(∩_∩)O哈哈~




二、准备工作


1、一张背景,当然在这个特殊的夜晚就要选择有圣诞节氛围的图片作为背景,准备12张不同颜色的星星

2、一首悦耳的背景音乐

3、类视图




三、实现细节


我编写这个程序的灵感来自在网上偶然看见的一张图,如下:


看着这些星星,大小不一,加上联想到自己最近要再来做一个粒子系统的高级应用Demo,因此这个美好的想法就诞生了 : >


本次编程的思想还是沿用之前的面向对象的编程思想,电脑屏幕前的你觉得应该封装几个类、又该如何实现呢?

恩,对(我姑且认为 you & me 的想法一致哈),我封装了两个类:CStar(负责星星的处理操作)CScene(负责贴背景和播放背景音乐)


实现剖析原理:

1、贴背景与播放音乐就不用详解了,这是之前所有MFC程序Demo的基础

2、主要来剖析一下星光四射实现的原理

在实际的笛卡尔坐标系中分四个象限:一、二、三、四(很简单,不用细说了)

而在windows窗口中,默认的窗口坐标系和我们熟悉的笛卡尔坐标系是不一样的,到底怎么个不一样法呢?来看看我为大家绘制的图解,你就能明白了:


(注:熟悉Win32的朋友可能知道,这个关系到windows窗口的一个技术:映射模式,这里windows窗口默认的映射模式是MM_TEXT,因此我们也可以用这个技术来让windows窗口使用笛卡尔坐标系模式,这里就不去赘述了,不熟悉或者感兴趣的朋友可以去看看P先生的《windows程序设计》关于映射模式的章节,里面有详细说明)


注意上图中四个象限的坐标x、y的符号,表示随着当前象限中的箭头所指方向移动,横坐标或纵坐标的变化趋势,负号表示越来越小;反之,越来越大


ok,下面结合一段核心代码来继续理解:(注意下面四个象限设置的mx、my的符号,和上图是对应的)

以下代码是设置星星数组参数(现在,你只需要关注设置象限那一块)

for(int i=0; i<STAR_MAX; i++)
{
	//设置星星数组参数
	m_star[i].id = rand()%STAR_TYPE;
	m_star[i].x = x;
	m_star[i].y = y;
	m_star[i].time = 0;
	m_star[i].exist = true;

	//
	//让星星在四个不同的象限移动
	//
	switch(i%4)
	{
	//第一象限
	case 0:
		m_star[i].mx = 3 + rand()%20;
		m_star[i].my = - (3 + rand()%20);
		break;

	//第二象限
	case 1:
		m_star[i].mx = - (3 + rand()%20);
		m_star[i].my = - (3 + rand()%20);
		break;

	//第三象限
	case 2:
		m_star[i].mx = - (3 + rand()%20);
		m_star[i].my = 3 + rand()%20;
		break;

	//第四象限
	case 3:
		m_star[i].mx = 3 + rand()%20;
		m_star[i].my = 3 + rand()%20;
	}
}


但愿经过我的精心讲解,电脑屏幕前的你已经懂了,或者多少已有一些思绪,呵呵




四、代码剖析


还是主要讲解一下我自行封装的两个类,其余在View窗口中的代码直接贴出(但依然注有较为详细的注释)


我封装的两个类:

1、CScene

这个类较为简单,也直接贴出来吧^:

头文件中的类核心定义

//------变量成员-----
private:
	CImage	m_bg;//背景
	CRect	m_rClient;//窗口客户区范围

//------成员函数-----
private:
	//绘制字符串(按照特定的字体样式)
	void DrawString(CDC&);

public:
	bool Init();//初始化场景
	void Paint(CDC&);//绘制背景
	//设置窗口客户区范围
	void SetClientRect(CRect);
	void Release();//释放内存资源


函数成员的实现:

//----------------------------
//		成员函数的实现
//----------------------------

//初始化
bool CScene::Init()
{
	m_bg.Load(L"res\\bg.jpg");

	//如果加载失败
	if(m_bg.IsNull())
	{
		return false;
	}

	m_rClient.SetRectEmpty();

	mciSendString(L"open res\\bgm.mp3 alias bgm", NULL, 0, NULL);
	mciSendString(L"play bgm repeat", NULL, 0, NULL);
	return true;
}

//设置客户区范围
void CScene::SetClientRect(CRect rClient)
{
	m_rClient = rClient;
}

//绘制字符串
void CScene::DrawString(CDC &cdc)
{
	CFont font;
	font.CreatePointFont(110, L"微软雅黑");
	cdc.SelectObject(font);

	cdc.SetBkMode(TRANSPARENT);

	int y = m_rClient.bottom - 160;

	cdc.SetTextColor(RGB(0, 180, 0));
	cdc.TextOutW(0,    y, L"谨在平安夜这个美好的时刻,");
	cdc.TextOutW(0, y+20, L"为大家献上这个漂亮的程序~");
	
	cdc.SetTextColor(RGB(80, 147, 221));
	cdc.TextOutW(0, y+50, L"祝大家在新的一年中梦想成真^_^~");
	cdc.TextOutW(0, y+70, L"记住,别忘了自己最初的梦想~");
	cdc.TextOutW(0, y+90, L"别放弃,Try Your Best O(∩_∩)O~");

	cdc.SetTextColor(RGB(240, 50, 50));
	cdc.TextOutW(0, y+120, L"~Hi, Guys, Merry Christmas~");
}

//绘制场景
void CScene::Paint(CDC &cdc)
{
	cdc.SetStretchBltMode(COLORONCOLOR);
	m_bg.StretchBlt(cdc, m_rClient, SRCCOPY);

	DrawString(cdc);
}

//释放内存资源
void CScene::Release()
{
	mciSendString(L"close bgm", NULL, 0, NULL);
	m_bg.Destroy();
}




2、CStar

(1)先来看看星星结构体的定义,这是粒子系统中粒子的模板定义:

//星星结构体(粒子)
typedef struct star
{
	int id;//关联CImage对象的索性号(以匹配不同颜色的星星)
	int x;//横坐标
	int y;//纵坐标
	int mx;//横坐标每次移动的位移
	int my;//纵坐标每次移动的位移
	int time;//移动的次数(扩展:还用于确定当前星星的大小)
	bool exist;//是否存在
}STAR;


(2)由于有12张不同颜色的星星图片(png),因此需要一个有12个元素的CImage数组

(3)另外,还需要一个粒子Star数组来控制每一个粒子的运动状态

(4)然后我们还需要一个int成员m_count来标识当前星星存在的个数

以下是该类的成员:

//--静态成员--
public:
	//星星的最大颗数
	static const int STAR_MAX = 150;
	//星星的种类数
	static const int STAR_TYPE = 12;

//--成员变量--
private:
	STAR	m_star[STAR_MAX];//星星结构体
	CImage	m_img[STAR_TYPE];//星星png
	int		m_count;//星星的当前数目计数
	CSize	m_sClient;//view窗口客户区大小

//--成员函数--
public:
	bool Init();//初始化
	void SetStarParam();//设置星星的参数
	void MoveStar();//移动星星
	void Paint(CDC&);//绘制星星
	void Release();//释放内存资源

函数成员的实现:

//-----------------------------------
//			成员函数的实现
//-----------------------------------


//初始化
bool CStar::Init()
{
	CString path;

	//加载图片
	for(int i=0; i<STAR_TYPE; i++)
	{
		path.Format(L"res\\%d.png", i+1);
		m_img[i].Load(path);

		//加载失败
		if(m_img[i].IsNull())
		{
			return false;
		}
	}

	m_count = 0;
	m_sClient.SetSize(790, 568);

	return true;
}

//设置星星参数
void CStar::SetStarParam()
{
	srand(GetTickCount());

	//为了确保效果更佳, 需保证星星起点在窗口中央处
	int x = rand()%(m_sClient.cx / 2) + 
		m_sClient.cx / 4;
	int y = rand()%(m_sClient.cy / 2) + 
		m_sClient.cx / 4;

	for(int i=0; i<STAR_MAX; i++)
	{
		//设置星星数组参数
		m_star[i].id = rand()%STAR_TYPE;
		m_star[i].x = x;
		m_star[i].y = y;
		m_star[i].time = 0;
		m_star[i].exist = true;

		//
		//让星星在四个不同的象限移动
		//
		switch(i%4)
		{
		//第一象限
		case 0:
			m_star[i].mx = 3 + rand()%20;
			m_star[i].my = - (3 + rand()%20);
			break;

		//第二象限
		case 1:
			m_star[i].mx = - (3 + rand()%20);
			m_star[i].my = - (3 + rand()%20);
			break;

		//第三象限
		case 2:
			m_star[i].mx = - (3 + rand()%20);
			m_star[i].my = 3 + rand()%20;
			break;

		//第四象限
		case 3:
			m_star[i].mx = 3 + rand()%20;
			m_star[i].my = 3 + rand()%20;
		}
	}

	//初始化后, 当前计数就是最大值
	m_count = STAR_MAX;
}

//移动星星
void CStar::MoveStar()
{
	//如果当前没有星星就需要重新设置星星的相关属性
	if(m_count == 0)
	{
		SetStarParam();
	}

	for(int i=0; i<STAR_MAX; i++)
	{
		//如果星星存在, 才需要移动
		if(m_star[i].exist)
		{
			//移动坐标
			m_star[i].x += m_star[i].mx;
			m_star[i].y += m_star[i].my;

			//移动次数加1
			m_star[i].time ++;

			//如果星星出界或则移动的次数超过50次, 则星星不存在了
			if(m_star[i].time > 50 ||
				m_star[i].x < -20 ||
				m_star[i].x > m_sClient.cx ||
				m_star[i].y < -20 ||
				m_star[i].y > m_sClient.cy)
			{
				m_star[i].exist = false;
				m_count --;//计数减1
			}
		}
	}
}

//绘制星星
void CStar::Paint(CDC &cdc)
{
	cdc.SetStretchBltMode(COLORONCOLOR);

	for(int i=0; i<STAR_MAX; i++)
	{
		//星星存在才需要绘制
		if(m_star[i].exist)
		{
			int xy = m_star[i].time;
			/*
			根据移动次数来决定星星显示的大小
			随着移动次数的增加, 星星显示的也就越来越大
			(这就是你看到的星星逐渐变大的效果)
			*/
			m_img[m_star[i].id].TransparentBlt(
				cdc, m_star[i].x, m_star[i].y, 
				xy, xy, RGB(255, 255, 255));
		}
	}
}

//释放内存资源
void CStar::Release()
{
	for(int i=0; i<STAR_TYPE; i++)
	{
		m_img[i].Destroy();
	}
}



3、View窗口中相关实现:

//移动星星计时器ID
#define ID_MOVESTAR 100


//-----------成员变量-------------
private:
	CScene	m_scene;//场景类实例对象
	CStar	m_star;//星星类实例对象
	CRect	m_rClient;//View窗口Rect


int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	//----------------初始化操作---------------

	if(!m_scene.Init() ||
		!m_star.Init())
	{
		AfxMessageBox(L"图片资源加载失败");
		exit(0);
	}

	m_star.SetStarParam();

	SetTimer(ID_MOVESTAR, 40, NULL);
	return 0;
}

void CChildView::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);

	//获取view窗口客户区大小
	GetClientRect(m_rClient);
	m_scene.SetClientRect(m_rClient);
}

void CChildView::OnTimer(UINT_PTR nIDEvent)
{
	m_star.MoveStar();

	InvalidateRect(NULL, false);
	CWnd::OnTimer(nIDEvent);
}

void CChildView::OnPaint() 
{
	CPaintDC dc(this); // 用于绘制的设备上下文

	CDC bufferDC;
	CBitmap bufferBmp;

	//--------------双缓冲贴图---------------------
	bufferDC.CreateCompatibleDC(NULL);
	bufferBmp.CreateCompatibleBitmap(&dc,
		m_rClient.Width(), m_rClient.Height());
	bufferDC.SelectObject(bufferBmp);

	m_scene.Paint(bufferDC);
	m_star.Paint(bufferDC);

	dc.BitBlt(0, 0, m_rClient.Width(), m_rClient.Height(),
		&bufferDC, 0, 0, SRCCOPY);

	//释放内存资源
	bufferBmp.DeleteObject();
	bufferDC.DeleteDC();
}

void CChildView::OnDestroy()
{
	CWnd::OnDestroy();

	//-------------------释放内存资源------------------

	m_scene.Release();
	m_star.Release();
}




五、零积分资源下载

点击下载源代码




送大家一句正能量的箴言:

       优秀是一种习惯。

——亚里士多德

我们从现在起就要把优秀变成一种习惯,使我们的优秀行为习以为常,变成我们的第二天性。让我们习惯性地去创造性思考,习惯性地去认真做事情,习惯性地对别人友好,习惯性地欣赏大自然。


电脑屏幕前的你,养成了优秀的习惯吗?


评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值