Windows【游戏】编程开篇:Violet框架

前言

开新坑了!
去年被windows编程坑了好几个月也,现在基本上忘完了。
所以现在再来记录一下windows编程的历程算是复习吧。

有事千万别学windows编程,没什么用!我入过这个坑,所以不想真的甩开,这次就写几篇,做一个游戏引擎【图像库】的框架就停手。

设计文档和情节串联图版

管你想做什么游戏,管你使用什么语言和开发技术。千万别瞎jb乱写!

我从我短暂的做人生涯中学到,人是记忆不是很好。就算是100多行的小程序,你过了许久也未必看得明白。

无论是为了之后的学习还是为了开发过程中的调理清晰,写好像样的文档觉得是必须的。

(先写后做,边写边做,做完又写)
首先,先从简单的故事写起。
描写这个游戏是讲什么的、谁是主角、游戏思路是什么,以及如何进行游戏。
然后是游戏的细节。
每次有了新的思路,就把它们加入文档。
除了设计思路,每次进行的较大更改也需要写入文档。

如果不想写连篇累牍的大型设计文档,也可以用情节图版。
在这里插入图片描述

游戏构成

在这里插入图片描述

Windows桌面程序开发须知

windows系统很小,几十G的内存大多是驱动。
windows主要部分仅仅通过三个动态链接模块来实现,Kernel、User、GDI,代表了windows的三个主要子系统。

  1. Kernel处理所有在传统上由操作系统处理的事物,如内存管理,文件IO输入输出、多任务管理
  2. User指使用者接口。实现所有窗口运作机制。
  3. GDI是一个图形设备接口,允许程序在屏幕和打印机上显示文字和图形。

Windows API是Windows应用程序接口,学这个很无聊。

其中32位的Windows操作系统的编程接口叫Win32 API,使用VS2013、VS2015的在开发时需要选择创建Win32应用程序,而VS2017笔者所用的,是创建Windows桌面应用程序或Windows桌面向导。

窗口和消息

各种各样的窗口,有各种各样的属性,自然在创建窗口时就需要很复杂的类来囊括这些属性了。

windows应用程序执行后,系统会为程序建立一个消息队列。有些消息是需要放入队列,有些不需要。

资源和句柄

应用程序窗口中,经常会用到图标、光标、菜单和对话框等。
这些是windows全部资源类型,存储在.exe文件中(并不是驻留在程序的数据区域中)。

资源不能通过在程序代码中定义变量来直接存取,而是由windows提供的API来加载它们到内存中。

使用资源的好处,在于程序的许多组件能够连接编译进程序的.exe文件中。 如果没有资源,如图标图像之类的二进制文件可能会存放在单独的文件中,.exe文件会把它读入内存中使用。或者图标不得不在程序中以字节数组的形式定义(这样就无法看到实际的图标图像了)。 作为资源,图标文件存储在开发者计算机硬盘可单独编辑的文件中,但在编译程序中被连接编译进.exe文件。

句柄HANDLE是一个(通常32位)无符号的唯一整数值(long),用于标识应用程序中各种对象、资源、窗口、光标、应用程序实例等。
系统通过句柄来找到相应的对象、资源,从而进行管理和操作。

句柄的实际值对程序来说是无关紧要的,但是程序中的windows模块知道利用它使用相对应的对象。

按资源的类型,句柄可分为图标句柄HICON,光标句柄HCURSOR,窗口句柄HWND,应用程序实例句柄HINSTANCE等。

windows桌面应用程序框架

使用VS2017创建Windows桌面应用程序,会得到一个简单的wIndows程序框架。
在实际开发中并不是直接走捷径,而是靠一个简化版的框架来编程。

创建一个windows应用程序空项目,将下面的框架代码加入。

#include<Windows.h>


//用于注册的窗口类名
const char temp[] = "MyWindowClass";
LPTSTR gClassName= (LPTSTR)temp;




void Paint(HWND hwnd);//画一条线
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);//回调函数
void Register_MyWindow(HINSTANCE hInstance);//1 注册
HWND CreateMyWindow(HINSTANCE hInstance, int iCmdShow, LPCWSTR gWindowName);//2 创建



//主函数
int   WINAPI  WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow){

	HWND hwnd;
	MSG msg;
	//1 注册
	Register_MyWindow(hInstance);
	//2 创建
	hwnd = CreateMyWindow(hInstance, iCmdShow, TEXT("我的窗口名称"));
	//3 消息循环
	while (GetMessage(&msg, NULL, 0, 0) > 0){
		TranslateMessage(&msg);
		DispatchMessage(&msg);

	}

	return msg.wParam;//WM_QUIT
}


