简介《Windows游戏编程大师技巧第二版》源码光盘文件打包面向Windows平台游戏开发初学者与进阶者可作为学习DirectX图形编程、游戏循环、动画与音效处理的对照范例。压缩包共含1035个文件其中bmp位图资源多达518个cpp源程序159个exe可执行演示140个wav音效99个还有pal调色板、h头文件、scn场景文件等辅助资源整体约38.49MB目录按demo1_3至demo3_1等章节示例组织便于按原书顺序查阅运行。已有492人学习下载适合配套原书阅读时动手调试各章演示程序。包内除完整工程源码外还包含大量位图背景、按钮导航、光标图标与MIDI音乐素材以及编译好的exe文件可直接运行观察效果也可结合源码深入理解Windows游戏编程中的设备上下文、精灵动画、碰撞检测与音效播放等经典实现是游戏编程学习者难得的完整参考包。1. Windows游戏编程大师技巧第二版源码包先把光盘内容拖进硬盘再谈编译这份《Windows游戏编程大师技巧第二版》的源码包是从原书附带光盘上整理出来的完整资源。十几年前的光盘到现在多数已经读不出来就算能读光驱也成了稀有设备。把整套随书源码单独打成 ZIP至少让那些还愿意研究底层图形编程的开发者能继续拿到一份可用的文本素材。它解决的是两件事一是把当年光盘里最要紧的源码部分完整保留下来二是让拆解过程中最费时的“到处找小文件”变成一次解压就能完成的操作。适合谁适合正在复习旧式 DirectDraw 编程思路、想把成熟源码改造成可运行 demo 的人也适合想了解 Windows 游戏程序在 Win32 消息循环基础上怎么做渲染和输入的人。2. 解压后的第一件事先用目录结构判断这套源码的完整程度拿到任何源码包第一反应都应该是“先别急着按编译器按钮”。尤其这种年代感很强的资源多半混杂了书稿示例、半成品引擎、第三方库和工具链配置文件。直接放进 Visual Studio 里强行编译轻则几十个报错重则把上千个文件搅成一团。这一章把目录结构先拉出来后面所有操作就都有坐标可查。2.1 先列一个文件树确认光盘时代最典型的“三件套”是否都在在资源管理器里逐层双击文件夹效率太低直接打开命令行在解压目录里执行一次递归列表tree /F /A . | more/F列出所有文件名/A用文本符号代替图形线框more防止输出滚屏太快。这一步能看到完整层级也能一眼判断出文件有没有因为压缩而损坏。老光盘源码包里一般能明显看到三类东西随书章节对应的示例源码、一个被多章共享的公共引擎目录、以及若干工具和第三方数据目录。如果这三个类别里缺了公共引擎,那就意味着每个示例都无法独立编译后面就要准备自己补代码了。2.2 从命名习惯读出源码包的模块切分方式老程序员给文件起名比现在直白得多。看到大量CH、DEMO、LIB、SAMPLE这样的前缀基本就能推断出全书的目录结构。我习惯在列完文件树之后专门做一张对照表方便快速查找。目录或前缀常见内容编译时需要留意的地方CHxx、DEMOxx对应书里第 x 章的示例源码部分例子依赖公共库改动公共库会波及全部示例LIB、COMMON、ENGINE被封装的窗口类、图形工具类、输入工具类编译顺序上优先处理先给其它示例准备头文件和静态库TOOLS、UTILS图片转换、地图编辑、调试辅助程序不参与主项目编译按需单独打开DATA、BMP、WAV、MAP素材文件运行时通过相对路径读取不要移动或改名这张表不是用来背的而是用来建立心理地图。真正要编译运行的时候你只需要盯着ENGINE和当前示例两个目录就好其它地方就算有报错也基本不影响主流程。2.3 找出被反复引用的公共库它就是整套源码的心脏光盘时代的书作者都会把大量重复代码抽到一个公共模块里。这样做的好处是书里每个章节都能直接用同一套封装不必每次从头讲窗口创建。对读者来说这套公共库正好是阅读顺序上的核心文本先读它再顺手浏览两个 demo源码结构基本上就全通了。我一般会先在公共库目录下找两个文件一个负责封装 Win32 窗口和消息循环另一个负责封装图形表面。它们的典型特征是有大量类似CWin、CGame、CSurface的类名以及一堆以DD开头的 DirectDraw 调用。找到这两个文件之后不要急着全面编译先把它们的头文件路径记下来。后面所有示例项目都会 include 这两个头文件头文件路径配错是最常见的首个报错来源。公共库里还经常出现一些奇怪的参数写法比如把调色板索引硬编码成常数。这不是作者偷懒而是那个时期 DirectDraw 在调色板模式下必须直接操作索引值。读这部分代码时最好忍住“改面向对象一点”的冲动先忠实保留原样后面运行阶段再去优化。3. 拿新工具链编译旧源码工程文件、头文件路径、链接库三件事这章是从“能读”跨到“能跑”的关键一步。很多源码包解压后代码一眼看去很完整但新的开发环境已经不认识早年的工程文件。花几小时在编译配置上看起来像在做杂活实际上是把旧工具链的约定逐步翻译成新工具链能理解的配置。3.1 升级向导不是万能的.dsp 和 .vcxproj 之间隔着一整套平台约定源码包里大量出现以.dsp、.dsw结尾的工程文件那是十几年前 VC 6.0 时代的格式。新版 Visual Studio 在打开这种文件时会尝试运行升级向导。但升级向导面对引入 DirectDraw、旧版 Windows SDK 的头文件和库时经常只生成一个空壳项目甚至直接拒绝加载。与其和向导搏斗我更推荐直接绕开它在解压目录里新建一个空目录把当前 demo 的.cpp、.h全部复制进去再通过“新建项目 → 空项目 → 添加现有项”的方式手动建立工程。这样能最大程度保留源码本身不让工具链把原有代码结构改得面目全非。手工建立工程时要注意.h文件是否放在stdafx.h里统一包含。这属于旧工程常见的预编译头写法新项目默认开启stdafx.h机制时不要删掉它只需确认项目设置里的预编译头选项是开启状态。如果源码本身没有stdafx.h则在项目属性里关闭预编译头否则每个.cpp都会报“找不到 stdafx.h”的错。3.2 DirectX 头文件优先级把旧版 SDK 的 Include 目录放到最前面源码调用的是 DirectDraw、DirectInput、DirectSound 这一代 API。新版 Windows SDK 里保留了部分头文件但定义和旧源码的预期不一致。最稳妥的做法是单独安装一份完整的 DirectX SDK我一般用 2010 年 6 月那一版它是最后一份独立发布的 DX 开发包然后把它的Include路径放在所有路径的最前面。INCLUDED:\DXSDK\Include;D:\source\wgpg\includes;D:\source\wgpg\lib注意这里不能把新 SDK 的头文件目录排在前面。旧源码调用CreateSurface、SetCooperativeLevel这类函数时参数列表和新的头文件声明有细微差异如果系统自带的头文件抢先被 include 进来后续会产生大量“重定义”和“参数类型不匹配”错误。设置路径不一定要靠上面的环境变量写法。在 VS 项目属性里的VC 目录 → 包含目录中手动添加也可以。但有一点必须遵守D:\DXSDK\Include必须排在最上面编辑框里上下调整时每次保存后再打开确认一次顺序因为这个编辑器偶尔会按字母序重排条目。3.3 链接库别漏ddraw、dxguid、winmm 几个名字必须同时出现源码注释里经常只提到一两个函数名但链接器要求的是具体的.lib文件。新手最容易在“编译通过、链接失败”这一步卡住。报错信息通常是unresolved external symbol DirectDrawCreate unresolved external symbol _main第一个错误说明ddraw.lib没加进链接器输入第二个错误通常说明入口函数写成了WinMain却选择了控制台子系统或者反过来。处理链接问题的标准动作是在当前示例的头部加一段链接器指令#pragma comment(lib, ddraw.lib) #pragma comment(lib, dxguid.lib) #pragma comment(lib, winmm.lib) #pragma comment(lib, dsound.lib) #pragma comment(lib, dinput8.lib)dxguid.lib经常被忽略但它包含 DirectX 里的 GUID 定义比如CLSID_DirectDraw、IID_IDirectDraw7。不加它时会报出大量“无法解析的外部符号”而且每个符号看起来都跟 GUID 相关很容易让人以为是代码写错实际上只是少连了一个库。winmm.lib是 Windows 多媒体库提供时钟和高精度计时相关函数。游戏主循环经常用它做帧率统计少了它也会在链接阶段报错。用工具链检查这些.lib是否存在时可以直接去 DX SDK 的Lib目录下dir一下确认 x86 版本存在即可。4. 跑通一个示例从窗口程序到 DirectDraw 表面的最小改动编译配置补全后接下来的目标就是让一个示例真正弹出一个游戏窗口。建议先找一个不读外部纹理、不需要按键输入的最简 demo把工程编译跑通再逐步替换成更复杂的项目。这一章用具体代码演示从入口函数到图形表面初始化的过程。4.1 先看入口函数统一用WinMain还是main源码包里早期章节的示例多数使用WinMain这是纯 Windows 程序的入口。后期练习章节里偶尔能看到main那是作者为了方便书稿排版写的控制台版本。入口函数不同项目属性里的“子系统”设置就得相应调整。WinMain对应Windowsmain对应Console。如果选错链接器第一阶段就会报unresolved external symbol main或WinMain。#include windows.h int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // 消息循环驱动整个程序 MSG msg {0}; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return msg.wParam; }这段代码是 Win32 程序的基本骨架。GetMessage在没有消息时会让线程阻塞等待系统派发新的窗口消息。游戏循环之所以要用PeekMessage替代是因为它需要持续刷新画面不能一直阻塞在消息队列里。源码包里更靠后的 demo 会逐步把PeekMessage版本替换掉原本的消息循环那个版本才能保证游戏画面连续刷新。4.2 DirectDraw 初始化的三个关键参数协作等级、显示模式、表面描述DirectDraw 对象的创建过程非常模式化总共就几步创建对象、设置协作等级、设置显示模式、创建主表面。每个步骤都可能返回错误码所以源码几乎都保留了if (FAILED(...))的检查逻辑。LPDIRECTDRAW7 lpDD NULL; DirectDrawCreateEx(NULL, (LPVOID*)lpDD, IID_IDirectDraw7, NULL); lpDD-SetCooperativeLevel(hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); lpDD-SetDisplayMode(640, 480, 16, 0, 0); DDSURFACEDESC2 ddsd {0}; ddsd.dwSize sizeof(ddsd); ddsd.dwFlags DDSD_CAPS | DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP; ddsd.dwBackBufferCount 1; LPDIRECTDRAWSURFACE7 lpddsPrimary NULL; lpDD-CreateSurface(ddsd, lpddsPrimary, NULL);DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN表示以独占全屏模式运行这是旧式 DirectDraw 游戏最常见的启动方式。SetDisplayMode里的640, 480, 16代表分辨率 640×480、色深 16 位。这里要注意第三个参数必须和表面管理逻辑匹配16 位色深下每个像素占 2 字节很多古老的图像读取函数都默认按 8 位调色板处理素材格式不符时会直接花屏。DDSURFACEDESC2结构是整段代码里最需要关心的对象。它通过dwFlags告诉 DirectDraw 你准备使用哪些字段不要自作主张往结构里填没被dwFlags声明的数据。源码里常见DDSCAPS_BACKBUFFER配DDSCAPS_FLIP的组合表示这是一个支持翻转的复杂主表面后面就能用Flip方法把后台缓冲呈现到屏幕。4.3 运行到这一步就把“黑匣子”打开一半了很多人在CreateSurface这一步返回失败后直接怀疑自己的硬件不行。实际上在虚拟机或远程桌面环境里DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN经常会失败因为底层显示驱动并没有提供真正的独占模式。这时候把协作等级改成DDSCL_NORMAL同时把SetDisplayMode去掉就能以窗口模式跑通初始化。lpDD-SetCooperativeLevel(hWnd, DDSCL_NORMAL);窗口模式不需要设置显示模式主表面可以直接覆盖客户区。代价是效率略低但在调试阶段足够用了。我自己的习惯是凡是遇到运行环境不支持全屏的就保留DDSCL_NORMAL改窗口等逻辑全部调通后再回到全屏验证性能。这个习惯能省下大量因为显示模式不匹配导致的排查时间。5. 源码包避坑排查手册四类高频问题的现象、原因和处理这一章收录的是最常出现的坑按“现象 → 原因 → 解决”的方式记录。它们不是某个示例独有的问题而是在整个源码包范围内反复会撞上的共同障碍。5.1 编译报错 C1083dxerr.h 或 ddraw.h 找不到现象编译刚开始就弹出fatal error C1083: Cannot open include file: ddraw.h后面还有一串发生在 DirectX 相关头文件里的连带报错。原因当前项目的“包含目录”里没有旧版 DirectX SDK 的头文件路径。新版 Windows SDK 虽然也有部分同名头文件但源码调用的接口声明早于它们头文件之间互相排斥。解决安装一份完整的 DirectX SDK把它的Include路径放到项目包含目录的第一位。随后检查Linker → AdditionalLibraryDirectories同样把 DX SDK 的Lib路径排到最前面。最后重新生成解决方案时注意选择 Win32 平台不要默认选 x64这些旧库多数没有 64 位版本。5.2 打开 .dsp 后升级向导直接退出或转换后一片空现象双击 .dsp 文件VS 弹出升级提示点击确认后项目文件消失只剩一个空的解决方案。原因.dsp 这种老格式依赖 VC 6.0 时代的“平台名称”和项目类型 GUID新版工具链已经把对应映射移除导致向导生成失败。解决不要用向导转换。新建空项目把源码里的.cpp、.h用“添加现有项”的方式导进去。如果源码用到stdafx.h在预编译头设置里选择“使用”并检查每个.cpp的第一行是否都有#include stdafx.h。这个办法虽然朴素但能绕开所有工程格式映射问题。5.3 链接失败unresolved external symbol DirectDrawCreate12现象编译阶段完全通过链接阶段出现unresolved external symbol DirectDrawCreate12后面跟着一长串 DirectDraw 相关函数。原因链接器没有找到ddraw.lib。更隐蔽的情况是ddxguid.lib没有加入导致所有使用 GUID 作为参数实参的函数都解析不了。解决在项目里添加这几项到附加依赖库ddraw.lib、dxguid.lib、winmm.lib、dinput8.lib、dsound.lib。也可以直接在每个使用 DirectDraw 的.cpp文件头部用#pragma comment(lib, ddraw.lib)固定下来。我推荐后面这种写法因为源码包里的示例很多每个示例单独在 IDE 里配置链接库太容易遗漏。5.4 运行后黑屏窗口内没有画面也不退出现象程序启动后窗口显示出来了但没有渲染结果按键盘也没有反应调试器里看不到异常。原因典型的全屏切换失败或者后台缓冲区创建成功后Flip循环没进入绘制状态。很多老示例默认DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN在非独占环境下表面指针仍是空的Flip调用返回失败又被源码用if (FAILED(...))静静吞掉。解决把协作等级降为DDSCL_NORMAL去掉SetDisplayMode再把Flip改成Blt或者直接把绘制代码放到窗口的WM_PAINT里。这样调试时至少能在窗口上看到每帧图像再逐步恢复成原版全屏模式。6. 进阶用法从这套旧源码里提炼你自己的最小游戏框架编译跑通不是终点。把源码包里的公共库和示例揉成一个可以反复使用的小框架才是这套资源真正能沉淀下来的价值。6.1 抽取三个核心文件替换掉全套依赖我通常只保留三个文件一个负责窗口创建和消息循环一个负责 DirectDraw 表面管理一个负责把输入、图像、声音三个子模块串接起来的顶层类。这三个文件对应源码包里核心目录下的同名类抽出来后把所有#include收敛成你自己框架里唯一的引用入口。#include core_platform.h #include core_render.h #include core_app.hcore_platform.h管窗口core_render.h管表面core_app.h管整个游戏生命周期。这样抽取以后新写 demo 时只需要关心每帧要画什么窗口创建和表面初始化的细节被完全挡在后边。6.2 用自己掌握的帧函数替换掉示例里的主循环代码新项目里只要创建core_app对象注册一个frame回调每次循环里刷新画面即可。这种做法和现代游戏引擎的“回调注入”思路类似但它直接建立在 Win32 消息循环上没有引入任何中间层。class MyGame : public core_app { public: void frame() override { // 这里做本帧渲染画背景、贴图、输出文本都可以 renderer.bltBackBuffer(0, 0, background, NULL); renderer.flip(); } };把源码包里那些被 if 包裹得严严实实的 DirectDraw 调用逐步换成这个更直观的bltBackBuffer方法。新框架保持旧代码的核心调用顺序但对使用者隐藏了错误处理细节这样新手能更快进入真正的游戏逻辑编写。6.3 从那以后我每次接手老源码包都强制走一遍这套流程解压后先列目录树找出公共库确认头文件优先级再把链接库一次性补齐。先用一个最简示例验证整条链路随后才去碰复杂 demo。这种做法现在已经变成我的肌肉记忆哪怕是完全没有文档的源码包也能在半小时内从零进入编译状态。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?