@Andream
2017-05-06T23:47:06.000000Z
字数 6167
阅读 719
MainFrm // 主框架,设置窗口基本属性
Doc // Model,数据源
View // View,视图
App // Control,逻辑处理
注:每个文件对应一个.h文件和.cpp文件,且会加上对应的项目前缀
BOOL App::InitInstance(){
// 添加该行代码
m_pMainWnd->SetWindowPos(NULL,0,0,width,height,SWP_NOMOVE);
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
//删除菜单栏
SetMenu(NULL);
/*
注释掉工具栏
*/
return 0;
}
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. |
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. |
View.h // 在这里定义
BOOL View::PreCreateWindow(CREATESTRUCT&cs){
//在这里初始化
}
void CBugHunt2016AView::OnDraw(CDC* pDC){
CRect client;
GetClientRect(&client);
// GetDC和ReleaseDC已经封装好了,在这里绘制即可
}
View.h
CImage m_img; // 在头文件里定义
BOOL View::PreCreateWindow(CREATESTRUCT&cs){
//在这里初始化
m_img.Load("hero_No.png");
//TransparentPNG(&m_img);
}
void View::OnDraw(CDC* pDC){
CRect client;
GetClientRect(&client);
// GetDC和ReleaseDC已经封装好了,在这里绘制即可
// 普通绘制
m_img.Draw(pDC->GetSafeHdc(), xDest, yDest, xWidth, yWidth);
// 把背景透明化后绘制
m_img.TransparentBlt(pDC->GetSafeHdc(), x, y, w, h, xSrc, ySrc, srcW, srcH, RGB(0, 255, 0));
}
采用CBitmap类进行贴图时,操作比较麻烦,而且BMP图片占空间还比较大;使用CImage图片操作简单,而且PNG格式小巧,占用空间小。另外,还有一点很重要的区别,使用CBitmap类时,图片被打包进了可执行程序中去,导致可执行程序比较大,但是exe程序可以到处运行,而不需要那些资源图片在旁边。使用CImage类时,PNG图片是从文件中读取的,所以要运行程序,PNG图片必须在exe程序旁边,当然,exe程序也就很小了。
由于CImage比较简单,在以后的教程中,我将都采用CImage贴图。
在CWind类中定义了许多消息响应函数,如:
afx_msg void OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ); // 对应消息 WM_KEYDOWN
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 鼠标滚轮转动消息
需要注意的一点是:如果消息响应过后需要更新图像(比如人物坐标发生的变化),需要加上一句:
InvalidateRect(&m_client);
为什么?请往下看
关于定时器(Timer)的三个函数(定义在CWnd
中):
// 添加定时器
UINT SetTimer(
UINT nIDEvent, // 定时器代号
UINT nElapse, // 多久调用一次回调函数
void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) // 回调函数
);
// 删除定时器
BOOL KillTimer(
int nIDEvent // 定时器代号(哪个定时器不想活了?)
);
// 定时器默认回调函数
afx_msg void OnTimer(
UINT nIDEvent // 定时器代号(是谁在调用我?)
);
注:定时器应当在窗口创建之后再创建,所以不能在`PreCreateWindow`中创建
一般可以在`OnCreate`和`OnDraw`中创建
为了解决问题,我们得首先搞清楚闪烁的原因是什么,然后才能对症下药。能够导致游戏画面闪烁的原因非常多,但是对于我们做游戏开发的同学来说,最主要的就是一种:贴图贴的太频繁。
如果大家是一个细心人的话,那么应该可以发现,在笔记三讲解贴图的时候,当我们贴出背景图的时候,是根本不会闪烁的,但是当我们贴出人物后,闪烁就出来了,而当我们移动人物的时候,闪烁的画面简直惨不忍睹啊。
想弄清楚真正的原因就得要理解GDI绘图的原理:GDI绘图的时候是先绘制到显存里面,然后显存每隔一段时间就需要把里面的内容输出到屏幕上,这个时间就是刷新周期。在绘图的时候,系统会先用一种背景色擦除掉原来的图像,然后再绘制新的图像。如果这几次绘制不在同一个刷新周期中,那么我们看到的就是先看到背景色,再看到内容出来,就会有闪烁的感觉,而绘制的次数越多,看到这种现象的可能性就越大,就闪烁的越厉害。
大家清楚了闪烁的原因后,再结合我们只贴出背景的时候并没有闪烁的事实,那么或许大家就可以想到一种解决方法了:我们事先将要画的所有东西画在一张图片上,然后将这张图直接贴出来,不就解决了吗?
如果你想到这里,那么恭喜你,你已经想到了图像双缓冲技术。其实看起来很高端的这个名词其实非常简单。我们之前画图的时候都是直接画在窗口DC上,在之前我们可以自己先创建一个内存DC,然后把画图都画在内存DC中,最后再一次性的将内存DC输出到窗口DC中,就可以解决画面闪烁的问题了。
// 缓冲绘制代码
void CBugHunt2016AView::OnDraw(CDC* pDC)
{
CBugHunt2016ADoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// 绘制参数
GetClientRect(&m_client);
const int width = m_client.Width();
const int height = m_client.Height();
int cx = m_bug.GetWidth() / 18;
int cy = m_bug.GetHeight() / 4;
// 准备画布
CBitmap cacheBmp;
cacheBmp.CreateCompatibleBitmap(pDC, width, height);
// 准备画家
CDC cache;
cache.CreateCompatibleDC(NULL);
cache.SelectObject(&cacheBmp);
// ---------开始绘制
// 画背景
m_imgBackground.Draw(cache, 0, 0, width, height,
(m_imgBackground.GetWidth() - width) / 2,
(m_imgBackground.GetHeight() - height) / 2,
width,
height);
// 画虫子
m_bug.TransparentBlt(cache, m_bug_position.x, m_bug_position.y, cx, cy, 0 * cx, 0 * cy, cx, cy, RGB(0, 255, 0));
// 把作品一次刷到显卡里
pDC->BitBlt(0, 0, width, height, &cache, 0, 0, SRCCOPY);
//---------结束绘制
cache.DeleteDC();
cacheBmp.DeleteObject();
}
我在项目中遇到的一些问题:
error C2653: “CXXXX”: 不是类或命名空间名称
出现该问题主要是mfc自定义的类时忘记#include "stdafx.h",或者#include "stdafx.h"没有放在实现代码的第一行导致。
类 xxx 不存在默认构造函数
没有默认构造函数貌似不行,手动加一个
FastBug() {}
如何添加背景音乐
// 必须是wav格式
#include <mmsystem.h>
#pragma comment(lib,"winmm.lib")
// 循环播放背景音乐
PlaySound(_T(".\\Bitmap\\Go Time.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
// mci播放点击音乐
mciSendString(_T("close a2"), NULL, 0, m_hWnd);
mciSendString(_T("open .\\Bitmap\\click4.wav alias a2 wait"), NULL, 0, NULL);
mciSendString(_T("play a2"), 0, 0, 0);