[关闭]
@Andream 2017-05-06T23:47:06.000000Z 字数 6167 阅读 719

原文
Markdown版

项目结构

MainFrm // 主框架,设置窗口基本属性
Doc     // Model,数据源
View    // View,视图
App     // Control,逻辑处理

注:每个文件对应一个.h文件和.cpp文件,且会加上对应的项目前缀

窗口调整

  1. 改变窗口大小
  1. BOOL App::InitInstance(){
  2. // 添加该行代码
  3. m_pMainWnd->SetWindowPos(NULL,0,0,width,height,SWP_NOMOVE);
  4. }
  1. 删除菜单栏
  1. int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
  2. {
  3. if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
  4. return -1;
  5. //删除菜单栏
  6. SetMenu(NULL);
  7. /*
  8. 注释掉工具栏
  9. */
  10. return 0;
  11. }

关于windows程序的消息机制

   windows程序采用了消息机制,当大家点击一下鼠标左键,就产生了一个鼠标点击消息,按下键盘,就产生了键盘消息,windows操作系统为每个程序维护了一个消息队列,每次大家操作后,产生相应的消息,windows会确定这个消息归属的窗口,并将其添加进该窗口的消息队列。

   而windows应用程序呢,就在一个while(1)循环中,读取消息队列,当读取到消息后,就进行相应的处理,对于游戏,当没有读取到消息,就绘制画面。

   如果大家建立win32 应用程序,就需要自己写建立窗口,显示窗口,消息循环的代码,而MFC将这个过程封装起来了,大家是看不到的,当然也失去了很多灵活性。不过对于我们的游戏开发,影响不是很大。

   我这里简略的介绍了下消息机制,只是希望大家能大概的知道windows程序运行的流程,如果想更深入的了解,那么大家可能就需要读一些书,自己研究下了。

图形绘制

相关函数
Update/Painting Functions in CWnd

Function Discription
BeginPaint Prepares CWnd for painting.
EndPaint Marks the end of painting.
Print Draws the current window in the specified device context.
PrintClient Draws any window in the specified device context (usually a printer device context).
LockWindowUpdate Disables or reenables drawing in the given window.
UnlockWindowUpdate Unlocks a window that was locked with CWnd::LockWindowUpdate.
GetDC Retrieves a display context for the client area.
GetDCEx Retrieves a display context for the client area, and enables clipping while drawing.
RedrawWindow Updates the specified rectangle or region in the client area.
GetWindowDC Retrieves the display context for the whole window, including the caption bar, menus, and scroll bars.
ReleaseDC Releases client and window device contexts, freeing them for use by other applications.
UpdateWindow Updates the client area.
SetRedraw Allows changes in CWnd to be redrawn or prevents changes from being redrawn.
GetUpdateRect Retrieves the coordinates of the smallest rectangle that completely encloses the CWnd update region.
GetUpdateRgn Retrieves the CWnd update region.
Invalidate Invalidates the entire client area.
InvalidateRect Invalidates the client area within the given rectangle by adding that rectangle to the current update region.
InvalidateRgn Invalidates the client area within the given region by adding that region to the current update region.
ValidateRect Validates the client area within the given rectangle by removing the rectangle from the current update region.
ValidateRgn Validates the client area within the given region by removing the region from the current update region.
ShowWindow Shows or hides the window.
IsWindowVisible Determines whether the window is visible.
ShowOwnedPopups Shows or hides all pop-up windows owned by the window.
EnableScrollBar Enables or disables one or both arrows of a scroll bar.
  1. View.h // 在这里定义
  2. BOOL View::PreCreateWindow(CREATESTRUCT&cs){
  3. //在这里初始化
  4. }
  5. void CBugHunt2016AView::OnDraw(CDC* pDC){
  6. CRect client;
  7. GetClientRect(&client);
  8. // GetDC和ReleaseDC已经封装好了,在这里绘制即可
  9. }

CBitmap(太麻烦了,没弄清楚)

