【VC++游戏开发】用C++来架构一个适合windows游戏编程的框架——取名为BCF

本文由BlueCoder编写   转载请说明出处:

http://blog.csdn.net/crocodile__/article/details/18375315

我的邮箱:[email protected]    欢迎大家和我交流编程心得

我的微博:BlueCoder_黎小华    欢迎光临^_^


注:

     正如大家所看的,BlueCoder自己架构了一个适合windows游戏编程的简易框架(BCF)。   这就意味着,BlueCoder都会用BCF这个框架来模拟以后的游戏效果,因此一直关注此专栏的朋友可以稍微花点儿时间熟悉一下——你可以结合源码和本文一起学习。

      BlueCoder

      2014/1/20


一、序言

众所周知:

MFC适合桌面应用的开发,而不太适合windows游戏编程,因为它封装了很多我们游戏编程中所不需要的东西,这些东西在一定程度上都影响着GDI的效率,略显冗余了。但是MFC有丰富的类库,这在写代码时又能提供很大的方便……


再来看看Win32 SDK,接近底层,效率肯定好,但是却没有MFC那样的类库,写代码着实不太方便……


这样一想,我就有个问题了:在接下来的游戏效果模拟中,是继续使用MFC,还是专用Win32SDK呢?或者还有什么更好的方法?……

这么一想,嘿,一个idea就诞生了:用C++来自行架构一个适合windows游戏编程的框架,使它既能使用MFC类库效率又更好

这个框架如何架构呢?请继续阅读下面的内容吧^_^


二、架构的一个简易框架,MFC类库、效率都能兼顾

我的架构思路:

       1>将窗口创建过程(设计窗口类结构体实例、注册窗口类、创建窗口、显示窗口、消息循环)以及消息响应函数都封装在一个名为CCWindow的C++类中

       2>在Main.cpp源文件中就实现WinMain以及窗口过程


由于我的开源框架的源代码中有详尽的注释,F话就不多说了,直接贴上源代码:

StdAfx.h头文件:这是使用MFC类库所必须添加的头文件

/***
*
*	StdAfx.h
*	包含一些用到的类库所在的头文件
*
***/

#pragma once

#define _AFXDLL//动态添加MFC类库
#include <SDKDDKVer.h>//定义最高版本windows

#include <afxwin.h>		// MFC封装的核心组件和标准组件
#include <atlimage.h>	//CImage类包含的头文件
#include <MMSystem.h>	//播放媒体(音乐)所需包含的头文件
#pragma comment(lib, "winmm.lib")//添加媒体库

#pragma comment(linker,"/manifestdependency:\"type=‘win32‘ 				name=‘Microsoft.Windows.Common-Controls‘ 				version=‘6.0.0.0‘ processorArchitecture=‘x86‘ 				publicKeyToken=‘6595b64144ccf1df‘ language=‘*‘\"")

CWindow.h头文件(CCWindow类的定义)

#pragma once
#include"StdAfx.h"
#include<time.h>

class CCWindow
{
//==================成员====================
private:
	WNDCLASS	m_wndclass; //窗口类结构体实例
public:
	HWND		m_hwnd;		//窗口句柄
	CImage		m_img;		//背景图片
	CRect		m_rect;		//窗口户区大小

//=============窗口创建相关的成员函数=============
public:
	//设计窗口类
	bool InitWndClass(
					HINSTANCE hInstance,	//实例句柄
					WNDPROC	  wpWndProc,	//窗口过程
					LPCTSTR	  lpWndName,	//窗口名称
					LPCTSTR   lpIconPath);	//图标路径

	//设计窗口类
	bool InitWndClass(WNDCLASS);
	
	//注册窗口类
	ATOM RegisterWndClass();

	//创建窗口(默认居中)
	void Create(
			LPCTSTR lpClassName,	//窗口类名称
			LPCTSTR lpWndName,	//窗口标题名称
			DWORD	dwStyle,		//窗口风格
			int		nWidth,			//窗口宽度
			int     nHeight);		//窗口高度

