- 游戏开发面面观
- 游戏产业的分类——六大游戏市场
- 游戏类型
- WINDOWS编程基础
- Visual Studio文件类型分析
- API
- SDK
- WinMain
- PlaySound
- 工程中包含库文件的方式
- Windows程序的“外貌”——窗口
- 窗口创建四部曲
- 设计
- 注册
- 创建
- 显示与更新
- Windows资源的“身份证”——句柄
- Windows程序的“邮局”——消息与消息队列
- 消息的表现形式——MSG结构体
- 以GetMessage为核心的消息循环体系
- 以PeekMessage为核心的消息循环体系
- 窗口过程函数
- 窗口类的注销
- 完整的窗口程序
- 命名规范
- 初探·GDI2D游戏编程
- GDI中的基本图形
- GDI的函数分类
- 设备环境(DC)
- 获取设备环境句柄(HDC)的两种方式
- 第一种
- 第二种
- Windows屏幕区域相关概念阐述
- GDI程序通用框架
- GDI基本几何绘图
- 创建画笔
- 创建画刷
- 图形对象的选择
- 绘制图形和线条
- 游戏随机数系统初步
- 产生一个范围内的随机数
- 筛选型随机数
- 从连续的一段范围内取随机数
- 从一组乱数中取随机数
- 产生一定范围内随机数的通用算法公式
游戏开发面面观
游戏产业的分类——六大游戏市场
AAA级游戏开发领域
AAA级(游戏业界通常称其为“Triple-A”,即 3A)游戏是我们经常在评价一款游戏是否为大作的衡量标准,是游戏业界“酷”的代名词。
通常指的是游戏平台像XBOX360/Play Station或者 PC上的游戏作品。
通常团队规模50人以上,开发周期在2~5年之间,有着不菲的开发预算。
是成熟的商业作品,通常都有着精美的画质,好莱坞电影风格的过场CG动画,能将艺术与技术进行完美的结合,是会让玩家玩上几分钟就瞬间爱上然后大呼过瘾的游戏大作。
特质:
- 高投入
- 高品质
- 高销量
- 极少的BUG
- 良好可上手性
- 广阔市场前景
- 经过完善的测试
- 图形用户界面友好
- 规模庞大的研发团队
- 大规模市场宣传和炒作
- 将艺术与技术完美结合
- 高表现的视觉与听觉效果
- 让玩家在玩上的最初5分钟入迷
- 愉悦的体验连续、均衡、贯穿整个游戏过程
社交和休闲游戏开发领域
社交和休闲(Social and casual)类游戏与AAA级游戏相比,有着更小的开发团队(5 ~ 25人),更短的开发周期(4 ~ 8个月),更少的开发预算。
主要依附着社交网络(国内如QQ空间、人人网、新浪微博,国外如 facebook,twitter)而存在。
(老少咸宜的QQ农场就是一款在国内风靡一时的典型社交和休闲类游戏)
移动游戏开发领域
移动游戏市场包括普通移动手机,智能手机以及平板电脑。
目前该平台由 Android、iOS 以及Windows Phone这三大智能移动平台主导。
这个市场与社交和休闲类游戏市场有着很大的交集,与社交和休闲类游戏相比,有着更小的开发团队(甚至是个人),更少的预算以及更短的开发周期。
功能型游戏开发领域
功能性游戏(Serious game)是开发出来用于响应特定的企业或者机构的需求的游戏作品。
(比如,国外一款名叫《Remission》的游戏,就是用于帮助孩子们理解什么是化疗以及化疗的作用和重要性的)
(又比如,我国解放军开发的FPS大作《光荣使命》系列游戏,就是为我国的军队量身打造的,进行专门的高科技军事训练的一流游戏作品)
学术型游戏开发领域
学术型游戏开发领域是近年来新出现的游戏产业分支。
(现在越来越多的机构开始资助大学或者研究所的游戏开发团队,促进了这种新生的游戏开发领域的发展)
独立游戏开发领域
独立型游戏开发领域是游戏产业的命脉所在,随着近年移动平台与社交平台的火热崛起,独立游戏开发者们的舞台越扩越宽。
有着这样一群独立游戏开发者,他们视游戏开发为梦想,他们是独立游戏开发领域的主角。
独立开发者是一个游戏背后的唯一创造力量和经济力量。
没有外部的发行商为独立游戏提供资金,也没有其他的实体发挥对游戏内容的创造性控制。
独立游戏大多是自己发行的,但多数独立开发者也愿意与发行商一同协作。发行商不提供开发资金或者控制游戏的开发过程。在这种情况下,独立开发者将首先开发游戏,仅当游戏开发完成时或者快要完成时才让发行商介入。同时,通常独立开发者工作室都非常小,常常仅有1~2人。
游戏类型
-
RPG = Role-playing Game :角色扮演游戏
由玩家扮演游戏中的一个或数个角色,有完整的故事情节的游戏。
玩家可能会与冒险类游戏混淆,其实区分很简单,RPG游戏更强调的是剧情发展和个人体验。
一般来说,RPG可分为日式和美式两种,主要区别在于文化背景和战斗方式。日式RPG多采用回合制或半即时制战斗。 -
ACT = Action Game :动作游戏
玩家控制游戏人物用各种武器消灭敌人以过关的游戏,不追求故事情节。
电脑上的动作游戏大多脱胎于早期的街机游戏和动作游戏如《魂斗罗》《三国志》《鬼泣》系列等。
设计主旨是面向普通玩家,以纯粹的娱乐休闲为目的,一般有少部分简单的解谜成份,操作简单,易于上手,紧张刺激,属于“大众化”游戏。 -
AVG = Adventure Game:冒险游戏
由玩家控制游戏人物进行虚拟冒险的游戏。
与RPG不同的是,AVG的特色是故事情节往往是以完成一个任务或解开某些迷题的形式出现的,而且在游戏过程中刻意强调谜题的重要性。
AVG也可再细分为动作类和解迷类两种,动作类AVG可以包含一些格斗或射击成分。
而解迷类AVG则纯粹依靠解谜拉动剧情的发展,难度系数较大。 -
SLG = Simulation Game:策略游戏
玩家运用策略与电脑或其他玩家较量,以取得各种形式胜利的游戏。(或统一全国,或开拓外星殖民地)
策略游戏可分为回合制和即时制两种。
后又被细分出模拟经营,于是就产生了SIM (simulation)类游戏。 -
RTS = Real-Time Strategy Game:即时战略游戏
本来属于策略游戏SLG的一个分支,但由于其在世界上的迅速风靡,使之慢慢发展成了一个单独的类型,知名度甚至超过了SLG,有点像现在国际足联和国际奥委会的关系。
后来,从其上又衍生出了所谓“即时战术游戏”,多以控制一个小队完成任务的方式,突出战术的作用。 -
FTG = Fighting Game:格斗游戏
由玩家操纵各种角色与电脑或另一玩家所控制的角色进行格斗的游戏。
按呈画技术可再分为2D和3D两种。
此类游戏谈不上什么剧情,最多有个简单的场景设定,或背景展示,场景、人物、操控等也比较单一,但操作难度较大,主要依靠玩家迅速的判断和微操作取胜。 -
STG = Shooting Game:射击类游戏
这里所说的射击类,并非是模拟射击(枪战),而是指纯的飞机射击。
由玩家控制各种飞行物(主要是飞机)完成任务或过关的游戏。
此类游戏分为两种,一叫科幻飞行模拟游戏(Science-Simulation Game),以非现实的想象空间为内容。
另一种叫真实飞行模拟游戏(Real- Simulation Game),以现实世界为基础,以真实性取胜,追求拟真,达到身临其境的感觉。 -
FPS = First Personal Shooting Game:第一人称视角射击游戏
严格来说它是属于动作游戏的一个分支,但和RTS一样,由于其在世界上的迅速风靡,使之展成了一个单独的类型。 -
PZL = Puzzle Game:益智类游戏
Puzzle的原意是指以前用来培养儿童智力的拼图游戏,引申为各类有趣的益智游戏,总的来说适合休闲。 -
RCG ( RAC ) =Racing Game:竞速游戏
在电脑上模拟各类赛车运动的游戏,通常是在比赛场景下进行。
非常讲究图像音效技术,往往是代表电脑游戏的尖端技术。惊险刺激,真实感强,深受车迷喜爱。
另一种说法称之为“Driving Game”。目前,RCG内涵越来越丰富,出现了另一些其他模式的竞速游戏,如赛艇、赛马等等。 -
CAG = Card Game:卡片游戏
玩家操纵角色通过卡片战斗模式来进行的游戏。
丰富的卡片种类使得游戏富于多变化性,给玩家无限的乐趣。 -
TAB = Table Game:桌面游戏
顾名思义,是从以前的桌面游戏脱胎到电脑上的游戏。
如各类强手棋(即掷骰子决定移动格数的游戏),经典的如《大富翁》系列。棋牌类游戏也属于TAB。 -
MSC = Music Game:音乐游戏
培养玩家音乐敏感性,增强音乐感知的游戏。
伴随美妙的音乐,有的要求玩家翩翩起舞,有的要求玩家手指体操。 -
SPG = Sports Game:体育类游戏
模拟各种体育赛事的游戏。
WINDOWS编程基础
Visual Studio文件类型分析

