前言
开新坑了!
去年被windows编程坑了好几个月也,现在基本上忘完了。
所以现在再来记录一下windows编程的历程算是复习吧。
有事千万别学windows编程,没什么用!我入过这个坑,所以不想真的甩开,这次就写几篇,做一个游戏引擎【图像库】的框架就停手。
设计文档和情节串联图版
管你想做什么游戏,管你使用什么语言和开发技术。千万别瞎jb乱写!
我从我短暂的做人生涯中学到,人是记忆不是很好。就算是100多行的小程序,你过了许久也未必看得明白。
无论是为了之后的学习还是为了开发过程中的调理清晰,写好像样的文档觉得是必须的。
(先写后做,边写边做,做完又写)
首先,先从简单的故事写起。
描写这个游戏是讲什么的、谁是主角、游戏思路是什么,以及如何进行游戏。
然后是游戏的细节。
每次有了新的思路,就把它们加入文档。
除了设计思路,每次进行的较大更改也需要写入文档。
如果不想写连篇累牍的大型设计文档,也可以用情节图版。
游戏构成
Windows桌面程序开发须知
windows系统很小,几十G的内存大多是驱动。
windows主要部分仅仅通过三个动态链接模块来实现,Kernel、User、GDI,代表了windows的三个主要子系统。
- Kernel处理所有在传统上由操作系统处理的事物,如内存管理,文件IO输入输出、多任务管理
- User指使用者接口。实现所有窗口运作机制。
- 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里。
我要做的就是这样一件事,将游戏的流程解耦出几个函数出来,它们的运行流程在内部设计好,在外部只需要实现每个部分就行---------像个黑匣子。
类的设计:
- 因为不知道之后会扩展开发成什么样子,所以我决定在写抽象类的时候,都写成纯虚类。
下面这个纯虚类就是我的游戏引擎的核心了。
定义 | 描述 |
---|---|
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 | 光标处理 |
下面纯虚函数,在下面这个子类里就省略了。还有成员变量对应的接口也省略了。
定义 | 描述 |
---|---|
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游戏编程大师》