	//显示窗口
	void Show(int);

	//一般的消息循环
	int RunMsgLoop();

	//更高效的消息循环
	int RunMsgLoop(void (*Display)(), int);

//================消息响应函数================
public:
	//注:在这里添加需要响应的消息处理函数的声明

public:
	//构造函数
	CCWindow(void);
	//析构函数
	~CCWindow(void);
};

CWindow.cpp(CCWindow类的成员函数实现)

#include "CWindow.h"


CCWindow::CCWindow(void)
{
}

CCWindow::~CCWindow(void)
{
}

/*------------------------------
	*功能:设计窗口类
	*@hInstance:	实例句柄
	*@WndProc:		窗口过程
	*@WndName:		窗口名称
	*@IconPath:		图标路径
 -----------------------------*/
bool CCWindow::InitWndClass(HINSTANCE hInstance,//实例句柄
							WNDPROC WndProc,	//窗口过程
							LPCTSTR WndName,	//窗口名称
							LPCTSTR IconPath)	//图标路径
{
	ZeroMemory(&m_wndclass, sizeof(WNDCLASS));

	m_wndclass.style			= CS_HREDRAW | CS_VREDRAW;
	m_wndclass.lpfnWndProc		= WndProc;
	m_wndclass.hInstance		= hInstance;
	m_wndclass.cbClsExtra		= 0;
	m_wndclass.cbWndExtra		= 0;
	m_wndclass.hIcon			= 
		static_cast<HICON>(LoadImage(NULL, IconPath, IMAGE_ICON,
						0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE));
	m_wndclass.hCursor			= ::LoadCursor(NULL, IDC_ARROW);
	m_wndclass.hbrBackground	= 
		static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
	m_wndclass.lpszMenuName		= NULL;
	m_wndclass.lpszClassName	= WndName;

	return true;
}

/*--------------------------------------
				设计窗口类
--------------------------------------*/
bool CCWindow::InitWndClass(WNDCLASS wndclass)
{
	m_wndclass = wndclass;

	return true;
}

/*--------------------------------------
			   注册窗口类
--------------------------------------*/
ATOM CCWindow::RegisterWndClass()
{
	return RegisterClass(&m_wndclass);
}

/*--------------------------------------
				创建窗口
--------------------------------------*/
void CCWindow::Create(
	    LPCTSTR lpClassName,
		LPCTSTR lpWindowName,
		DWORD dwStyle,
		int nWidth,
		int nHeight)
{
	//获取屏幕宽度和高度
	int screenW = GetSystemMetrics(SM_CXSCREEN);
	int screenH = GetSystemMetrics(SM_CYSCREEN);

	//创建并居中显示窗口
	m_hwnd = CreateWindow(lpClassName, lpWindowName, dwStyle,
				 (screenW-nWidth)/2, (screenH-nHeight)/2,
				 nWidth, nHeight, NULL, NULL,
				 m_wndclass.hInstance, NULL);
}

/*--------------------------------------
				显示窗口
--------------------------------------*/
void CCWindow::Show(int nCmdShow)
{
	ShowWindow(m_hwnd, nCmdShow);
}

//UpdateWindow(...)更新窗口(可以省略)