Debug文件夹,当中存放着编译过程中的中间文件,以及最后的目标文件,.exe型的执行文件( Debug 文件夹的出现对应着我们在选择解决方案时选择的是 Debug 型,如果我们解决方案类型为Release型,那么这时工程文件夹下就出现的不是 Debug文件夹,而是名为Release的文件夹)。
工程文件夹(HelloVisualStudio文件夹),当中存放着我们工程相关的源文件,头文件以及若干和后缀名为.vcxproj的项目文件。这个文件夹的名称与工程名的名称一致,因为我们例子中的工程名为 HelloVisualStudio,所以这个工程文件夹名称就为HelloVisualStudio。
扩展名为 .sIn 的文件,它记录着关于解决方案中的信息,我们打开一个解决方案,就是通过双击它。
扩展名为 .suo 的文件,它记录着应用于该解决方案的用户的选项。
扩展名为 .opensdf 的文件,记录着关于项目的状态信息。此文件只在项目处于打开状态时才会有。
API
微软公司为了方便我们开发基于Windows 的应用程序,为我们提供了各种各样的函数。这些函数是Windows操作系统提供给应用程序编程的接口,即Application Programming Interface,简称API。
任何Windows应用程序与Windows本身之间的所有通信,都要使用Windows应用程序编程接口,也就是Windows API。
这个接口说白了就是几百个函数组成的一个集合,它们是Windows操作系统提供的标准函数,可以提供应用程序与Windows互相进行通信的方法。
因为Windows API是在C语言称王争霸叱咤风云的年代开发的,那时候C++还未发迹,所以我们可以发现,在Windows和应用程序之间传递数据的是结构体而不是类。
SDK
Software Development Kit,中文译为软件开发包。
SDK说白了就是一个开发所需资源的一个集合。
所以 DirectX SDK 就是用 DirectX 进行开发的一个资源的打包集合。
这里需要注意API和SDK都是IT业界广泛使用的术语,并不是专指某种特定的API或者SDK。
WinMain
原型:
1
2
3
4
5
6
7int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_ HINSTACNE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow );
WINAPI 就是_stdcall,_stdcall 表示的是一种调用约定,让编译器知道应当以Windows兼容的方式来产生机器指令。
WINAPI有时也写作CALLBACK,二者完全等效,因为宏定义相似。
1
2
3# define CALLBACK __stdcall # define WINAPI __stdcall
如果我们在写WinMain函数的时候去掉这句WINAPI 而直接写:
int WinMain(),仍然是可以编译并运行的,只不过在编译期间会得到一条警告( warning)。
这里的_In_可以理解为一个宏,表示需要我们进行自行输入(input)的一个参数。与其对应的是_Out_,之后我们也会经常在函数定义的代码中看到,它表示指定的这个参数是函数本身向外输出(Output)的一个参数。
第一个参数,HINSTANCE类型的 hInstance,它表示该程序当前运行的实例句柄。
我们可以对这个类型HINSTANCE进行字面上的理解,h前缀表示这个参数的类型为handle,句柄的意思,而后面的Instance中文意思是实例,将两个意思结合起来,所以这个类型就是实例句柄。
hInstance其实就是一个数值。当一个程序在 Windows下运行时,它唯一对应一个运行中的实例。也只有运行中的程序实例,才有资格分配到实例句柄。
一个应用程序可以运行多个实例每运行一个实例,系统都会给该实例分配一个句柄值,并且通过hInstance参数传递给程序的入口点WinMain函数。
第二个参数,HINSTANCE类型的hPrevInstance,表示当前实例的前一个实例的句柄。
我们可以对这个参数进行字面上的理解: h表示参数类型为句柄(handle) , Prev代表先前的(previous)意思,Instance依旧表示实例,那么组合起来就是先前的实例句柄。
对于这个参数的用法,MSDN中明确表示在Win32环境下,该参数总是取 NULL,这就是说,在Win32环境下,这个参数没有存在感,不起任何作用,只是在进行WinMain函数书写时需要将它专门作为一个参数表示出来而已。
第三个参数,LPSTR类型的lpCmdLine,它是一个以空终止的字符串
指定传递给运用程序的命令行参数。
参数肢解:lp前缀表示这个参数为一个指针,Cmd表示 command,命令的意思,与Line组合起来就表示命令行。
(例如,在 Windows7操作系统下的E盘有一个叫ForTheDream.txt 的文件,我们用鼠标双击这个文件时将启动记事本程序(notepad.exe)。此时系统会将E:ForTheDream.txt作为命令行的参数传递给记事本程序的WinMain函数
记事本程序在得到这个文件的文件路径后,就会在窗口中正确显示这个文件的内容)
第四个参数,int类型的nCmdShow,指定程序窗口应该如何显示,是最大化,最小化,还是隐藏等等。
这个参数可有如下取值:
PlaySound
函数定义:
1
2
3
4
5
6BOOL PlaySound( LPCTSTR pszSound, HMODULE hmod, DWORO fdwSound );
必须在编译之前连接winmm.lib库文件。
第一个参数,LPCTSTR类型的pszSound,可以看到作为LPCTSTR类型的此参数为一个字符串,指定了要播放的声音文件。这个值设为NULL 的话,就会把所有当前播放的声音全部停掉。
第二个参数,HMODULE类型的 hmod,包含了在第一个参数中指定的声音文件作为资源的可执行文件的句柄。之后会详细介绍各种资源,这里把这个参数设为NULL就可以了。
第三个参数,DWORD类型的 fdwSound,是一个用来控制声音播放的一个标志。
下面是一些常用标识的列表。
(可以使用一个或者多个标识,标识之间用“|”连接)
工程中包含库文件的方式
- #pragma comment(lib,“winmm.lib”),在""之间填需要链接的库文件,一般置于头文件include语句后。
- 在工程配置中添加库文件的包含。
菜单栏【项目】->【属性】,【配置属性】->【链接器】->【输入】,【附加依赖项】->【编辑】
Windows程序的“外貌”——窗口
每个Windows应用程序至少有一个窗口,称之为主窗口。
一个应用程序通常都包含 标题栏、菜单栏、系统菜单、最小化框最大化框,甚至滚动条。
- 客户区:窗口的一部分,游戏软件通常都在客户区完成游戏中各种画面、功能与效果的实现。而应用程序则主要管理客户区的外观及其操作。
- 非客户区:标题栏、菜单栏、系统菜单、最小化、最大化框。主要由Windows系统来管理。
窗口创建四部曲
设计
Windows已经定义好了一个窗口所具有的所有基本属性。
WNDCLASSEX结构体 定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15typedef struct tagWNDCLASSEX{ UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX, *PWNDCLASSEX;
在WinMain函数中定义一个WNDCLASSEX:
方式一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//开始设计一个完整的窗口类 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化 wndClass.cbSize = sizeof(WNDCLASSEX); // UINT类型的cbSize:结构体的字节数大小 wndClass.style = CS_HREDRAW | CS_VREDRAW ;// UINT类型的style:指定这一类型窗口的风格样式,取多个用逻辑与‘|’连接 wndClass.lpfnWndProc = WndProc; // WNDPROC类型的lpfnWndProc,指向窗口过程函数的函数指针 wndClass.cbClsExtra = 0; // int类型的cbWndExtra,表示窗口的附加内存 wndClass.hInstance = hInstance; // HINSTANCE类型的hInstance,指定包含窗口过程的程序的实例句柄 wndClass.hIcon = (HICON)::LoadImage(NULL,_T("icon.ico"), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE); // HICON类型的hIcon,用于指定窗口类的句标图柄 // 这个成员变量必须是一个图标资源的句柄,若为NULL,则系统提供默认图标 // 用全局的::LoadImage函数从本地加载自定义ico图标 // T("")会根据当前的字符环境智能地在宽字符和普通字符间转换,与unicode字符集环境下的L"icon.ico"是一个用法 wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // HCURSOR类型的hCursor,指定窗口类的光标句柄 // 用LoadCursor加载一个光标资源,返回系统分配给该光标的句柄 // 设为默认的箭头光标 wndClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); // HBRUSH类型的hbrBackground,指定窗口类的背景画刷句柄,hbr(handle to brush) // 当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景 // 用GetStockObject函数来得到系统的标准画刷,指定一个灰色画刷句柄 wndClass.lpszMenuName = NULL; // LPCTSTR类型的lpszMenuName,指定菜单资源的名字 // 若不需要下拉菜单则设为NULL wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop"); // LPCTSTR类型的lpszClassName,一个以空终止的字符串,指定窗口类的名字
方式二:
1
2
3
4
5
6
7
8
9
10
11
12
13WNDCLASSEX wndClass = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0L, 0L, hInstance, (HICON)::LoadImage(NULL, _T("icon.ico"), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE), LoadCursor(NULL, IDC_ARROW), NULL, _T("ForTheDreamOfGameDevelop"), NULL };
第二个参数 UINT类型的style:
常用样式取值:
第三个参数 WNDPROC类型的lpfnWndProc:
一个指向窗口过程函数的函数指针。
窗口过程函数是一个回调函数(不是由该函数的实现方直接调用的,而是在特定事件或条件发生时由另一方调用的,用于对该事件或条件进行响应)。
针对Windows的消息处理机制,窗口过程函数被调用过程如下:
1.在设计窗口类时,将窗口过程函数的地址赋值给lpfnWndProc成员变量
2.调用ResgisterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址
3.当应用程序接收到某一窗口消息时,调用DispatchMessage(&msg)将消息回传给系统,系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理
一个Windows程序可以包含多个窗口过程函数,一个窗口过程总是与某一个特定的窗口类相关联(通过WNDCLASS结构体中的lpfnWndProc成员变量来指定),而基于该窗口类创建的窗口使用的是同一个窗口过程。
第十二个参数 HICON类型的hIconSm:指定窗口类的小图标句柄
注册
注册函数RegisterClassEx的原型声明:
1
2
3
4ATOM WINAPI RegisterclassEX( _In_ const WNDCLASSEX *lpwcx );
注册窗口类对象wndClass(取名)
1
2RegisterClassEx(&wndClass); // 因为变量类型前有一个“*”,所以对应窗口类名前要加上一个&
创建
首先可以调用AdjustWindowsRect()函数根据设定的尺寸和风格来计算窗口的尺寸。
AdjustWindowsRect()函数首先利用一个矩形定义左下角、右上角、左上角、右上角窗口区域的坐标。左上角属性代表窗口的起始位置,结合右下角可以反映窗口的宽度和高度。
AdjustWindowsRect()函数中有一个布尔类型的标明窗口类型的变量,指示窗口是否拥有菜单栏。
CreateWindow()函数创建设计好的这类型窗口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14HWND WINAPI CreateWindow( _In_opt_ LPCTSTR lpClassName, // 指定对应窗口类的名称 _In_opt_ LPCTSTR lpWindowName, // 指定创建的窗口名字(即显示在标题栏上的程序名字) _In_ DWORD dwStyle, // 指定创建的窗口样式 _In_ int x, // 指定窗口的水平位置,一般取CW_USEDEFAULT,表示默认位置 _In_ int y, // 指定窗口的竖直位置,一般取CW_USEDEFAULT,表示默认位置 _In_ int nWidth, // 指定窗口宽度 _In_ int nHeight, // 指定窗口高度 _In_opt_ HWND hWndParent, // 指定被创建窗口的父窗口句柄,一般设为NULL _In_opt_ HMENU hMenu, // 指定窗口菜单的资源句柄,设为NULL _In_opt_ HINSTANCE hInstance, // 指定窗口所属的应用程序实例的句柄,即应用程序的实例ID,即WinMain函数的第一个参数 _In_opt_ LPVOID lpParam // lpParam作为WM_CREATE消息的附加参数lParam传入的数据指针,在MFC程序创建多文档界面时会用到,大多数情况下设为NULL );
(opt是可选参数的意思)
如果窗口创建成功,则CreateWindows函数返回系统为该窗口分配的句柄,若失败,则返回NULL。
1
2
3
4
5HWND hWnd = CreateWindow(_T("ForTheDreamOfGameDevelop"), L"致我们永不熄灭的游戏开发梦想!", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL);
WNDCLASSEX中style成员与CreateWindow函数的dwStyle参数 的区别:
前者指定窗口类的样式,基于该窗口类创建出的窗口都具有这些样式
后者指定某个具体的窗口样式
显示与更新
设定窗口位置主要是 MoveWindow() 这个函数。
可以改变指定窗口的位置和大小,其中窗口位置是以屏幕的左上角为原点(0,0)的。
MoveWindow()函数原型声明:
1
2
3
4
5
6
7
8
9BOOL WINAPI MoveWindow( _In_ HWND hWnd, // 要操作的窗口句柄名称 _In_ int X, // 指定窗口左方相对于屏幕左上角的新位置 _In_ int Y, // 指定窗口上方相对于屏幕左上角的新位置 _In_ int nWidth, // 指定窗口的新宽度 _In_ int nHeight, // 指定窗口的新高度 _In_ BOOL bRepaint // 指定是否要重画窗口,若设定TRUE,窗口会像通常那样在OnPaint消息处理函数中接收到一条WM_PAINT消息 );
1
2MoveWindow(hWnd, 200, 50, 800, 600, true); // 调整窗口显示时的位置,窗口左上角位于屏幕坐标(200, 50)处
ShowWindow()函数进行窗口显示
原型声明:
1
2
3
4
5BOOL WINAPI ShowWindow( _In_ HWND hWnd, // 要显示的窗口句柄 _In_ int nCmdShow // 指定窗口的显示状态 );
1
2
3ShowWindow(hWnd, nCmdShow); // 调用ShowWindow函数来显示窗口 // 可以直接填nCmdShow,因为这个函数是WinMain内部调用的,直接取WinMain函数的参数化就可以了
UpdateWindow()函数刷新窗口
原型声明:
1
2
3
4BOOL UpdateWindow( _In_ HWD hWnd );
1
2UpdateWindow(hWnd); // 对窗口进行更新
Windows资源的“身份证”——句柄
在Windows应用程序中,窗口都是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作的话,首先就是要得到这个窗口的句柄。
句柄(HANDLE)
在Windows程序中,有各种各样的资源,比如窗口、图标、光标等。系统创建这些资源时会为它们分配内存,并返回标识这些资源的标识号,这些标识号就是句柄。
一款优质的游戏一般都会有着个性化的图标与鼠标光标,而为了在我们自己开发的游戏中使用这样个性化的图标与光标资源,我们就会与图标句柄(HICON)和光标句柄(HCURSOR)打交道。
Windows程序的“邮局”——消息与消息队列
Windows程序设计是一种基于事件驱动方式的程序设计模式,Windows程序与操作系统间的通信主要是基于消息的。
每个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用于存放该程序创建的窗口的消息。
每单击一次鼠标或按键,操作系统就会感知事件,将事件包装成消息,投递到消息队列中,然后程序从消息队列中取出消息并响应。
在这个处理过程中,操作系统也会给游戏程序“发送消息”,即操作系统调用程序代码中专门负责处理消息的“窗口过程函数”。
Windows程序运行的机制:Windows将产生的消息依次放到消息队列中,而应用程序则通过一个消息循环不断地从消息队列中取出消息并进行响应。
消息的表现形式——MSG结构体
定义:
1
2
3
4
5
6
7
8
9typedef struct tagMSG{ //msg HWND hwnd; //指定消息所属窗口 UINT message; //指定消息的标识符 WPARAM wParam; //指定此msg的附加信息 LPARAM lParam; //指定此msg的附加信息 DWORD time; //指定投递到消息队列的时间 POINT pt; //指定投递到消息队列中时鼠标的当前位置 } MSG;
第一个参数 HWND类型的hwnd:通常一个消息都是与某个窗口相关联的。(例在某个活动窗口按下鼠标左键,产生的按键消息就是发给这个窗口的)
第二个参数 UINT类型的message:在Windows中,消息由一个数值来表示,不同消息对应着不同数值。由于数值记忆不方便,所以Windows将消息对应的数值定义为WM_XXX宏的形式。(其中WM是Windows Message的缩写,XXX对应某种消息的英文拼写的大写形式)
第三个参数和第四个参数 WPARAM类型的wParam 和 LPARAM类型的lParam:用于指定消息的附加信息。(这两种类型分别是unsigned int 和 long)
第五个参数 DWORD类型的time:表示投递到消息队列中的时间。
第六个参数 POINT类型的pt:表示投递到消息队列时鼠标的当前位置。
以GetMessage为核心的消息循环体系
以PeekMessage为核心的消息循环体系
GetMessage() 与 PeekMessage() 函数的第二个参数通常不要填窗口句柄,最好填0,因为有可能某一时间这个窗口句柄失效了,而消息循环仍在进行,这样会导致错误。
窗口过程函数
窗口过程函数的名字在实际编写程序过程中可以随便取,不一定非要叫WindowProc,但是函数的定义形式必须和上面的声明格式一样。
系统通过窗口过程函数的地址(指针)来调用窗口过程函数,而不是通过函数的名字来调用。
窗口类的注销
UnregisterClass()函数原型声明:
完整的窗口程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90# include <Windows.h> # pragma comment (lib, "winmm.lib") # define WINDOW_WIDTH 800 # define WINDOW_HEIGHT 600 # define WINDOW_TITLE L"HELLO WORLD!" // 窗口过程函数 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); /* __stdcall 表示一种调用约定,让编译器知道应当以Windows兼容的方式来产生机器指令 # define CALLBACK __stdcall # define WINAPI __stdcall */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { /* HINSTANCE 类型的 hInstance:当前运行的实例(instance)句柄(handle) 一个应用程序可以运行多个实例 每运行一个实例,系统都会给运行中的程序分配实例句柄值 HINSTANCE 类型的 hPrevInstance:当前实例的前(previous)一个实例的句柄 在Win32环境下,该参数总是取NULL LPSTR 类型的 lpCmdLine:以空终止的字符串(lp指针),指定传递给运用程序的命令(command)行(line)参数 int 类型的 nCmdShow:指定程序窗口应该如何显示 */ WNDCLASSEX wndClass = {0}; wndClass = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0,0, hInstance, (HICON)::LoadImage(NULL, L"icon.ico", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_DEFAULTSIZE), LoadCursor(NULL, IDC_ARROW), (HBRUSH)GetStockObject(GRAY_BRUSH), NULL, L"ForTheDreamOfGameDevelop" }; if (!RegisterClassEx(&wndClass)) return -1; HWND hwnd = CreateWindow(L"ForTheDreamOfGameDevelop", WINDOW_TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL); MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); MSG msg = {0}; while (msg.message != WM_QUIT) { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); return 0; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_PAINT: ValidateRect(hwnd, NULL); break; case WM_KEYDOWN: if (wParam == VK_ESCAPE) DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, message, wParam, lParam); } return 0; }
运行结果:
命名规范
匈牙利命名法:变量名 = 属性 + 类型 + 对象描述
其中每一对象的名称都要求有明确含义,可以取对象名字全称或名字的一部分。
初探·GDI2D游戏编程
GDI(Graphics Device Interface 或 Graphical Device Interface)。
是微软公司设计的一套API,是风靡全球的Windows操作系统的三大核心部件(也称“子系统”)之一。
GDI(图形设备接口)在Windows操作系统中的地位非常高,掌管了所有显像设备的图像显示及输出功能。
在Windows操作系统下,绝大多数具备图形界面的应用程序都离不开GDI,利用GDI所提供的众多函数,可以方便地在屏幕、打印机及其他输出设备上输出图形、文本等操作。
GDI的出现使程序员无须关心硬件设备及设备驱动,就可以将应用程序的输出转化为硬件设备上的输出,实现了程序开发者与硬件设备的隔离。
GDI+可以理解为GDI的一个技术升级版,在很多方面都添加了GDI不曾有过的功能。
很多人认为,GDI+最出彩的一点是对多图片格式的支持,GDI只对常见的bmp位图格式支持地很好。
GDI+基于GDI的基础上再次开发,比GDI更高层,即离底层硬件更远,所以绘图效率远不如GDI。
GDI中的基本图形
-
映像模式和变换
虽然内定以像素为单位进行绘图,但是我们并不用画地为牢,局限在当中。GDI映像模式允许我们以英寸(甚至以几分之一英寸)、毫米或任何想使用的单位来绘图。 -
图元
图元(Metafile)是以二进制形式储存的GDI命令集合,主要用于通过剪贴板传输向量图形。 -
绘图区域
绘图区域是形状任意的复杂区域,通常定义为较简单的绘图区域组合。在GDI内部,绘图区域除了储存为最初用来定义绘图区域的线条组合外,还以一系列扫描线的形式储存。我们可以将绘图区域用于绘制轮廓、填入图形和剪裁。 -
路径
路径是GDI内部储存的直线和曲线的集合。路径可以用于绘图、填入图形和剪裁,还可以转换为绘图区域。 -
剪裁
剪裁就是绘图可以限制在显示区域的某一部分中。剪裁区域无论是不是矩阵都可以,剪裁通常是通过区域或者路径来定义的。 -
调色盘
在GDI中,调色盘一般是指在图形输出设备(比如显示器)上可用的颜色范围。 -
打印
和打印机相关的内容。
GDI的函数分类
设备环境(DC)
DC(Device Context),一般译为设备环境、设备上下文、设备描述表。
就绘图的观点而言,DC就是程序可以进行绘图的地方。
系统中可以具有多个设备环境,每一个设备环境都有一个与之对应的关联设备。
应用程序在进行图像输出时,只需关心设备环境的类型。
如果需要将图像输出到特定的设备,只需创建相应类型的设备句柄(HDC),对于不同类型的设备环境操作方式都是统一的。
应用程序中不需要关注设备硬件的异同,无论哪种设备数据接口、哪种通信数据的格式都是同样的处理,GDI的接口都是相同的。
在程序需要用GDI来绘图时,必须先取得设备环境的句柄。
在取得句柄后,Window用内定的属性值填入到内部设备的内容结构中,我们可以调用不同的GDI函数来改变这些默认值,也可以利用一些GDI函数取得这些属性的当前值。当然,还有其他的GDI函数能够在窗口的显示区域里真正地绘图。
当程序在显示区域绘图完毕后,必须释放掉设备环境句柄。
句柄只是一个数值而已,被释放后就不再有效,且不能再使用。
程序必须在处理单个消息处理期间取得和释放句柄。
获取设备环境句柄(HDC)的两种方式
第一种
BeginPaint()函数原型声明:
LPPAINTSTRUCT:LP远指针,PAINTSTRUCT结构体。
第二个参数是需要填写一个指向PAINTSTRUCT类型结构体的指针,或者把一个PAINTSTRUCT类型结构体进行取地址操作“&”。
hdc:用于绘制的句柄
fErase:为非零值则擦除背景,否则不擦除背景
rcPaint:通过制定左上角和右下角的坐标确定一个要绘制的矩形范围,这个矩形单位相对于客户区左上角
后面三个参数都是系统预留,一般用不到。
1
2PAINTSTRUCT paintStruct; // 定义PAINTSTRUCT结构体
如果BeginPaint()函数成功,返回值是指定窗口的设备环境句柄,如果失败,则返回NULL。
EndPaint()函数原型声明:
ValidateRect()函数,用于更新指定窗口的无效矩形区域,使之有效。
第二种
可以调用GetDC()函数来取得句柄,且在调用完后,需要调用ReleaseDC()对设备环境进行释放。
GetDC()函数原型声明:
ReleaseDC()函数原型声明:
调用实例:
Windows屏幕区域相关概念阐述
屏幕上的二维坐标表示法:
客户区的二维坐标表示法:
在Windows操作系统中,以屏幕左上角为原点。
屏幕上任何位置都可以用一个坐标来表示,称为屏幕坐标。
当程序中调用某些以坐标点为实惨的函数时,都需要给它们传递相应的屏幕坐标。
ClientToScreen()函数:转换客户区坐标为屏幕坐标
ScreenToClient()函数:转换屏幕坐标为客户区坐标
GDI程序通用框架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136# include <Windows.h> # pragma comment (lib, "winmm.lib") # define WINDOW_WIDTH 800 # define WINDOW_HEIGHT 600 # define WINDOW_TITLE L"HELLO WORLD!" HDC g_hdc = NULL; // 全局设备环境句柄(g_用于标识全局变量) // 窗口过程函数 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); BOOL Game_Init(HWND hwnd); //资源初始化 VOID Game_Paint(HWND hwnd); // 绘图代码 BOOL Game_CleanUp(HWND hwnd); // 资源清理 /* __stdcall 表示一种调用约定,让编译器知道应当以Windows兼容的方式来产生机器指令 # define CALLBACK __stdcall # define WINAPI __stdcall */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { /* HINSTANCE 类型的 hInstance:当前运行的实例(instance)句柄(handle) 一个应用程序可以运行多个实例 每运行一个实例,系统都会给运行中的程序分配实例句柄值 HINSTANCE 类型的 hPrevInstance:当前实例的前(previous)一个实例的句柄 在Win32环境下,该参数总是取NULL LPSTR 类型的 lpCmdLine:以空终止的字符串(lp指针),指定传递给运用程序的命令(command)行(line)参数 int 类型的 nCmdShow:指定程序窗口应该如何显示 */ // 设计一个完整的窗口类 WNDCLASSEX wndClass = {0}; wndClass = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0,0, hInstance, (HICON)::LoadImage(NULL, L"icon.ico", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_DEFAULTSIZE), LoadCursor(NULL, IDC_ARROW), (HBRUSH)GetStockObject(GRAY_BRUSH), NULL, L"ForTheDreamOfGameDevelop" }; // 注册窗口类 if (!RegisterClassEx(&wndClass)) return -1; // 正式创建窗口 HWND hwnd = CreateWindow(L"ForTheDreamOfGameDevelop", WINDOW_TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL); // 窗口的移动、显示、更新 MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // 游戏资源的初始化,若初始化失败,弹出一个消息框,并返回FALSE if (!Game_Init(hwnd)) { MessageBox(hwnd, L"资源初始化失败", L"消息窗口", 0); return FALSE; } // 消息循环过程 MSG msg = {0}; // 定义并初始化msg while (msg.message != WM_QUIT) // 若消息不是WM_QUIT消息,则继续循环 { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) // 查看应用程序消息队列,有消息时将队列中的消息派发出去 { TranslateMessage(&msg); // 将虚拟键消息转换为字符消息 DispatchMessage(&msg); // 分发一个消息给窗口程序 } } // 窗口类的注销 UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); return 0; } // 报错已定义,故注释掉 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT paintStruct; // 定义一个PAINTSTRUCT结构体记录绘制信息 switch (message) { case WM_PAINT: // 客户区重绘消息 g_hdc = BeginPaint(hwnd, &paintStruct); // 指定窗口进行绘图工作的准备,并将和绘图有关的信息填充到paintStruct结构体中 Game_Paint(hwnd); EndPaint(hwnd, &paintStruct); // 标记指定窗口的绘画过程结束 ValidateRect(hwnd, NULL); // 更新客户区的显示 break; case WM_KEYDOWN: // 键盘按下消息 if (wParam == VK_ESCAPE) // ESC键 DestroyWindow(hwnd); // 销毁窗口,并发送一条WM_DESTROY消息 break; case WM_DESTROY: // 窗口销毁消息 Game_CleanUp(hwnd); // 调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理 PostQuitMessage(0); // 向系统表明有个线程有终止请求,用来响应WM_DESTROY消息 break; default: return DefWindowProc(hwnd, message, wParam, lParam); // 调用默认的窗口过程 } return 0; } BOOL Game_Init(HWND hwnd) { g_hdc = GetDC(hwnd); Game_Paint(hwnd); ReleaseDC(hwnd, g_hdc); return TRUE; } VOID Game_Paint(HWND hwnd) { } BOOL Game_CleanUp(HWND hwnd) { return TRUE; }
GDI基本几何绘图
创建画笔
HPEN是画笔对象的句柄数据类型,用于标识一个画笔对象。
CreatePen()函数原型声明:
第一个参数,PS_SOLID实线,虚线:PS_DASH短线、PS_DOT点、PS_DASHDOT短线和点间隔、PS_DASHDOTDOT短线间间隔两个点,PS_NULL表示线不可见。
此外,还可以使用ExtCreatePen()函数和CreatePenIndirect()函数创建画笔。
创建画刷
HBRUSH是画刷对象句柄数据类型,用于标识一个画刷对象。
CreateSolidBrush()函数原型声明:
功能是创建实心画刷,用于填充时,填充使用纯色。
COLORREF类型的crColor指定了画刷的颜色,取一个RGB值即可。
CreateHatchBrush()函数原型声明:
功能是创建一个阴影画刷,不是实心的,用于填充时,填充的内容是阴影线。
图形对象的选择
1
2
3
4
5HGDIOBJ SelectObject( __in HDC hdc, // 设备环境句柄 __in HGDIOBJ hgdiobj // 被选用对象的句柄 );
绘制图形和线条
LineTo函数的原型声明:
MoveToEx()函数可以移动画笔当前点,原型声明:
绘制矩形使用的GDI函数——Rectangle()函数原型声明:
游戏随机数系统初步
计算机中一般不能产生绝对随机的随机数。
计算机产生随机数的过程,是根据一个数(种子)为基准以某个递推公式推算出来的一系列数,当这系列数很大的时候,就符合正态分布,从而相当于产生了随机数,但这并不是真正的随机数,当计算机正常开机后,这个种子的值是确定的,除非我们调用了改变种子数值的函数(如srand()函数),或者对系统进行了某种修改。
也就是说,计算机一般情况下只能生成相对的随机数,即伪随机数。
伪随机数不是假随机数,“伪”是有规律的意思,计算机产生的伪随机数既是随机的又是有规律的。
产生的伪随机数有时遵守一定的规律,有时不遵守任何规律。
伪随机数有一部分遵守一定的规律,另一部分不遵守任何规律。
srand()初始化随机种子,rand()产生随机数。
rand()函数和原型声明:
1
2int rand(void);
调用无需设置参数:
1
2rand();
- rand()的内部实现用的是线性同余法,不是真的随机数,因其周期特别长,故在一定的范围里可以看成是随机的。
- 这种伪随机数是由小M多项式序列生成的,其中产生每个小序列都有一个初始值,即随机种子。(小M多项式序列的周期是65535,即每次利用一个随机种子生成的随机数周期是65535,当你取得65535个随机数后,它们又重复出现了)
- 目前,计算机中用来产生随机数的算法基本都是“线性同余”法,rand()返回一随机数值的范围在0至RAND_MAX之间,其中RAND_MAX的范围是32767到2147483647之间(int型)
- 0 ~ RAND_MAX 每个数字被选中的几率是相同的
- 用户未设定随机数种子时,系统默认的随机数种子为 1
- rand()产生的是伪随机数,每次执行时基于的伪随机数序列是相同的,若要让这个伪随机数列不同,就需要用随机数发生函数srand()初始化它,即对随机数种子进行“播种”。
在 0 ~ 100 之中取一个随机数:
1
2
3int AnswerNumber; AnswerNumber = rand() % 101;
srand()函数用于设置供rand()使用的随机数种子,如果在第一次调用rand()前没有调用srand(),系统会自动调用一次srand(),但使用相同的数调用srand()会导致生成相同的随机数序列。
所以如果想得到高质量的随机数,在使用rand()函数之前,一定要正确地调用srand()函数进行随机数种子的播种。
srand()函数原型声明:
1
2
3
4void srand( unsigned int seed );
参数seed必须为无符号整型,通常可以利用返回系统当前时间的time(NULL)的返回值来当作种子(seed)。
1
2srand((unsigned)time(NULL)); // 用系统时间初始化随机种子,time()函数需要头文件time.h
1
2srand(timeGetTime()); // 用系统时间初始化随机种子
产生一个范围内的随机数
1
2
3j = (int)(100.0 * rand() / (RAND_MAX + 1.0)); // 产生 0 ~ n 之间的随机数 j = random(100); // 产生 0 ~ n 之间的随机数
筛选型随机数
1
2
3
4
5
6x = random(100); // 取 0 ~ 99 的随机数 while (x == 6) // 但不能是 6 x = random(100); while ((x % 5) == 0) // 但不能是 5 的倍数 x = random(100);
从连续的一段范围内取随机数
1
2x = random(11) + 40; // 从 40 ~ 50 范围内取随机数
从一组乱数中取随机数
1
2
3
4a = new Array(67, 87, 34, 78, 12, 5, 9, 108, 999, 378); j = random(10); x = a[j];
产生一定范围内随机数的通用算法公式
最后
以上就是踏实帅哥最近收集整理的关于《逐梦旅程——windows游戏编程之从零开始》笔记游戏开发面面观WINDOWS编程基础的全部内容,更多相关《逐梦旅程——windows游戏编程之从零开始》笔记游戏开发面面观WINDOWS编程基础内容请搜索靠谱客的其他文章。
发表评论 取消回复