void Paint(HWND hwnd) {

	PAINTSTRUCT ps;
	HDC hDC;
	HPEN _pen;
	hDC = BeginPaint(hwnd, &ps);
	_pen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
	SelectObject(hDC, _pen);
	MoveToEx(hDC, 50, 50, NULL);
	LineTo(hDC, 150, 100);
	EndPaint(hwnd, &ps);


}
//4窗口过程
LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

	switch (msg)
	{
	case WM_PAINT://窗口绘制消息

		Paint(hwnd);
		break;
	case WM_CLOSE://窗口关闭消息
		DestroyWindow(hwnd);
		break;
	case WM_DESTROY://窗口销毁消息
		PostQuitMessage(0);
		break;
	default://pass to system
		return DefWindowProc(hwnd, msg, wParam, lParam);

	}
	return 0;

}

//1 注册窗口类
void Register_MyWindow(HINSTANCE hInstance) {

	WNDCLASSEX wc;
	//1配置窗口属性
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = 0;
	wc.lpfnWndProc = MyWindowProc;//设置第四步的窗口过程回调函数
	wc.cbWndExtra = 0;
	wc.cbClsExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);//默认程序图标
	wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);//默认程序图标
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);//光标为箭头
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = gClassName;


	//2注册
	if (!RegisterClassEx(&wc)) {
		MessageBox(NULL, TEXT("窗口注册失败"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
		exit(0);//进程结束
	}


}

//2 创建窗口
HWND CreateMyWindow(HINSTANCE hInstance, int iCmdShow, LPCWSTR gWindowName) {

	HWND hwnd;
	hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,
		gClassName,
		gWindowName,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		600, 400,  //出现坐标 
		NULL, NULL, hInstance, NULL);


	if (hwnd == NULL) {
		MessageBox(NULL, TEXT("窗口创建失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
		exit(0);//进程退出
	}

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);
	return hwnd;

}

显示效果:
在这里插入图片描述

引擎类的设计

每次都在上面那个框架中编程太无聊了。为了不用再管windows编程的框架代码,我需要将其都封装起来。

如果玩过processing应该知道如何友好的编程。不经过main函数,而是经过自己的入口函数,再经过自己设计的流程,最后循环在自己设计的loop里。

我要做的就是这样一件事,将游戏的流程解耦出几个函数出来,它们的运行流程在内部设计好,在外部只需要实现每个部分就行---------像个黑匣子。

在这里插入图片描述

类的设计:

  • 因为不知道之后会扩展开发成什么样子,所以我决定在写抽象类的时候,都写成纯虚类。

下面这个纯虚类就是我的游戏引擎的核心了。

V i o l e t Violet类

定义 描述
virtual void Init(int iCmdShow) = 0 引擎的初始化,传入是显示种类
virtual long HandleEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) = 0 实现回调函数里应该实现的部分
virtual void Logic() = 0 游戏逻辑
virtual void End() = 0 游戏结束
virtual void Paint(HDC hdc) = 0 游戏绘制,常常需要在缓冲后
virtual void KeyAction(int ActionType) = 0 按键处理
virtual void MouseAction(int x, int y, int ActionType) = 0 光标处理

下面纯虚函数,在下面这个子类里就省略了。还有成员变量对应的接口也省略了。

M y E n g i n e MyEngine类

定义 描述
void Register_Window() 注册窗口,写在Init()内
HWND Create_Window() 创建窗口,写在Init()内
static MyEngine* p_myEngine 在引擎外部使用这个静态指针指向本身
HINSTANCE m_Instance 应用程序实例句柄
HWN m_Window 主窗口句柄
int m_show 显示的形式,如是显示还是隐藏。是全屏还是最小化
TCHAR m_WndClass[32] 窗口类的名称
TCHAR m_Title[32] 主游戏窗口名称
WORD m_Icon, m_Small_Icon 游戏的两个程序图标的数字ID
int width, height 游戏屏幕宽高
int m_Frame_delay 游戏循环周期间隔,单位ms
bool isSleep 游戏是否休眠。windows程序常常在你把窗口最小化或是遮挡等闲置时会“休眠”,再次点进才会继续

Violet.h

#ifndef _VIOLET_H_
#define _VIOLET_H_


#include<Windows.h>

/*----------------------------------------非成员的外部自定义函数声明区域-------------------------------------*/


/*------------------------------------------------------------------------------------------------------------------------*/



//windows应用程序入口函数
int WINAPI        WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow);
//windows应用程序回调函数
long CALLBACK  WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);




//游戏引擎的命名空间
namespace vio {


	/*------------------------------------非成员的内部函数声明区域----------------------------------------------*/
	//初始化游戏
	bool Game_Initialize(HINSTANCE hInstance);
	//游戏循环
	void Game_Cycle();