/*--------------------------------------
			一般的消息循环
			 GetMessage()
--------------------------------------*/
int CCWindow::RunMsgLoop()
{
	MSG	msg;
	ZeroMemory(&msg, sizeof(MSG));

	while(GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}

/*--------------------------------------
		消息循环(更好的消息循环)
			PeekMessage()
--------------------------------------*/
int CCWindow::RunMsgLoop(void (*Display)(), int interval)
{
	MSG	msg;
	ZeroMemory(&msg, sizeof(MSG));

	//获取运行到此处时的时间
	int last = GetTickCount();

	//如果不是退出消息
	while(msg.message != WM_QUIT)
	{
		//如果有消息
		if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		//否则, 空闲的时候执行响应函数(大多数是绘制函数)
		else
		{
			//如果窗口客户区大小不为0就是显示(有可能窗口是在最小化)
			if(m_rect.Width() && 
			   m_rect.Height())
			{
				Display();
				Sleep(interval);
			}
		}
	}

	return msg.wParam;
}

//----------------------------------------------------------
//						消息响应函数
//----------------------------------------------------------
//注:在这里添加需要响应的消息处理函数的实现

Main.h头文件(主程序头文件)

/***
*
*	Main.h
*	主程序所需包含的头文件、宏定义、声明等
*
***/

#pragma once
#include "CWindow.h"

#define WNDNAME	"【VC++游戏开发】窗口名称"//窗口名称
#define WNDWIDTH 800//窗口宽度
#define WNDHEIGHT 600//窗口高度

//窗口关联对象:全局对象
CCWindow wnd;

//窗口过程
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

Main.cpp(主程序代码:负责WinMain、WndProc)

#include "Main.h"

/*
	显示函数:绘制随机位置、大小、颜色的矩形
	注:由于调用很频繁,故设为内联函数
*/
inline void Display()
{
	//做响应的操作
}

//
//==============WinMain=======================
//
int WINAPI WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance,
				   LPSTR lpCmdLine,
				   int nCmdShow)
{
	//设计窗口类
	CString iconPath = "";//图标的路径(这里没有, 需自己设定)
	wnd.InitWndClass(hInstance, WndProc, WNDNAME, iconPath);

	//注册窗口类
	if(!wnd.RegisterWndClass())
	{
		::AfxMessageBox("RegisterWndClass() Failed");

		return 0;
	}

	DWORD style = WS_OVERLAPPEDWINDOW &
				~(WS_THICKFRAME | WS_MAXIMIZEBOX);

	//创建窗口并居中窗口
	wnd.Create(WNDNAME, WNDNAME, style,
			   WNDWIDTH, WNDHEIGHT);

	//显示窗口
	wnd.Show(nCmdShow);

	/*进入消息循环
		1. 使用更好的消息循环 如:wnd.RunMsgLoop(Display, 100)
		2. 使用一般的消息循环 如:wnd.RunMsgLoop()
	*/
	return wnd.RunMsgLoop();
}

//
//================窗口过程:处理消息=================
//
LRESULT CALLBACK WndProc(
				HWND hwnd,
				UINT msg,
				WPARAM wParam,
				LPARAM lParam)
{
	/*
	在这里添加消息映射代码(switch-case语句)

	如:
		switch(msg)
		{
		case WM_CREATE:
			wnd.OnCreate();	//窗口建立消息:进行一些初始化操作
			return 0;
		}
	*/

	return DefWindowProc(hwnd, msg, wParam, lParam);
}


下面,我简单地介绍一下这个框架的使用方法:

1>创建一个空项目(我这里是VS2010)

2>将这3个头文件以及2个cpp源文件添加到创建好的项目中

3>在 CWindow.h中添加消息响应函数(CCWindow类的成员函数)的声明

如:(名称还是遵循MFC的命名方式)

//================消息响应函数================
public:
	//注:在这里添加需要响应的消息处理函数的声明
	void OnCreate();;
4>CWindow.cpp中实现这些消息响应函数

5>在Main.cpp的窗口过程函数中调用响应的消息响应函数

如:

switch(msg)
{
case WM_CREATE:
	wnd.OnCreate();	//窗口建立消息:进行一些初始化操作
	return 0;
}


我还是为这个框架取一个"艺名儿",名为:BCF

BC:我的csdn博客ID缩写——BlueCoder

F:frame——框架的意思

合起来就是BlueCoder的框架,当然,大家都可以用,只要你愿意,我也乐意^_^


