本文由BlueCoder编写 转载请说明出处:
http://blog.csdn.net/crocodile__/article/details/17228209
我的邮箱:bluecoder@yeah.net 欢迎大家和我交流编程心得
我的微博:BlueCoder_黎小华 欢迎光临^_^
临近期末,各种考试和课设都随之来临,所以博文更新进度也就慢了许多,不过今天总算抽出来一天的时间来继续聊聊2D游戏效果的那些事儿
今天做的效果就是生活中很常见的一个现象:自由落体运动——重力作用的测试,下面进入正题^_^……
一、效果演示
还是来一个gif
二、准备工作
(1)准备素材:本次是采用愤怒的小鸟游戏中的素材
(2)再来一首配套的背景音乐(还是取自愤怒的小鸟游戏)
(3)VS2010创建的MFC工程的类视图
三、实现细节
由于今天实现的效果是重力的测试,那么要想模拟出重力作用的效果,还是要点儿物理知识(不过这很简单),就是加速度有关的重要推论:(初速度为0的匀加速运动的一个重要推论)
第1s内,第2s内,第3s内……第ns内的位移之比为1:3:5:……:(2n-1)
可见这是一个等差数列,换句话说就是线性关系——即第ns内走过的位移比第(n-1)s内走过的位移始终多一个定长,可以证明到就是加速度a的值:
初速度为0的匀加速直线运动的位移公式为:S(t) = 1/2 * a * t^2 (S位移, a加速度, t时间)
第ns内走过的位移:S<n> = S(n) - S(n-1) = 1/2 * a * (2n - 1)
第(n-1)s内走过的位移:S<n-1> = S(n-1) - S(n-2) = 1/2 * a * (2n - 3)
S<n> - S<n-1> = a
根据这个结论,我们就可以轻松模拟自由落体运动了,下面结合一段核心代码来理解一下:
m_bird.yMove = m_bird.yMove + m_bird.gravity; m_bird.y += m_bird.yMove;
其中yMove是y轴上每1s内移动的位移,y就是纵坐标,gravity就是重力加速度(不变的值)
只要每一次计时器消息到来时,我们就可以此来更新小鸟的纵坐标y,就能到达自由落体的效果
四、代码剖析
(1).CChildView.h头文件中的重要内容:
//计时器ID #define ID_TIMER 100 //愤怒的小鸟 typedef struct bird { int x;//x坐标 int y;//y坐标 int xMove;//x轴上的位移分量 int yMove;//y轴上的位移分量 int gravity;//重力加速度 }BIRD;
// 特性 public: static const int DOWNBORDER = 42; private: CImage m_bg;//背景 CImage m_imgBird;//小鸟png BIRD m_bird;//关联m_imgBird的参数结构体 CSize m_sClient;//客户区大小 CSize m_sBird;//小鸟png大小 CString m_status;//小鸟运动状态 CString m_state;//说明 // 操作 public: //初始化 bool Initialize(); //移动小鸟 void MoveBird(); //绘制小鸟状态以及说明 void DrawString(CDC&); //绘制小鸟以及场景 void PaintScene(CDC*);
(2). 初始化操作函数
//初始化 bool CChildView::Initialize() { m_bg.Load(L"res\\bg.jpg"); m_imgBird.Load(L"res\\bird.png"); //如果初始化失败 if(m_bg.IsNull() || m_imgBird.IsNull()) { return false; } m_sClient.SetSize(790, 568); m_sBird.SetSize(50, 50); //实例化小鸟结构体 m_bird.x = (m_sClient.cx - m_sBird.cx) / 2; m_bird.y = - m_sBird.cy; m_bird.xMove = 0; m_bird.yMove = 0; m_bird.gravity = 4; //初始状态小鸟处于下降的运动状态以及说明 m_status = "运动状态:下降"; m_state = "说明:\n(1)鼠标左键重新测试\n(2)按下Esc键退出程序."; //打开并播放背景音乐 mciSendString(L"open res\\愤怒的小鸟.mp3 alias bgm", NULL, 0, NULL); mciSendString(L"play bgm repeat", NULL, 0, NULL); return true; }
(3). 移动小鸟
//移动小鸟 void CChildView::MoveBird() { //更新y轴上的每单位时间内的位移分量和y坐标 m_bird.yMove = m_bird.yMove + m_bird.gravity; m_bird.y += m_bird.yMove; //更改运动状态 m_status = "运动状态: "; m_status += m_bird.yMove > 0 ? "下降":"上升"; //如果小鸟碰到下边界, 速度反向 if(m_bird.y >= (m_sClient.cy - m_sBird.cy - DOWNBORDER)) { m_bird.y = m_sClient.cy - m_sBird.cy - DOWNBORDER; //------------------------速度反向--------------------------- //由于实际中,物体与地面碰撞反弹时会有动能损失(转换为了热能) //因此反向的速度应该小于原来的速度(这里是指绝对值), 故应该乘上4/5, //其中1/5就是转换为了热能 //----------------------------------------------------------- m_bird.yMove = - m_bird.yMove * 4 / 5; //如果速度为0, 则小鸟停止运动了(关闭计时器) if(m_bird.yMove == 0) { m_status = "运动状态: 静止"; KillTimer(ID_TIMER); } } }
(4). 绘制小鸟的运动状态和程序说明
//绘制小鸟状态以及说明 void CChildView::DrawString(CDC &cdc) { //更改字体和文本背景模式 CFont font; font.CreatePointFont(150, L"微软雅黑"); cdc.SelectObject(font); cdc.SetBkMode(TRANSPARENT); CSize size; size = cdc.GetTextExtent(m_state); size.cx /= 2; size.cy *= 3; CRect rect(CPoint(5, m_sClient.cy - size.cy * 5/3), size); //小鸟运动状态 cdc.SetTextColor(RGB(100, 230, 0)); cdc.TextOutW(5, (m_sClient.cy - size.cy * 2), m_status); //程序说明 cdc.SetTextColor(RGB(250, 30, 20)); cdc.DrawText(m_state, rect, DT_WORDBREAK | DT_EDITCONTROL | DT_LEFT); }
(5). 在响应WM_PAINT消息的消息映射函数OnPaint中绘制整个场景
//绘制场景 void CChildView::PaintScene(CDC *pDC) { CDC bufferDC; CBitmap bufferBmp; //----------------双缓冲贴图----------------------- bufferDC.CreateCompatibleDC(NULL); bufferBmp.CreateCompatibleBitmap(pDC, m_sClient.cx, m_sClient.cy); bufferDC.SelectObject(bufferBmp); //绘制背景 bufferDC.SetStretchBltMode(COLORONCOLOR); m_bg.StretchBlt(bufferDC, 0, 0, m_sClient.cx, m_sClient.cy, SRCCOPY); //绘制小鸟状态以及说明 DrawString(bufferDC); //绘制小鸟 m_imgBird.TransparentBlt(bufferDC, m_bird.x, m_bird.y, m_sBird.cx, m_sBird.cy, RGB(0, 162, 232)); //将内存中的图统一绘制到客户区 pDC->BitBlt(0, 0, m_sClient.cx, m_sClient.cy, &bufferDC, 0, 0, SRCCOPY); //释放内存 bufferBmp.DeleteObject(); bufferDC.DeleteDC(); }
(6). 在响应WM_CREATE消息的消息映射函数中进行初始化操作
int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CWnd::OnCreate(lpCreateStruct) == -1) return -1; //初始化 if(!Initialize()) { AfxMessageBox(L"图片加载失败"); exit(0); } //设置计时器 SetTimer(ID_TIMER, 40, NULL); return 0; }
(7). 在响应WM_TIMER消息的消息映射函数中移动小鸟
void CChildView::OnTimer(UINT_PTR nIDEvent) { //移动小鸟 MoveBird(); //重绘客户区 InvalidateRect(NULL, false); CWnd::OnTimer(nIDEvent); }
(8). 以下是一些一般消息的响应
void CChildView::OnLButtonDown(UINT nFlags, CPoint point)//鼠标左键消息 { //实例化小鸟结构体 m_bird.x = m_bird.x = (m_sClient.cx - m_sBird.cx) / 2; m_bird.y = - m_sBird.cy; m_bird.xMove = 0; m_bird.yMove = 0; m_bird.gravity = 4; //先关闭计时器 KillTimer(ID_TIMER); //设置计时器 SetTimer(ID_TIMER, 40, NULL); CWnd::OnLButtonDown(nFlags, point); } void CChildView::OnDestroy()//窗口销毁消息 { CWnd::OnDestroy(); //关闭计时器和音乐 KillTimer(ID_TIMER); mciSendString(L"close bgm", NULL, 0, NULL); //回收内存资源 m_bg.Destroy(); m_imgBird.Destroy(); } void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)//按键消息 { //按下Esc键退出程序 if(nChar == VK_ESCAPE) { exit(0); } CWnd::OnKeyDown(nChar, nRepCnt, nFlags); }
五、零积分资源下载
点击下载源代码
在结束之前,送大家一句话(和大家共勉):
人的一生可以不去拥有很多东西,但唯独不能失去的是希望
——摘自欧亨利《最后一片叶子》
我们都曾因为实际生活不为自己所想而迷茫过、失落过,甚至放弃过。没关系,我们不能改变环境,就只能迫使自己去适应环境——只要你心中始终保持着那份纯真和坚定不移的希望,相信自己总有一天会成功
ok,本次游戏效果模拟到此结束,下次我们再会( ^_^ )/~~
————
先透露一下"剧情"哈:下次模拟一个跑酷游戏的Demo,敬请期待吧:)