1.vs技巧
项目建错了如何修改项目类型?
项目文件夹右键属性-->配置属性-->链接器-->系统-->修改子系统的值
修改项目字符集
项目文件夹右键属性-->配置属性-->常规-->修改字符集的值
头文件路径设置
依次打开“项目——属性——配置属性——C++——常规——附加包含目录”中加入所有的路径。
这里需要注意的是:
- 路径必须指向头文件所在的子文件夹,而不能直到父文件夹就结束
- 每个路径不需要加上双引号,输入了之后,vs会自动加上双引号,如果自己加可能vs无法识别双引号
- 如果是多个路径,路径直接用“;”隔开。
2. win32显示图片
导入图片
注意:导入的图片的格式只能是bmp格式的(图片句柄hbitmap类型对应的类型就是bmp的)
资源视图-->添加资源-->选择Bitmap-->导入-->选择图片导入(导入的图片会有自己的ID)
贴图
贴图实际上叫拷贝位图,是指从一个DC里面拷贝到另一个DC里面,
步骤:
- 需要两个DC
HDC dc = ::GetDC(hwnd); //GetDC得到的DC不需要放入图片,默认有位图,为窗口的背景图
HDC HMemDC = ::CreateCompatibleDC(dc); //创建兼容性DC,默认里面没有DC
- 每个DC中都要图片
HBITMAP hBitmap = ::LoadBitmap(实例句柄,MAKEINTRESOURCE(要加载的图片的ID));
::SelectObject(hMemDC,hBitmap);
- 明白哪个是目标,哪个是源,从哪个拷贝到哪个
::BitBlt(dc,0,0,500,500,hMemDC,0,0,SRCCOPY);
- 删除
::DeleteObject(hBitmap); //删除位图
::ReleaseDC(hwnd,dc); //释放DC
::DeleteDC(hMemDC); //删除兼容性DC
如何贴一个透明背景的图
- 第一种方法是将图片通过其他工具处理后,使其背景透明
- 这里介绍第二种方法,这种方法需要准备两张一样的图片,但是一张背景颜色为白色,一张背景颜色为黑色
HDC hMemDC = ::CreateCompatibleDC(dc);
HBitmap hBitmapWhite = ::LoadBitmap(hInst,MAKEINTRESOUTCE(IDB_BITMAP1));
::SeleteObject(hMemDC,hBitmapWhite);
::BitBlt(dc,0,0,200,200,hMemDC,0,0,SRCAND);
HBitmap hBitmapBlack = ::LoadBitmap(hInst,MAKEINTRESOUTCE(IDB_BITMAP2));
::SeleteObject(hMemDC,hBitmapBlack);
::BitBlt(dc,0,0,200,200,hMemDC,0,0,SRCPAINT);
::DeleteDC(hMemDC);
先贴白色图片,再贴黑色图片,两个背景部分是与运算,然后背景就变透明了,而其他地方显示正常
让图片动起来
这里一定需要用到的一个东西,就是定时器了,我们再哪里创建定时器?之前在Win32笔记中说,在主函数的窗口创建函数之后,消息循环之前。这里我们可以让它在窗口创建的时候就启动定时器,然后定时器消息来的时候,去改变贴图的位置坐标,就能实现图片的移动了
int x = 0; //用来控制图片移动
case WM_CREATE: //CreateWindow 发送的
{
::SetTimer(hwnd,1,500,0); //C++中的NULL和0没有区别
}
break;
case WM_TIMER:
{
x+=5; //每次向右移动5个像素点
HDC dc = ::GetDC(hwnd);
//-------------------------------------
//注释中间的内容可以封装在一个ShowBitmap(dc)函数中以便调用,后面的例子中就直接写ShowBitmap(dc)函数
HDC HMemDC = ::CreateCompatibleDC(dc);
HBITMAP hBitmap = ::LoadBitmap(hInst,MAKEINTRESOUCE(IDB_BITMAP1));
::SelectObject(hMemDC,hBitmap);
::BitBlt(dc,x,0,500,500,hMemDC,0,0,SRCCOPY);
::DeleteObject(hBitmap);
::DeleteDC(hMemDC);
//-------------------------------------
::ReleaseDC(hwnd,dc);
}
break;
这样写我们发现,图片是移动了,但是图片之前的“踪迹”还在,不想显示这个“踪迹”,可以在每次改变了贴图位置,在贴要移动的图之前先贴一张背景图上去将原来的覆盖掉就好了
用到的函数:
CreateCompatibleDC()
- 功能:创建一个与指定DC兼容的兼容性DC
- 原型:
HDC CreateCompatibleDC(
__in HDC hdc
);
- 参数:
hdc 现有DC的句柄。如果此句柄为空,函数将创建一个与应用程序当前屏幕兼容的内存DC。
- 返回值:
如果函数成功,返回值是内存DC的句柄。
如果函数失败,返回值为空。
DeleteDC()
- 功能:删除指定的DC。
- 原型:
BOOL DeleteDC(
__in HDC hdc
);
- 参数:
hdc 关联设备的句柄。
- 返回值:
如果函数成功,返回值是非零的。
如果函数失败,返回值为零。
LoadBitmap()
- 功能:加载位图资源
- 说明:由LoadBitmap加载的所有位图最终应用DeleteObject清除。
- 原型:
HBITMAP LoadBitmap(
__in HINSTANCE hInstance,
__in LPCTSTR lpBitmapName
);
- 参数:
hInstance 一个模块实例的句柄,该模块的可执行文件包含要加载的位图。如果想加载系统位图,那么将第一个参数设为NULL。
lpBitmapName 一个指向以空字符结束的字符串的指针,该字符串包含要加载的位图资源的名称。。如果位图与整数标识符而不是与名称有联系,那么该参数就可以使用MAKEINTRESOURCE宏。
- 返回值:
如果函数成功,返回值是指定位图的句柄。
如果函数失败,返回值为空。
DeleteObject()
- 功能:DeleteObject函数删除逻辑笔、笔刷、字体、位图、区域或调色板,释放与该对象相关的所有系统资源。删除对象后,指定的句柄不再有效。
- 原型:
BOOL DeleteObject(
__in HGDIOBJ hObject
);
- 参数:
hObject 逻辑笔、笔刷、字体、位图、区域或调色板的句柄。
- 返回值:
如果函数成功,返回值是非零的。
如果指定的句柄无效或当前被选中到DC中,则返回值为零。
BitBlt()
- 功能:
- 原型:
BOOL BitBlt(
__in HDC hdcDest,
__in int nXDest,
__in int nYDest,
__in int nWidth,
__in int nHeight,
__in HDC hdcSrc,
__in int nXSrc,
__in int nYSrc,
__in DWORD dwRop
);
- 参数:
hdcDest 目标设备的DC
nXDest 目标矩形左上角的x坐标,以逻辑单位表示
nYDest 目标矩形左上角的y坐标
nWidth 源矩形与目标矩形的宽度
nHeight 源矩形与目标矩形的高度
hdcSrc 源设备的DC
nXSrc 源矩形左上角的x坐标
nYSrc 源矩形左上角的y坐标
dwRop 光栅操作代码。这些代码定义了源矩形的颜色数据如何与目标矩形的颜色数据结合以实现最终的颜色。
下面列出一些常用的光栅操作代码。
值 | 意义 |
---|---|
BLACKNESS | 使用物理调色板中与索引0关联的颜色填充目标矩形。(此颜色为默认物理调色板的黑色。) |
CAPTUREBLT | 包含所有在生成的图像中位于窗口顶部的分层窗口。默认情况下,图像只包含您的窗口。注意,这通常不能用于打印设备上下文。 |
DSTINVERT | 反转目标矩形。 |
MERGECOPY | 通过使用布尔与操作符,将源矩形的颜色与当前在hdcDest中选择的画笔进行合并。 |
MERGEPAINT | 使用OR运算符将源矩形的反色与目标矩形的颜色进行合并。 |
NOMIRRORBITMAP | 防止位图被镜像。 |
NOTSRCCOPY | 将源矩形反向复制到目标。 |
NOTSRCERASE | 使用OR运算符组合源矩形和目标矩形的颜色,然后对结果颜色进行反色。 |
PATCOPY | 将当前在hdcDest中选择的画笔复制到目标位图中。 |
PATINVERT | 将当前在hdcDest中选择的画笔颜色与目标矩形的颜色通过布尔XOR操作符进行组合。 |
PATPAINT | 使用OR运算符将此操作的结果与目标矩形的颜色组合在一起。 |
SRCAND | 使用与运算符将源矩形和目标矩形的颜色进行组合。 |
SRCCOPY | 直接将源矩形复制到目标矩形。 |
SRCERASE | 使用与运算符将目标矩形的反色与源矩形的颜色组合在一起。 |
SRCINVERT | 使用异或运算符组合源矩形和目标矩形的颜色。 |
SRCPAINT | 使用OR运算符组合源矩形和目标矩形的颜色。 |
WHITENESS | 使用与物理调色板中的索引1关联的颜色填充目标矩形。(默认物理调色板的颜色为白色。) |
- 返回值:
如果函数成功,返回值是非零的。
如果函数失败,返回值为零。要获得扩展的错误信息,调用GetLastError。
3.写一个可以应用于所有基于Win32游戏的游戏框架
接口设计
使用虚函数去设计一些接口,在创建游戏的时候就可以直接去继承这个游戏框架的类,在子类里面去重写这些接口函数就好了
创建一个CGameApp类,在CGameApp.h文件中这样去设计:
#include <windows.h>
Class CGameApp
{
protected:
HINSTANCE m_hIns; //实例句柄
HWND m_hMainWnd; //主窗口句柄
public:
CGameApp()
{
m_hIns = 0;
m_hMainWnd = 0;
}
virtual ~CGameApp(); //必须要虚析构;
public:
void SetHandle(HINSTANCE hInstance,HWND hWnd)
{
this->m_hIns = hInstance;
this->m_hMainWnd = hWnd;
}
public:
//考虑到有些游戏可能不会用到所有的接口,所以只需要定义一个虚函数即可,不需要定义成纯虚函数
virtual void OnCreateGame(){} //游戏创建时要执行的操作,可能会使用实例句柄和窗口句柄
virtual void OnGameDraw(){} //游戏窗口重绘时要执行的操作
virtual void OnGameRun(WPARAM nTimerID){} //游戏运行时要执行的操作,定时器消息处理,参数记录了定时器ID
virtual void OnKeyDown(WPARAM nKey){} //键盘按下,参数记录了哪个虚拟键码
virtual void OnKeyUp(WPARAM nKey){} //键盘抬起
virtual void OnLButtonDown(POINT point){} //鼠标左键按下,参数为鼠标按下时的坐标
virtual void OnLButtonUp(POINT point){} //鼠标左键抬起
virtual void onm ouseMove(POINT point){} //鼠标移动
};
...
相应的在WinMain源文件中,应该这样写:
HINSTANCE hIns; //全局变量,在消息处理函数中需要用到WinMain函数中的实例句柄,所以需要该变量去承接
void CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hProvInstance,LPSTR lpCmdLine,int nCmdShow)
{
hIns = hInstance;
...
}
CGameApp* pGame = NULL; //创建一个游戏类,这里应该new一个子类
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_CREATE:
{
if(pGame != NULL)
pGame->SetHandle(hIns,hWnd); //设置句柄
pGame->OnCreateGane(); //初始化游戏
break;
}
case WM_PAINT:
{
if(pGame != NULL)
pGame->OnGameDraw();
break;
}
case WM_TIMER:
{
if(pGame != NULL)
pGame->OnGameRun();
break;
}
case WM_KEYDOWN:
{
if(pGame != NULL)
pGame->OnKeyDown(wParam);
break;
}
case WM_LBUTTONDOWN:
{
POINT point;
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
if(pGame != NULL)
pGame->OnLButtonDown(point);
break;
}
case ...
}
}
问题1
以上代码存在一个问题,我们去写一个游戏框架的目的就是为了以后进行复用,所以我们不应当在使用此框架的时候修改CGameApp.h和WinMain.cpp这两个文件
但是WinMain.cpp文件并不知道你要写的类名叫什么,也就是说当你去写CGameApp* pGame = new ...
的时候你并不知道应该去new一个什么样子的子类,如果我们每次都去打开WinMain.cpp文件去修改,那么这个框架就没有意义了。
如何解决?
我们可以在WinMain.cpp中声明一个函数,而不去做定义,这个函数在我们写的CGameApp的子类的源文件中去定义
WinMain.cpp中添加这样的声明:
CGameApp* CreateObject();
//创建消息中使用CreateObject()
case WM_CREATE:
pGame = CreateObject(); //创建一个子类对象
...
break;
那么用户去使用这个游戏框架就多了一个要求(或者说是前提,即用户必须这样做,不这样做不可以使用),用户必须在CGameApp类的子类的源文件中,定义CreateObject()函数,并且返回自己定义的游戏类:
CGameApp* CreateObject()
{
return new CPlaneApp;
}
当然,为了给用户提供最大限度的便利,我们可以将CreateObject()函数的实现,在CGameApp.h中用一个宏来代替,用户只需要在使用的时候,包含这个宏即可,实现如下:
//CGameApp.h中
#define IMPLEMENT(ThisClass)\
CGameApp* CreateObject()\
{\
return new ThisClass;\
}
// "\"用来连接
这时候用户只需要在自己定义的游戏类(继承CGameApp)的源文件中使用这个宏即可:
IMPLEMENT(CPlaneApp);
问题2
好的,现在未知子类对象的问题解决了,但是类似的我们又有一个新的问题,用户在使用游戏框架的时候,想要按照自己的意愿去设计游戏窗口名称,窗口大小,背景,位置等的时候,是无法在不改动游戏框架代码的基础上去实现的,所以下面我们来解决这个问题
参照问题1的解决方法,我们在CGameApp.h文件中定义一个结构体,用以保存创建窗口时的一系列数据,这里我们只需自定义窗口名称,位置,大小即可,背景是可以在重绘消息中自行更改的。
typedef struct InitMyWindow
{
LPCWSTR WindowName; //窗口名字
int x; //窗口位置x值
int y; //窗口位置y值
int Width; //窗口宽度
int Height; //窗口高度
}InitWindow;
在WinMain.cpp文件中去声明一个函数,这个函数去返回一个InitWindow结构体,如下:
InitWindow DesignWindow();
为给用户提供方便,我们同样在CGameApp.h文件中用带参数的宏去替换该函数的定义,如下:
#define WINDOWDESIGN(windowName,x1,y1,width,height) InitWindow DesignWindow(){ \
InitWindow window; \
window.WindowName = (windowName); \
window.x = (x1); \
window.y = (y1); \
window.Width = (width); \
window.Height = (height); \
return window; \
}
在WinMain.cpp文件中,去使用DesignWindow()函数获取一个InitWindow类型的结构体,用该结构体成员去初始化窗口:
在设计窗口之前,应该先获取该结构体,如下:
InitWindow MyWindow = DesignWindow(); //获取初始化窗口的一系列数据
创建窗口的时候,去使用该结构体成员作为CreateWindow()函数的参数,如下:
//创建窗口
hWnd = CreateWindow(windowEx.lpszClassName,MyWindow.WindowName,WS_OVERLAPPEDWINDOW,MyWindow.x,MyWindow.y,MyWindow.Width,MyWindow.Height,NULL,NULL,hInstance,NULL);
这样一来,用户使用该框架就有了第二条要求,在用户自己定义的游戏类中需要去使用WINDOWDESIGN
这个宏规定窗口名称,窗口位置和大小,如下:
WINDOWDESIGN(L"biubiu",100,100,400,550)
总结:
总的来说这还是一个很简陋的框架,很多地方还是不够完善,比如用户如果想要自己设计窗口图标呢?(当然这些都是可以解决的)不过自己写小游戏的时候,使用它能够方便一下。
完整框架示例代码:
- CGameApp.h
#pragma once
#include <windows.h>
#define IMPLEMENT(ThisClass) CGameApp* CreateObject(){return new ThisClass;}
#define WINDOWDESIGN(windowName,x1,y1,width,height) InitWindow DesignWindow(){ \
InitWindow window; \
window.WindowName = (windowName); \
window.x = (x1); \
window.y = (y1); \
window.Width = (width); \
window.Height = (height); \
return window; \
}
typedef struct InitMyWindow
{
LPCWSTR WindowName; //窗口名字
int x; //窗口位置x值
int y; //窗口位置y值
int Width; //窗口宽度
int Height; //窗口高度
}InitWindow;
class CGameApp
{
protected:
HINSTANCE m_hIns;
HWND m_hWnd;
public:
CGameApp(void)
{
m_hIns = 0;
m_hWnd = 0;
}
virtual ~CGameApp(void){};
public:
void SetHandle(HINSTANCE hIns,HWND hWnd)
{
m_hIns = hIns;
m_hWnd = hWnd;
}
public:
virtual void OnCreateGame(){}
virtual void OnGameDraw(){}
virtual void OnGameRun(WPARAM TimeID){}
virtual void OnKeyDown(WPARAM nKey){}
virtual void OnKeyUp(WPARAM nKey){}
virtual void OnLButtonDown(WPARAM wParam,POINT point){}
virtual void OnLButtonUp(WPARAM wParam,POINT point){}
virtual void onm ouseMove(WPARAM wParam,POINT point){}
};
- WinMain.cpp
#include <windows.h>
#include "GameApp.h"
//函数声明
CGameApp* CreateObject();
InitWindow DesignWindow();
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
//全局变量
HINSTANCE hIns;
int CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hProvInstance,LPSTR lpCmdLine,int nCmdShow)
{
//变量声明
WNDCLASSEX windowEx;
HWND hWnd;
MSG msg;
//变量赋值
hIns = hInstance;
//设计窗口
InitWindow MyWindow = DesignWindow(); //获取初始化窗口的一系列数据
windowEx.style = CS_VREDRAW | CS_HREDRAW;
windowEx.cbSize = sizeof(windowEx);
windowEx.cbClsExtra = 0;
windowEx.cbWndExtra = 0;
windowEx.hInstance = hInstance;
windowEx.hbrBackground = CreateSolidBrush(RGB(225,176,225));
windowEx.hCursor = NULL;
windowEx.hIcon = NULL;
windowEx.hIconSm = NULL;
windowEx.lpfnWndProc = &WndProc;
windowEx.lpszClassName = L"haha";
windowEx.lpszMenuName = NULL;
//注册窗口
if(RegisterClassEx(&windowEx) == 0)
{
MessageBox(NULL,L"注册失败",L"提示",MB_OK);
return 0;
}
//创建窗口
hWnd = CreateWindow(windowEx.lpszClassName,MyWindow.WindowName,WS_OVERLAPPEDWINDOW,MyWindow.x,MyWindow.y,MyWindow.Width,MyWindow.Height,NULL,NULL,hInstance,NULL);
if(hWnd == NULL)
{
MessageBox(NULL,L"创建失败",L"提示",MB_OK);
return 0;
}
//显示窗口
ShowWindow(hWnd,SW_SHOW);
while(GetMessage(&msg,NULL,0,0))
{
//翻译消息
TranslateMessage(&msg);
//分发消息
DispatchMessage(&msg);
}
return 0;
}
CGameApp* pGame = NULL;
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_CREATE:
{
//创建一个子类对象
pGame = CreateObject();
if(pGame != NULL)
{
//设置句柄
pGame->SetHandle(hIns,hWnd);
pGame->OnCreateGame();
}
}
break;
case WM_PAINT:
{
if(pGame != NULL)
{
pGame->OnGameDraw();
}
}
break;
case WM_TIMER:
{
if(pGame != NULL)
{
pGame->OnGameRun(wParam);
}
}
break;
case WM_KEYDOWN:
{
if(pGame != NULL)
{
pGame->OnKeyDown(wParam);
}
}
break;
case WM_KEYUP:
{
if(pGame != NULL)
{
pGame->OnKeyUp(wParam);
}
}
break;
case WM_LBUTTONDOWN:
{
if(pGame != NULL)
{
POINT point;
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
pGame->OnLButtonDown(wParam,point);
}
}
break;
case WM_LBUTTONUP:
{
if(pGame != NULL)
{
POINT point;
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
pGame->OnLButtonUp(wParam,point);
}
}
break;
case WM_MOUSEMOVE:
{
if(pGame != NULL)
{
POINT point;
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
pGame->OnMouseMove(wParam,point);
}
}
break;
case WM_CLOSE:
{
PostQuitMessage(0);
}
break;
}
return DefWindowProc(hWnd,message,wParam,lParam);
}
说明
光使用这个两个文件去编译是会报无法解析的外部命令的错误,如下:
为什么呢?
上文中我们就说了,使用此框架有两个要求:
- 必须使用宏
WINDOWDESIGN
去规定窗口参数 - 必须使用宏
IMPLEMENT
返回一个子类对象
所以我们只需要定义一个自己的游戏类去继承CGameApp类,在该游戏类的源文件中按照要求使用宏 IMPLEMENT
和 WINDOWDESIGN
,正常编译即可