三、一个简单的实例,教你熟悉这个框架

我做了一个简单的实例,来帮助大家熟悉这个框架


这个实例呢,就是简单的贴图、贴文字,不过你会看到有趣的部分:我使用了诸如CPaintDC、CFont、CString、CImage、CRect等MFC类库。对,没错,这就是这个框架的一大特点:可以使用MFC类库,这就方便了我们写代码


由于没什么复杂的原理,我就按照步骤贴出核心代码哈:

(1)项目、头文件、源文件已经相继处理好了

(2)在CCWindow类中添加如下的消息响应成员函数:

//================消息响应函数================
//注:在这里添加需要响应的消息处理函数
public:
	//处理WM_CREATE消息
	void OnCreate();

	//处理WM_SIZE消息
	void OnSize();
	
	//处理WM_PAINT消息
	void OnPaint();
	
	//处理WM_KEYDOWN消息
	void OnKeyDown(WPARAM);
	
	//处理WM_DESTROY消息
	void OnDestroy();

(3)这些消息响应函数的实现:

//----------------------------------------------------------
//						消息响应函数
//----------------------------------------------------------

//窗口建立消息:进行一些初始化操作
void CCWindow::OnCreate()
{
	m_img.Load("res\\bg.jpg");

	if(m_img.IsNull())
	{
		AfxMessageBox("背景图片加载失败!");
		exit(0);
	}

	mciSendString("open res\\bgm.mp3 alias bgm", 0, 0, 0);
	mciSendString("play bgm repeat", 0, 0, 0);
}

//窗口size消息:获取窗口大小
void CCWindow::OnSize()
{
	::GetClientRect(m_hwnd, m_rect);
}

//窗口Paint消息:绘制窗口客户区
void CCWindow::OnPaint()
{
	CPaintDC dc(CWnd::FromHandle(m_hwnd));

	//绘制背景图片
	dc.SetStretchBltMode(COLORONCOLOR);
	m_img.StretchBlt(dc, 0, 0, m_rect.Width(), m_rect.Height(),
		0, 0, m_img.GetWidth(), m_img.GetHeight(), SRCCOPY);

	//绘制文字
	CFont font;
	font.CreatePointFont(150, "微软雅黑");
	dc.SelectObject(font);

	CString str1 = "给自己的梦想一次破茧而出的机会,创造属于自己的幸福";
	CString str2 = "BlueCoder(黎小华)";

	dc.SetBkMode(TRANSPARENT);
	dc.SetTextColor(RGB(163, 21, 21));
	dc.TextOut(130, 220, str1);

	dc.SetTextColor(RGB(21, 155, 230));
	dc.TextOut(500, 300, str2);
}

//按键消息:按下Esc键退出程序
void CCWindow::OnKeyDown(WPARAM wParam)
{
	if(wParam == VK_ESCAPE)
	{
		DestroyWindow(m_hwnd);
	}
}

//窗口销毁消息:释放内存
void CCWindow::OnDestroy()
{
	m_img.Destroy();

	mciSendString("close bgm", 0, 0, 0);

	PostQuitMessage(0);
}

(4)在Main.cpp中的窗口过程中调用响应的消息成员函数:

//窗口过程:处理消息
LRESULT CALLBACK WndProc(
				HWND hwnd,
				UINT msg,
				WPARAM wParam,
				LPARAM lParam)
{
	switch(msg)
	{
	case WM_CREATE:
		wnd.OnCreate();	//窗口建立消息:进行一些初始化操作
		return 0;

	case WM_SIZE:
		wnd.OnSize();	//窗口size消息:获取窗口大小
		return 0;

	case WM_KEYDOWN:	//按键消息
		wnd.OnKeyDown(wParam);
		return 0;

	case WM_PAINT:		//窗口Paint消息:绘制窗口客户区
		wnd.OnPaint();
		return 0;

	case WM_DESTROY:	//窗口销毁消息:释放内存
		wnd.OnDestroy();
		return 0;
	}

	return DefWindowProc(hwnd, msg, wParam, lParam);
}