CImage

  1. View.h
  2. CImage m_img; // 在头文件里定义
  3. BOOL View::PreCreateWindow(CREATESTRUCT&cs){
  4. //在这里初始化
  5. m_img.Load("hero_No.png");
  6. //TransparentPNG(&m_img);
  7. }
  8. void View::OnDraw(CDC* pDC){
  9. CRect client;
  10. GetClientRect(&client);
  11. // GetDC和ReleaseDC已经封装好了,在这里绘制即可
  12. // 普通绘制
  13. m_img.Draw(pDC->GetSafeHdc(), xDest, yDest, xWidth, yWidth);
  14. // 把背景透明化后绘制
  15. m_img.TransparentBlt(pDC->GetSafeHdc(), x, y, w, h, xSrc, ySrc, srcW, srcH, RGB(0, 255, 0));
  16. }

两种贴图方式的对比

   采用CBitmap类进行贴图时,操作比较麻烦,而且BMP图片占空间还比较大;使用CImage图片操作简单,而且PNG格式小巧,占用空间小。另外,还有一点很重要的区别,使用CBitmap类时,图片被打包进了可执行程序中去,导致可执行程序比较大,但是exe程序可以到处运行,而不需要那些资源图片在旁边。使用CImage类时,PNG图片是从文件中读取的,所以要运行程序,PNG图片必须在exe程序旁边,当然,exe程序也就很小了。
   由于CImage比较简单,在以后的教程中,我将都采用CImage贴图。

消息响应(鼠标 & 键盘)

键盘消息

在CWind类中定义了许多消息响应函数,如:

  1. afx_msg void OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ); // 对应消息 WM_KEYDOWN
  2. afx_msg void OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags ); // 对应消息 WM_KEYUP

这两个函数分别响应键盘按下弹起,只需要在View类中重写即可。
也可以通过 项目->类向导->消息->WM_KEYDOWN 来自动生成重写函数
想了解更多的响应函数及其说明,可以查阅MFC手册(定义在CWind里的On开头的各种函数)

鼠标消息

与键盘消息的处理方法一毛一样,仅列出常见的鼠标消息

WM_LBUTTONDBLCLK    双击鼠标左键消息
WM_LBUTTONDOWN      单击鼠标左键消息
WM_LBUTTONUP        松开鼠标左键消息
WM_MBUTTONDBLCLK    双击鼠标中键(滚轮)消息
WM_MBUTTONDOWN      单击鼠标中键(滚轮)消息
WM_MBUTTONUP        松开鼠标中键(滚轮)消息
WM_RBUTTONDBLCLK    双击鼠标右键消息
WM_RBUTTONDOWN      单击鼠标右键消息
WM_RBUTTONUP        松开鼠标右键消息
WM_MOUSEMOVE        鼠标移动消息
WM_MOUSEWHEEL       鼠标滚轮转动消息

需要注意的一点是:如果消息响应过后需要更新图像(比如人物坐标发生的变化),需要加上一句:

  1. InvalidateRect(&m_client);

为什么?请往下看

定时器与动画(让Windows反复调用OnDraw)

关于定时器(Timer)的三个函数(定义在CWnd中):

  1. // 添加定时器
  2. UINT SetTimer(
  3. UINT nIDEvent, // 定时器代号
  4. UINT nElapse, // 多久调用一次回调函数
  5. void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) // 回调函数
  6. );
  7. // 删除定时器
  8. BOOL KillTimer(
  9. int nIDEvent // 定时器代号(哪个定时器不想活了?)
  10. );
  11. // 定时器默认回调函数
  12. afx_msg void OnTimer(
  13. UINT nIDEvent // 定时器代号(是谁在调用我?)
  14. );
注:定时器应当在窗口创建之后再创建,所以不能在`PreCreateWindow`中创建
一般可以在`OnCreate`和`OnDraw`中创建

图像双缓冲(让图像不再闪烁)

