[GDI+] 一个基于MFC、GDI+的粒子系统:玫瑰花满屏飞舞 (情人节送祝福的绝佳利器)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/felicityWSH/article/details/70695816

Demo下载 (VS2013-MFC工程)

大家应该在游戏中见过这种场景:很多雨滴或者雪花,在屏幕上动态地飞舞。基于MFC和GDI+,我也实现了一个类似的玫瑰花飞舞效果,先看下运行的效果截图 (要是在代码中再写一段浪漫的话 一定是情人节时最有创意的祝福吧):


具体设计思路:
每一朵花,都看成一个独立的渲染对象,这个对象具有一些属性和参数,在渲染整个窗口时,依次画出每一个渲染对象。很明显,整个运行过程中,需要定时更新对象(旋转、下落、水平移动)同时也要定时更新UI界面,达到动态的效果。

根据分析,每个渲染对象需要这些信息:
待渲染的图片数据,
图片的缩放比例,
对象移动过程中是否旋转,
对象水平移动的x坐标区间,
对象水平移动的速度,
对象垂直下落的速度,

根据以上信息,实时更新图片的位置信息:x坐标、y坐标、旋转角度,再结合图片信息,就可以渲染出一个对象。于是有了以下设计:
typedef std::tr1::shared_ptr<Gdiplus::Image> GDI_IMAGE_PTR; // 智能指针
typedef std::tr1::shared_ptr<CDrawItem> DRAW_ITEM_PTR; // 智能指针
struct tItemParam // 对象参数 与位置更新以及渲染图片有关
{
	GDI_IMAGE_PTR m_pImage; // 图片对象(使用智能指针)
	float m_fScale; // 缩放比例
	bool m_bRotate; // 是否旋转
	int xLeft; // 能移动到的最左侧位置
	int xRight; // 能移动到的最右侧位置
	int speedMove; // 每秒水平方向移动的像素
	int speedDown;  // 每秒下坠的像素
};
class CDrawItem // 一个CDrawItem对象 代表一个图片
{
	tItemParam m_tInfo;
	DWORD m_dwStart;
	DWORD m_dwPreUpdate;
	DWORD m_dwPreRotate;

	bool m_bToRight; // 当前水平方向是否正向右移动
	long m_xPos; // 当前x坐标
	long m_yPos; // 当前y坐标
	Gdiplus::RotateFlipType m_eRotate; // 当前旋转信息
public:
	static RECT s_rcScreen; // 所有对象运动的有效区间 demo中是以屏幕坐标为准

	explicit CDrawItem(tItemParam& info);
	~CDrawItem();
 
// 根据对象中的参数,定时更新对象的位置信息。如果下落到屏幕的底部则返回false,调用者就会将这个对象移除
bool Update(); 
	
// 根据对象当前的位置信息画出对象(实际的渲染功能,就是在这个函数中实现)这个函数会在CDrawPool::Draw(...)中被调用 
void Draw(Gdiplus::Graphics& grh);
};
然后再写一个管理所有对象的类CDrawPool,CDrawPool中会创建一个多线程,多线程函数做以下功能:
1、定时生成一些新的对象,对象的信息(图片、缩放、x范围、移动速度)都是随机的;
2、定时更新所有对象的位置信息;
3、更新对象位置时,如果发现对象已经无效(下落到底部了)就将对象移除
class CDrawPool
{
public:
	CImagePool m_Images;

	CRITICAL_SECTION m_cs;
	vector<DRAW_ITEM_PTR> m_Items;

	HANDLE m_hExitEvent;
	HANDLE m_hThreadUpdate;
public:
	CDrawPool();
	~CDrawPool();

	void Start(); // 开始
	void Stop(); // 结束
	void Draw(Gdiplus::Graphics& grh); // 渲染所有对象
private:
	static unsigned __stdcall ThreadUpdateItems(void* pParam);
	void InnerThread();
};
在函数CDrawPool::Draw(Gdiplus::Graphics& grh)中,依次调用所有对象的渲染函数CDrawItem::Draw(Gdiplus::Graphics& grh)
void CDrawPool::Draw(Gdiplus::Graphics& grh)
{ 
	CAutoLockCS AutoLock(m_cs);

	vector<DRAW_ITEM_PTR>::iterator itr = m_Items.begin();
	for (; itr != m_Items.end() ;++itr)
	{
		(*itr)->Draw(grh); // 调用每一个对象的渲染函数
	}
}
其中多线程函数如下:
void CDrawPool::InnerThread()
{
	DWORD dwPreCreateItem = 0; // 上一次创建一个对象的时刻
	while (!IsHandleSigned(m_hExitEvent, 10))
	{
		CAutoLockCS AutoLock(m_cs);

		vector<DRAW_ITEM_PTR>::iterator itr = m_Items.begin();
		while (itr != m_Items.end() && !IsHandleSigned(m_hExitEvent, 0))
		{
			if ((*itr)->Update()) // 更新对象的位置信息
			{
				itr++;
			}
			else
			{
				itr = m_Items.erase(itr); // 对象已经移动到底 移除对象
			}
		} 

		if (GetTickCount() - dwPreCreateItem >= 100) // 每隔100毫秒 就创建一个新的对象
		{
			dwPreCreateItem = GetTickCount();
			 
			tItemParam info;
			info.image = m_Images.GetRandomImage(); // 从图片池中获取一张随机的图片
			info.m_fScale = CToolFunc::GetRandomNumber(0.2f, 0.4f);
			info.xLeft = CToolFunc::GetRandomNumber(0, CDrawItem::s_rcScreen.right - 100);
			info.xRight = info.xLeft + CToolFunc::GetRandomNumber(50, CDrawItem::s_rcScreen.right - info.xLeft);
			info.speedMove = CToolFunc::GetRandomNumber(10, 200);
			info.speedDown = CToolFunc::GetRandomNumber(10, 200);
			info.m_bRotate = (timeGetTime() % 3) == 0;

			DRAW_ITEM_PTR pItem(new CDrawItem(info));
			m_Items.push_back(pItem);
		}
	}
}
CToolFunc::GetRandomNumber是一个生成随机数的函数:可以生成一个指定范围的随机数。
#include <ctime>
#include <cstdlib>

#define FLOAT_INT_TEST  100000

int CToolFunc::GetRandomNumber(int min, int max)
{
	srand(unsigned(timeGetTime() * timeGetTime()));
	return (min + rand() % (max - min));
}

float CToolFunc::GetRandomNumber(float min, float max)
{
	int left = int(min * FLOAT_INT_TEST);
	int right = int(max * FLOAT_INT_TEST);
	
	int res = GetRandomNumber(left, right);
	return float(res) / FLOAT_INT_TEST;
}
注意,渲染窗口使用了扩展窗口属性WS_EX_LAYERED。如果还不清楚这个属性, 看这篇博文。界面模块的调用如下:
// 定义对象
CDrawPool m_ItemPool;

// 初始化函数
funcInit()
{
	m_ItemPool.m_Images.PushImage(L“C:\\image1.png”);
	m_ItemPool.m_Images.PushImage(L“C:\\image2.png”);
	m_ItemPool.m_Images.PushImage(L“C:\\image3.png”);

	m_ItemPool.Start();
}

// 渲染函数
funcPaint()
{
	HDC hDC = ::GetDC(m_hWnd);

	Gdiplus::Graphics grh(hDC);
	grh.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
	grh.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
	m_ItemPool.Draw(grh);

	::ReleaseDC(m_hWnd, hDC);
}

猜你喜欢

转载自blog.csdn.net/felicityWSH/article/details/70695816
今日推荐