注:此时,我们使用的是一般的消息循环


看着这些代码,你是否觉得和我原先的MFC代码以及风格很相似呢?呵呵:)


ok,来看看运行效果吧:




有心的朋友可能已经发现在我封装的CCWindow类中有两个消息循环成员函数:

1>RunMsgLoop()  ——  一般的消息循环:GetMessage

2>RunMsgLoop(void (*Display)(), int) ——  更有效的消息循环:PeekMessage

注:1、如果不熟悉这GetMessage、PeekMessage两个API函数的区别,请查阅MSDN

       2、给不明白的朋友一个提示: void (*Display)()  这是函数指针的声明的格式


这个一般的消息循环的实例已经在刚刚这个实例中用到,下面就用第二个:更有效的消息循环

这个更有效的消息循环有两个好处:(1)效率好  (2)我们可以省略计时器的设定


1、所谓效率好,就是程序能利用其空闲的时候——没有消息处理的情况下,来执行一些操作(例如:贴图==)

2、由于利用的是程序没有消息路由的空闲时间来处理一些操作,即只要没有消息,我们就能执行自己想要的代码,那么我们便可以通过Sleep这个函数来模拟计时器的效果——让这些操作的执行能有一个有序的时间间隔


那么这个有效的消息循环,如何使用呢?

你需要自行写一个函数void Display(),然后让这个消息循环执行这个显示函数就ok了


还是来看看代码吧:(我实现的功能,是借用P先生《Windows程序设计》中的一个例子,很simple:不断绘制一些随机位置、大小、颜色的矩形)

注:我并没有使用计时器,Sleep()函数模拟了计时器的作用效果

/*--------------------------------------
		消息循环(更好的消息循环)
			PeekMessage()
		@Display:执行函数
		@interval:函数执行的时间间隔
--------------------------------------*/
int CCWindow::RunMsgLoop(void (*Display)(), int interval)
{
	MSG	msg;
	ZeroMemory(&msg, sizeof(MSG));

	//获取运行到此处时的时间
	int last = GetTickCount();

	//如果不是退出消息
	while(msg.message != WM_QUIT)
	{
		//如果有消息
		if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		//否则, 空闲的时候执行响应函数(大多数是绘制函数)
		else
		{
			//如果窗口客户区大小不为0就是显示(有可能窗口是在最小化)
			if(m_rect.Width() && 
			   m_rect.Height())
			{
				Display();//执行函数
				Sleep(interval);//当前线程睡眠一会儿
			}
		}
	}

	return msg.wParam;
}

在WinMain调用这个更有效的消息循环:

	/*进入消息循环
		1. 使用更好的消息循环wnd.RunMsgLoop(Display, 100)
		2. 使用一般的消息循环wnd.RunMsgLoop()
	*/
	return wnd.RunMsgLoop(Display, 100);


实现效果:


可见一些随机的矩形在不断地绘制,还是很漂亮吧:》



四、你想要的开源源代码

等待多时的开源框架BCF的源代码总算能开始下载了:

开源框架BCF源码以及相关实例



希望大家帮我多测试一下BCF哈,有什么bug请及时告诉我,我将尽快纠正

另外,还欢迎大家和我交流,留下你想说的话(不喜勿碰^_^),因为你们的支持是我继续努力开源的动力:)


……

……


一不留神,时间飞逝,现在都凌晨1点多了——但愿我夜以继日的无私奉献,能帮助到和我有共同兴趣爱好的有志青年,让我们一起踏上美好的游戏编程之旅,扬帆起航,实现我们共同的梦想……


电脑屏幕前的你,晚安,BlueCoder也晚安,BCF,加油,哈哈……



??
??
??
??

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。