一、闪烁原因

   为了解决问题,我们得首先搞清楚闪烁的原因是什么,然后才能对症下药。能够导致游戏画面闪烁的原因非常多,但是对于我们做游戏开发的同学来说,最主要的就是一种:贴图贴的太频繁。

   如果大家是一个细心人的话,那么应该可以发现,在笔记三讲解贴图的时候,当我们贴出背景图的时候,是根本不会闪烁的,但是当我们贴出人物后,闪烁就出来了,而当我们移动人物的时候,闪烁的画面简直惨不忍睹啊。

   想弄清楚真正的原因就得要理解GDI绘图的原理:GDI绘图的时候是先绘制到显存里面,然后显存每隔一段时间就需要把里面的内容输出到屏幕上,这个时间就是刷新周期。在绘图的时候,系统会先用一种背景色擦除掉原来的图像,然后再绘制新的图像。如果这几次绘制不在同一个刷新周期中,那么我们看到的就是先看到背景色,再看到内容出来,就会有闪烁的感觉,而绘制的次数越多,看到这种现象的可能性就越大,就闪烁的越厉害。

二、图像双缓冲技术

   大家清楚了闪烁的原因后,再结合我们只贴出背景的时候并没有闪烁的事实,那么或许大家就可以想到一种解决方法了:我们事先将要画的所有东西画在一张图片上,然后将这张图直接贴出来,不就解决了吗?

   如果你想到这里,那么恭喜你,你已经想到了图像双缓冲技术。其实看起来很高端的这个名词其实非常简单。我们之前画图的时候都是直接画在窗口DC上,在之前我们可以自己先创建一个内存DC,然后把画图都画在内存DC中,最后再一次性的将内存DC输出到窗口DC中,就可以解决画面闪烁的问题了。
  1. // 缓冲绘制代码
  2. void CBugHunt2016AView::OnDraw(CDC* pDC)
  3. {
  4. CBugHunt2016ADoc* pDoc = GetDocument();
  5. ASSERT_VALID(pDoc);
  6. if (!pDoc)
  7. return;
  8. // 绘制参数
  9. GetClientRect(&m_client);
  10. const int width = m_client.Width();
  11. const int height = m_client.Height();
  12. int cx = m_bug.GetWidth() / 18;
  13. int cy = m_bug.GetHeight() / 4;
  14. // 准备画布
  15. CBitmap cacheBmp;
  16. cacheBmp.CreateCompatibleBitmap(pDC, width, height);
  17. // 准备画家
  18. CDC cache;
  19. cache.CreateCompatibleDC(NULL);
  20. cache.SelectObject(&cacheBmp);
  21. // ---------开始绘制
  22. // 画背景
  23. m_imgBackground.Draw(cache, 0, 0, width, height,
  24. (m_imgBackground.GetWidth() - width) / 2,
  25. (m_imgBackground.GetHeight() - height) / 2,
  26. width,
  27. height);
  28. // 画虫子
  29. m_bug.TransparentBlt(cache, m_bug_position.x, m_bug_position.y, cx, cy, 0 * cx, 0 * cy, cx, cy, RGB(0, 255, 0));
  30. // 把作品一次刷到显卡里
  31. pDC->BitBlt(0, 0, width, height, &cache, 0, 0, SRCCOPY);
  32. //---------结束绘制
  33. cache.DeleteDC();
  34. cacheBmp.DeleteObject();
  35. }

源码下载:BugHunt2016A.zip

我在项目中遇到的一些问题:

error C2653: “CXXXX”: 不是类或命名空间名称

出现该问题主要是mfc自定义的类时忘记#include "stdafx.h",或者#include "stdafx.h"没有放在实现代码的第一行导致。

类 xxx 不存在默认构造函数

没有默认构造函数貌似不行,手动加一个FastBug() {}

如何添加背景音乐

  1. // 必须是wav格式
  2. #include <mmsystem.h>
  3. #pragma comment(lib,"winmm.lib")
  4. // 循环播放背景音乐
  5. PlaySound(_T(".\\Bitmap\\Go Time.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
  6. // mci播放点击音乐
  7. mciSendString(_T("close a2"), NULL, 0, m_hWnd);
  8. mciSendString(_T("open .\\Bitmap\\click4.wav alias a2 wait"), NULL, 0, NULL);
  9. mciSendString(_T("play a2"), 0, 0, 0);
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注