	/*---------------------------------------------------------------------------------------------------------------------*/


	/*
	写一个接口类,不要有除了静态成员外其他成员变量
	要有纯虚接口方法
	要有虚析构函数,并提供默认实现
	不要声明构造函数

	 */
	class Violet
	{
	public:


		virtual ~Violet() = default;



		//纯虚函数声明(具体游戏需要重载这些函数并增加游戏功能代码
		virtual void Init(int iCmdShow) = 0;
		virtual long HandleEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) = 0;

		virtual void Logic() = 0;									//游戏逻辑处理
		virtual void End() = 0;										//游戏结束处理
		virtual void Paint(HDC hdc) = 0;
		virtual void KeyAction(int ActionType) = 0;	//处理按键行为
		virtual void MouseAction(int x, int y, int ActionType) = 0;	//处理鼠标行为


	};//Violet 


	//基础的引擎类,更强大的类应该在此基础上派生
	class MyEngine : public Violet {
	public:

		MyEngine(HINSTANCE h_instance, LPCTSTR sz_winclass, LPCTSTR sz_title,
			WORD icon = NULL, WORD sm_icon = NULL,
			int winwidth = 800, int winheight = 600);
		virtual ~MyEngine() = default;


	
		//注册窗口
		void Register_Window();
		//创建窗口
		HWND Create_Window();



		virtual void Init( int iCmdShow);		//游戏初始化
		virtual long HandleEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

		virtual void Logic();									//游戏逻辑处理
		virtual void End();								//游戏结束处理
		virtual void Paint(HDC hdc);
		virtual void KeyAction(int ActionType);	//处理按键行为
		virtual void MouseAction(int x, int y, int ActionType);//处理鼠标行为



		

	public:
		// 访问方法

		// 在引擎外部使用这个静态方法访问指向引擎的静态指针 
		static MyEngine* GetEngine();



		HINSTANCE	 GetInstance();
		HWND				 GetWindow();
		void					 SetWindow(HWND hWindow);
		LPTSTR			 GetTitle();
		WORD				 GetIcon();
		WORD				 GetSmallIcon();
		int						 GetWidth();
		int						 GetHeight();
		int						 GetFrameDelay();
		void					 SetFrameRate(int iFrameRate);
		bool					 AreSleeped();
		void					 SetSleep(BOOL bSleep);

	protected:

		static MyEngine*		 p_myEngine;//指向本类的引擎指针

		HINSTANCE             m_Instance;				 //应用程序实例
		HWND					     m_Window;					 //主窗口句柄
		int                             m_show;//表示显示的形式,比如是显示,还是隐藏,是全屏幕,还是最小化
		TCHAR                    m_WndClass[32];	     //窗口类的名称
		TCHAR                    m_Title[32];               //主游戏窗口的名称
		WORD                     m_Icon, m_Small_Icon;		 //游戏的两个程序图标的数字ID
		int                             width, height;		 //游戏屏幕的宽度和高度
		int                             m_Frame_delay;               //游戏周期之间的间隔,单位是ms
		bool                          isSleep;					 //表示游戏是否在休眠
										 

	};//MyEngine

};//vio

#endif





Violet.cpp

#include"Violet.h"

namespace vio {
	MyEngine* MyEngine::p_myEngine = NULL;
};


//windows应用程序入口函数
int WINAPI        WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,	PSTR szCmdLine, int iCmdShow) {

	MSG msg;
	static int trigger = 0;
	int count = 0;

	//游戏初始化
	if (vio::Game_Initialize(hInstance)) {

		//引擎初始化
		vio::MyEngine::GetEngine()->Init(iCmdShow);


		//3 消息循环
		while (1) {
			if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
				if (msg.message == WM_QUIT)
					break;
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else {
				//游戏没有休眠
				if (false == vio::MyEngine::GetEngine()->AreSleeped()) {
					//设置帧率
					count = GetTickCount();
					if (count > trigger) {
						trigger = count + vio::MyEngine::GetEngine()->GetFrameDelay();
						vio::Game_Cycle();
					}
				}
			}
		}
	}


	vio::MyEngine::GetEngine()->End();

	return msg.wParam;//WM_QUIT
}

//windows应用程序回调函数
long CALLBACK  WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {

	return vio::MyEngine::GetEngine()->HandleEvent(hWnd, msg, wParam, lParam);
}



/*
自己的小引擎,变量难免会撞车,所以使用命名空间在所难免。

*/
namespace vio {



	

