【Visual C++】游戏开发笔记二十三 游戏基础物理建模 五 粒子系统模拟 二
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.****.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
作者:毛星云 邮箱: [email protected] 期待着与志同道合的朋友们相互交流
本节在笔记二十二的基础上继续讲解了例子系统的模拟与实际运用,着重讲解和分析了基于例子系统的一个“星光绽放
demo”,最后盘点了史诗级游戏作品《暗黑破坏神3》上市首周所创下的**。
一.基础知识讲解
1. 概念与思路
基本的粒子系统概念在笔记二十二中已经讲过了,还不太清楚的朋友请移步前篇:
【Visual C++】游戏开发笔记二十二 游戏基础物理建模(四) 粒子系统模拟(一)
本节讲解的星光绽放demo相当于是一个模拟爆炸(或者说是烟花)特效的demo,浅墨认为这个特效拿出来讲解很多必要性,它可以为很多问题带来的思路的火花。这个demo之中,绽放(爆炸)点为在窗口中由随机数产生的一个位置,绽放(爆炸)后,会出现很多星光以不同的速度向四方飞散而去,当粒子飞出窗口后或者超出时间后便会消失。每一次爆炸所出现的粒子全部消失后,便会重新出现绽放(爆炸)的画面,以产生不断绽放星光的效果。
2.“星光”粒子的构造
首先我们来看一下这次如何用结构体来构造出星光粒子:
struct flystar{ int x; //星光所在的x坐标 int y; //星光所在的y坐标 int vx; //星光x方向的速度 int vy; //星光y方向的速度 int lasted; //星光存在的时间 BOOL exist; //星光是否存在}flystar[50];
6个成员分别为,粒子坐标两个值,粒子方向两个值,持续时间lasted,和粒子是否存在的标识exist。
3.核心代码讲解
最重要的当然是我们的MyPaint()绘图函数:
//全局变量声明HINSTANCE hInst;HBITMAP bg,star,mask; //用于贴图的三个HBITMAP变量HDC hdc,mdc,bufdc;HWND hWnd;RECT rect;int i,count; //定义count用于计数//****自定义绘图函数*********************************// 1.窗口贴图// 2.实现星光绽放的效果void MyPaint(HDC hdc){//创建粒子 if(count == 0) //随机设置爆炸点 { int x=rand()%rect.right; int y=rand()%rect.bottom; for(i=0;i<50;i++) //产生星光粒子 { flystar[i].x = x; flystar[i].y = y; flystar[i].lasted = 0; //设定该粒子存在的时间为零 if(i%2==0) //按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。 { flystar[i].vx = -(1+rand()%15); flystar[i].vy = -(1+rand()%15); } if(i%2==1) { flystar[i].vx = 1+rand()%15; flystar[i].vy = 1+rand()%15; } if(i%4==2) { flystar[i].vx = -(1+rand()%15); flystar[i].vy = 1+rand()%15; } if(i%4==3) { flystar[i].vx = 1+rand()%15; flystar[i].vy = -(1+rand()%15); } flystar[i].exist = true; //设定粒子存在 } count = 50; //50个粒子由for循环设置完成后,我们将粒子数量设为50,代表目前有50颗星光 } //先在内存dc中贴上背景图片 SelectObject(bufdc,bg); BitBlt(mdc,0,0,rect.right,rect.bottom,bufdc,0,0,SRCCOPY); for(i=0;i<50;i++) { if(flystar[i].exist) //判断粒子是否还存在,若存在,则根据其坐标(flystar[i].x,flystar[i].y)进行贴图操作 { SelectObject(bufdc,mask); BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCAND); SelectObject(bufdc,star); BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCPAINT); //计算下一次贴图的坐标 flystar[i].x+=flystar[i].vx; flystar[i].y+=flystar[i].vy; //在每进行一次贴图后,将粒子的存在时间累加1. flystar[i].lasted++; //进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减 if(flystar[i].x<=-10 || flystar[i].x>rect.right || flystar[i].y<=-10 || flystar[i].y>rect.bottom || flystar[i].lasted>50) { flystar[i].exist = false; //删除星光粒子 count--; //递减星光总数 } } } //将mdc中的全部内容贴到hdc中 BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);}
相关的书写思路在代码注释中浅墨已经写得比较清晰了。
这段代码的书写整体思路即:
第一步,判断粒子是否创建,若星光数量count不为0,则直接跳到第四步进行相关贴图操作。否则需按每步顺序完成粒子的初始化。
第二步,随机设置绽放点。
第三步,创建各个粒子(为结构体各属性赋值)。
第四步,在内存dc上贴上背景图片。
第五步,对各个粒子进行贴图操作并
第六步,对某些值,如count,exist进行特殊的处理
第七步,将mdc(内存dc)中的内容贴到hdc中,完成最后在屏幕上的显示。
二、详细注释的源代码欣赏
OK,讲解完成,现在我们就贴出详细注释的源代码:
#include "stdafx.h"#include <stdio.h>//全局变量声明HINSTANCE hInst;HBITMAP bg,star,mask; //用于贴图的三个HBITMAP变量HDC hdc,mdc,bufdc;HWND hWnd;RECT rect;int i,count; //定义count用于计数struct flystar{ int x; //星光所在的x坐标 int y; //星光所在的y坐标 int vx; //星光x方向的速度 int vy; //星光y方向的速度 int lasted; //星光存在的时间 BOOL exist; //星光是否存在}flystar[50];//全局函数声明ATOM MyRegisterClass(HINSTANCE hInstance);BOOL InitInstance(HINSTANCE, int);LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);void MyPaint(HDC hdc);//****WinMain函数,程序入口点函数************************************** int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ MSG msg; MyRegisterClass(hInstance); //初始化 if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } //消息循环 while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam;}//****设计一个窗口类,类似填空题,使用窗口结构体********************* ATOM MyRegisterClass(HINSTANCE hInstance){ WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = NULL; wcex.hCursor = NULL; wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = "maple"; wcex.hIconSm = NULL; return RegisterClassEx(&wcex);}//****初始化函数************************************* // 1.加载位图资源// 2.取得内部窗口区域信息 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){ HBITMAP bmp; hInst = hInstance; hWnd = CreateWindow("maple", "浅墨的绘图窗口" , WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } MoveWindow(hWnd,10,10,600,450,true); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); hdc = GetDC(hWnd); mdc = CreateCompatibleDC(hdc); bufdc = CreateCompatibleDC(hdc); bmp = CreateCompatibleBitmap(hdc,640,480); SelectObject(mdc,bmp); bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,rect.right,rect.bottom,LR_LOADFROMFILE); star = (HBITMAP)LoadImage(NULL,"star.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE); mask = (HBITMAP)LoadImage(NULL,"mask.bmp",IMAGE_BITMAP,30,30,LR_LOADFROMFILE); GetClientRect(hWnd,&rect); SetTimer(hWnd,1,0,NULL); MyPaint(hdc); return TRUE;}//****自定义绘图函数*********************************// 1.窗口贴图// 2.实现星光绽放的效果void MyPaint(HDC hdc){//创建粒子 if(count == 0) //随机设置爆炸点 { int x=rand()%rect.right; int y=rand()%rect.bottom; for(i=0;i<50;i++) //产生星光粒子 { flystar[i].x = x; flystar[i].y = y; flystar[i].lasted = 0; //设定该粒子存在的时间为零 if(i%2==0) //按粒子编号i来决定粒子在哪个象限运动,且x,y方向的移动速度随机为1—15之间的一个值,由1+rand()%15来完成。 { flystar[i].vx = -(1+rand()%15); flystar[i].vy = -(1+rand()%15); } if(i%2==1) { flystar[i].vx = 1+rand()%15; flystar[i].vy = 1+rand()%15; } if(i%4==2) { flystar[i].vx = -(1+rand()%15); flystar[i].vy = 1+rand()%15; } if(i%4==3) { flystar[i].vx = 1+rand()%15; flystar[i].vy = -(1+rand()%15); } flystar[i].exist = true; //设定粒子存在 } count = 50; //50个粒子由for循环设置完成后,我们将粒子数量设为50,代表目前有50颗星光 } //先在内存dc中贴上背景图片 SelectObject(bufdc,bg); BitBlt(mdc,0,0,rect.right,rect.bottom,bufdc,0,0,SRCCOPY); for(i=0;i<50;i++) { if(flystar[i].exist) //判断粒子是否还存在,若存在,则根据其坐标(flystar[i].x,flystar[i].y)进行贴图操作 { SelectObject(bufdc,mask); BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCAND); SelectObject(bufdc,star); BitBlt(mdc,flystar[i].x,flystar[i].y,30,30,bufdc,0,0,SRCPAINT); //计算下一次贴图的坐标 flystar[i].x+=flystar[i].vx; flystar[i].y+=flystar[i].vy; //在每进行一次贴图后,将粒子的存在时间累加1. flystar[i].lasted++; //进行条件判断,若某粒子跑出窗口区域一定的范围,则将该粒子设为不存在,且粒子数随之递减 if(flystar[i].x<=-10 || flystar[i].x>rect.right || flystar[i].y<=-10 || flystar[i].y>rect.bottom || flystar[i].lasted>50) { flystar[i].exist = false; //删除星光粒子 count--; //递减星光总数 } } } //将mdc中的全部内容贴到hdc中 BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);}//****消息处理函数***********************************LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ switch (message) { case WM_TIMER: //时间消息 MyPaint(hdc); //在消息循环中加入处理WM_TIMER消息,当接收到此消息时便调用MyPaint()函数进行窗口绘图 break; case WM_KEYDOWN: //按键消息 if(wParam==VK_ESCAPE) //按下【Esc】键 PostQuitMessage(0); break; case WM_DESTROY: //窗口结束消息 DeleteDC(mdc); DeleteDC(bufdc); DeleteObject(bg); DeleteObject(star); DeleteObject(mask); KillTimer(hWnd,1); //窗口结束时,删除所建立的定时器 ReleaseDC(hWnd,hdc); PostQuitMessage(0); break; default: //其他消息 return DefWindowProc(hWnd, message, wParam, lParam); } return 0;}
这个“星光绽放”demo运行的截图如下:
背景图片是盛大旗下网络游戏《龙之谷》的游戏原画。
这节的代码可以与之前讲解的重力系统,摩擦力系统相结合,创造出更为逼真的爆炸特效,有兴趣的朋友可以尝试一下,做出真实的受重力影响的烟花盛放的效果出来。
三、史诗级游戏作品《暗黑破坏神3》上市首周所创下的**
在文章末尾,我们来盘点一下暴雪大作《暗黑破坏神3》不俗的销售成绩。
5月15日《暗黑破坏神3》发售至今已经过了一周多,全球暗黑粉丝在激情中度过了美妙的一个星期。
《暗黑破坏神3》发布以来,受到了广大玩家的热情追捧。暴雪公布的数据显示,《暗黑破坏神3》开服24小时内共卖出了350万份拷贝。加上120万的魔兽世界年费用户,游戏发售当天共有470万玩家通过战网进入到庇护所世界。游戏发布第一周,游戏总销量为630万份(不包含WOW年费赠送和韩国数据)。
这份数据刷新了PC游戏史最高数据,并且大大超越去年暴雪另一款热卖游戏《星际争霸2》所创下的销售记录。《暗黑3》有望在一个月内突破《星际争霸》14年来1100万份的累计销量。
这是电子游戏界上的一个**!
暴雪花十几年时间交出的这份答卷可谓非常的出色~~!!
还是那句话,暴雪出品,必属精品。浅墨觉得,采用精品战略的公司一般都会有不俗的发展,比如说苹果,比如说暴雪,它们都是依靠着几款精雕细琢出来的精品,统治着他们各自所在的领域。
对于期待了8年的《暗黑破坏神3》,浅墨在5月15号“大菠萝3”发售之后的第一时间就购买了CD-KEY,但买过来之后一直比较忙,没有第一时间开始体验,也就是近几天才开始玩的。是在台服玩的一个“狩魔猎人”,每天也就花一个小时左右,浅墨打算慢慢玩,沿途看下风景,期待了这么多年的作品,如果很快就打通了就枉费了这么多年的期待了,是吧。(浅墨记得有一个在15号“大菠萝3”刚发售,开服8小时之后候就满级60级的家伙……)所以浅墨的升级速度不算快,目前才22级,不过身上目前基本上一身稀有装备了,敏捷堆了200+了,对于一个22级的狩魔猎人,已经很高了吧。我的BattleTag是浅墨#3762,也在台服玩的朋友可以加我好友一起玩哈。
同时在这里浅墨也提醒大家,“大菠萝3”虽然经典,但是一定要适度游戏,千万不要影响学习和生活。
前几天就爆出了美国一位32岁的男子连续玩了3天3夜的《暗黑破坏神3》后猝死,与世长辞的新闻,可真让人惋惜。
好了,本节笔记就到这里吧,关于源码的提供,浅墨越来越觉得没有提供两个版本的必要了,但是一定会满足不同IDE的朋友都可以把源码运行起来。下面是通用版的源码:
本篇文章的配套源码请点击这里下载:【Visual C++】Note_Code_23
(VC6.0直接可以打开工程,VS2005,VS2008,VS2010,VS2011等版本的朋友可以双击其中的工程文件进行版本转化,然后即可打开工程)
感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们。
目前在讲的GDI只是前奏。DirectX 11会在GDI梳理完后进行深入讲解,敬请期待~~
【Visual C++】游戏开发 系列文章才刚刚展开一点而已,因为游戏世界实在是太博大精深了~
但我们不能着急,得慢慢打好基础。做学问最忌好高骛远,不是吗?
浅墨希望看到大家的留言,希望与大家共同交流,希望得到睿智的评论(即使是批评)。
你们的支持是我写下去的动力~
精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习,共同进步。
大家看过后觉得值得一看的话,可以顶一下这篇文章,你们的支持是我继续写下去的动力~
如果文章中有什么疏漏的地方,也请大家指正。也希望大家可以多留言来和我探讨相关的问题。
最后,谢谢你们一直的支持~~~
——————————浅墨于2012年5月27日
------------------------------------------------------------------------------------------------------------------------------
浅墨历时一年为游戏编程爱好者锻造的入门宝典:《逐梦旅程:Windows游戏编程之从零开始》如果你喜欢浅墨写的【Visual C++】游戏开发系列博客文章,那么你一定会爱上这本书。这是浅墨专门为热爱游戏编程的朋友们写的入门级游戏编程宝典。
------------------------------------------------------------------------------------------------------------------------------