	MyEngine::MyEngine(HINSTANCE h_instance, LPCTSTR sz_winclass, LPCTSTR sz_title,
		WORD icon , WORD sm_icon ,
		int winwidth , int winheight ) {

		p_myEngine = this;
		m_Instance = h_instance;
		m_Window = NULL;
		if (lstrlen(sz_winclass) > 0)
			lstrcpy(m_WndClass, sz_winclass);
		if (lstrlen(sz_title) > 0)
			lstrcpy(m_Title, sz_title);
		m_Icon = icon;
		m_Small_Icon = sm_icon;
		width = winwidth;
		height = winheight;
		m_Frame_delay = 50;
		isSleep = true;//游戏休眠

	}


	//注册窗口
	void MyEngine::Register_Window(){

		WNDCLASSEX wc;
		//1配置窗口属性
		wc.cbSize = sizeof(WNDCLASSEX);
		wc.style = CS_HREDRAW | CS_VREDRAW;
		wc.lpfnWndProc = WndProc;//设置第四步的窗口过程回调函数
		wc.cbWndExtra = 0;
		wc.cbClsExtra = 0;
		wc.hInstance = m_Instance;
		wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);//默认程序图标
		wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);//默认程序图标
		wc.hCursor = LoadCursor(NULL, IDC_ARROW);//光标为箭头
		wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
		wc.lpszMenuName = NULL;
		wc.lpszClassName = m_WndClass;


		//2注册
		if (!RegisterClassEx(&wc)) {
			MessageBox(NULL, TEXT("窗口注册失败"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
			exit(0);//进程结束
		}

	}
	//创建窗口
	HWND MyEngine::Create_Window(){

		HWND hwnd;
		hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,
			m_WndClass,
			m_Title,
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT,
			CW_USEDEFAULT,
			width, 
			height,  
			NULL, NULL, m_Instance, NULL);


		if (hwnd == NULL) {
			MessageBox(NULL, TEXT("窗口创建失败!"), TEXT("错误"), MB_ICONEXCLAMATION | MB_OK);
			exit(0);//进程退出
		}

		ShowWindow(hwnd, m_show);
		UpdateWindow(hwnd);
		return hwnd;

	}




	void MyEngine::Init( int iCmdShow) {

		m_show = iCmdShow;//显示标志

		//1 注册
		Register_Window();
		//2 创建
		m_Window = Create_Window( );
	
		
	}
	long MyEngine::HandleEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {


		switch(msg){
		case WM_CREATE:
			SetSleep(false);//激活游戏
			return 0;
		case WM_SETFOCUS:
			return 0;
		case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HWND hwnd = GetEngine()->GetWindow();
			HDC hdc = BeginPaint(hwnd, &ps);
			//绘图
			Paint(hdc);
			EndPaint(hwnd, &ps);
		}
			return 0;
		case WM_DESTROY:
			End();
			PostQuitMessage(0);
			return 0;
		}

		return DefWindowProc(hWnd, msg, wParam, lParam);

	}

	void MyEngine::Logic() {


	}
	void MyEngine::End() {


	}
	void MyEngine::Paint(HDC hdc) {
		//实际绘图

	}
	void MyEngine::KeyAction(int ActionType) {


	}
	void MyEngine::MouseAction(int x, int y, int ActionType) {


	}



	// 访问方法
	MyEngine* MyEngine::GetEngine() { return p_myEngine; }

	HINSTANCE MyEngine::GetInstance() { return m_Instance; };
	HWND      MyEngine::GetWindow() { return m_Window; };
	void      MyEngine::SetWindow(HWND hWindow) { m_Window = hWindow; };
	LPTSTR    MyEngine::GetTitle() { return m_Title; };
	WORD      MyEngine::GetIcon() { return m_Icon; };
	WORD      MyEngine::GetSmallIcon() { return m_Small_Icon; };
	int       MyEngine::GetWidth() { return width; };
	int       MyEngine::GetHeight() { return height; };
	int       MyEngine::GetFrameDelay() { return m_Frame_delay; };
	void      MyEngine::SetFrameRate(int iFrameRate) { m_Frame_delay = 1000 / iFrameRate; };
	bool      MyEngine::AreSleeped() { return isSleep; };
	void      MyEngine::SetSleep(BOOL bSleep) { isSleep = bSleep; };


};//vio

main.cpp

#include"Violet.h"

bool vio::Game_Initialize(HINSTANCE hInstance) {

	MyEngine* pGame = new MyEngine(hInstance,
		TEXT("很好"),  //类名
		TEXT("我的窗口"), //窗口名
		NULL,
		NULL,
		800,  //窗口宽
		400); //窗口高
	return true;
}

void vio::Game_Cycle() {

	//do something.....

}

这个简单的引擎框架就完成了。下一篇讲如何绘图。
在这里插入图片描述

参考:《Windows游戏编程》《Windows游戏编程大师》

猜你喜欢

转载自blog.csdn.net/weixin_41374099/article/details/88677236