资讯动态

Windows游戏编程大师技巧第二版源码:DirectDraw编译、运行与移植实践

发布时间:2026/10/9 19:18:08 来源:尧图企业网站定制
简介《Windows游戏编程大师技巧第二版》源码压缩包收录了配套光盘中的完整工程代码与资源素材面向希望系统学习经典游戏编程技术的开发者尤其是打算将书中演示项目落地运行、研究早期游戏图形与音频处理思路的初中级读者。压缩包共1035个文件总大小38.49MB以bmp位图518个、cpp源文件159个、exe演示程序140个、wav音频99个为主另含pal调色板、h头文件、scn场景、光标图标与rc资源脚本等基本涵盖书内各示例的源代码、可执行程序及配套美术/音效资源。已有492人参与学习适合作为自学辅助包既可对照源码理解经典游戏编程技术的实现细节也可以直接运行演示程序验证效果在修改位图、音频、调色板等资源的过程中熟悉游戏素材的调用方式。整体保留光盘文件的原始组织结构按章节或示例命名便于按需检索和二次开发。1. 为什么还要下载一份 20 年前的老源码Windows游戏编程大师技巧(第二版) 源码.zip 能解决什么问题Windows游戏编程大师技巧(第二版) 源码.zip 这份压缩包装的就是那本经典书籍配套光盘里的全部源码与光盘文件。它不是电子书也不只是几个零散示例而是把整套 Win32 DirectDraw 时代的游戏编程素材按章节打包好解压后能当作原始光盘目录使用。对今天的从业者来说它解决的问题很实际当你想弄懂 2000 年前后 PC 游戏是怎么用 C/C 搭起窗口、贴图、播放音效的时候这份源码能让你直接看到当年的完整工程结构而不是在现代引擎的封装里猜来猜去。买过这本书的人大多已经找不回随书光盘二手书摊上那张 CD 要么刮花、要么早就压箱底。这份 zip 就是把「光盘损坏、光驱退役、老 SDK 链接失效」这些障碍一次清掉。适合两类人一类是新手想从消息循环开始理解 Windows 游戏程序的最小骨架另一类是熟手要维护或移植老代码手头缺一份能对上号的原始工程参考。2. 打开压缩包先看目录光盘文件结构与章节工程的对应关系2.1 顶层目录的典型排布从“盘符”变成“文件夹”后是否还能用把 zip 解压之后你看到的不是一堆零散 cpp而是基本还原了 CD 根目录的排布方式。第二版光盘的目录结构在不同批次印刷里略有差异但骨干是一致的章节示例放在一组命名规则的文件夹里公共素材和共用源码另起几个目录最后通常还有一个 README 或 DOCS 目录放说明文件。常见的样子大约是这样/BOOKDEMOS/ /CHAP01/ /CHAP02/ /CHAP03/ /... /SHARED/ /INCLUDE/ /LIB/ /GRAPHICS/ /SPRITES/ /BITMAPS/ /SOUNDS/ /README.TXT不要纠结目录名和书章节编号的一一对应关系。这里的章节文件夹往往命名为 CHAP01、DEMO02 这类内部代号而不是“第 1 章”。真正需要分清的是两类东西一类是各章节独立的示例工程一类是多个章节共用的静态库和公共头文件。后者往往决定了你的工程能不能一次链接通过所以解压后别急着双击某一个 .dsp先看看 SHARED 里面有多少家底。那“光盘文件变成文件夹”之后还能不能用答案是能。zip 只是把光盘上 Autorun 和驱动安装部分去掉了代码部分不受影响。但有一个细节要留意老光盘里文件的命名有些是全大写或长文件名当年打包工具如果不保留长文件名解压时就会出现截断或者大小写冲突。所以拿到这份 zip第一件事是用 7-Zip 这类工具完整解压到英文路径下比如 D:\BookSRC不要直接点开压缩包里的某一个 .cpp 就开看。提到这里先把文件类型的角色表列出来方便后面排查问题时对照扩展名在工程里的角色编译/运行时是否必须.dsp / .vcprojVisual C 工程文件主入口.cpp / .h源码文件必须.lib静态链接库链接阶段必须.bmp / .pcx图片资源运行阶段必须.wav / .mid音频资源运行阶段必须.rc资源脚本部分工程需要2.2 老书源码的耦合方式共享头文件、LIB 目录与 Win32 工程第二版源码里的工程组织方式和现在用 CMake 拉依赖的思路完全不同。它默认你正站在一张完整的 CD 目录结构里编译所以工程文件里多用相对路径引用共享库比如..\SHARED\INCLUDE、..\SHARED\LIB。这种做法的好处是只要保持整体目录结构把文件夹挪到任意盘符都能编译。坏处也很明显如果单独把某个章节的文件夹拖出来复制到别处编译时立刻报一堆“无法打开包含文件”的错误。这不是源码坏了而是你把它的依赖关系切断了。我一般会先保持整个目录不动编译某一个章节示例确认框架运转正常后再把工程复制出去拆开研究。还有一点值得提醒老 Visual C 工程编码默认是 GB2312 或 ASCII路径里如果出现中文字符链接器偶尔会给出莫名其妙的内部错误。把它放在纯英文路径下能省掉一大批玄学问题。共享库目录里通常还会带一份精简版 DirectX 开发库。第二版成书的年代网络下载 SDK 不像今天这么方便所以光盘里直接塞了头文件和导入库防止读者买书之后找不到编译环境。你在解压后的目录里看到 DXInclude、DXLib 这类文件夹千万别当作冗余删掉。它们对应的是 DirectDraw 7 和 DirectSound 这一代接口后面编译失败时第一反应就应该去检查头文件路径是不是指到了这里。从调用关系上看每章示例的程序骨架基本是WinMain → 初始化 DirectDraw → 加载位图素材 → 进入主循环 → 每帧处理输入和渲染 → 退出时释放 COM 接口。这个骨架贯穿整本书也是这份源码里最有复用价值的部分。理解了这个结构再看后面每一章的示例无非是在主循环的不同阶段增加逻辑。3. 编译环境还原与第一个 Demo 跑通工具链选型与配置步骤3.1 工具链选择VC6 虚拟机、现代 VS 与 MinGW 的取舍这一章直接说我在自己机器上试出来的组合。Windows 10 和 Windows 11 上编译老源码是完全可行的但工具链选不对后面全是眼泪。先给三条路第一条路Visual C 6.0 配老 DirectX 库。这是最接近原始出处的方案。第二版源码里大部分示例就是在 VC6 工程形态下写的VC6 可以直接打开 .dsp 文件。问题是 VC6 在 Windows 10 上偶尔会出现安装程序卡死、编译器进程假死的现象而且 VC6 只产出 32 位程序。所以我更建议在虚拟机里装一个简版 Windows 7再装 VC6编译通过后把 exe 拿到宿主机上运行排查。这条路线最稳折腾最少。第二条路现代 Visual Studio2019 或 2022加 DirectX 老库。头文件路径配好之后大部分源码能编译过但会冒出一堆 C4996 安全警告个别和 CRT 相关的代码还要加 _CRT_SECURE_NO_WARNINGS。这条路的优势是不用虚拟机劣势是工程文件要转换老 .dsp 需要导入或重写牵涉的细节多。第三条路MinGW-w64 或 Clang 直接命令行编译。这条路适合你只想临时验证某几个源文件的情况。把 ddraw.h 的路径指对再把 ddraw.lib、dxguid.lib 换成对应的导入库Win32 部分通常能过。但它对 Windows SDK 版本的依赖很敏感我不建议用它作为主方案。我的排序是先虚拟机加 VC6 跑通再用现代 VS 做移植。这样能快速区分“源码本身的问题”和“新旧工具链不兼容的问题”比直接跳到改造阶段省时间。3.2 配置 include/lib 路径并编译一个章节示例跑通第一个示例的步骤并不复杂但每一步都有讲究。先把流程放在前面完整解压 zip 到纯英文路径比如 D:\BookSRC保持目录层级不动。确认源码包自带的 DXInclude 和 DXLib 目录存在如果缺失准备一份 DirectX 老版本 SDK 的头文件和库版本对应 7.0 时代接口即可。打开命令行工具设置环境变量把公共目录加进 include 和 lib 搜索路径。调 cl 编译单个示例链接生成 exe。运行 exe按输出信息判断是否正常。命令行环境变量的配置长这样set INCLUDED:\BookSRC\SHARED\INCLUDE;D:\BookSRC\DXInclude;%INCLUDE% set LIBD:\BookSRC\SHARED\LIB;D:\BookSRC\DXLib;%LIB% set PATHD:\BookSRC\DXBin;%PATH%这里第一行是把公共头文件目录和 DirectDraw 头文件目录加进去顺序放在系统默认目录前面避免优先加载到新 SDK 的 ddraw.h。第二行是把 lib 目录指给链接器第三行是某些老工具运行时需要的辅助程序目录。如果你用的是现代 VS 命令行记得在这个环境变量基础上追加而不要覆盖原有值。编译一个简单章节示例可以用最原始的 cl 命令cl.exe /nologo /GX /O1 /ID:\BookSRC\SHARED\INCLUDE demo.cpp ^ /link /LIBPATH:D:\BookSRC\SHARED\LIB ^ user32.lib gdi32.lib winmm.lib ddraw.lib dxguid.lib ^ /OUT:demo01.exe逻辑说明/I指定头文件搜索目录/link之后是链接器参数/LIBPATH指定库搜索目录最后列出需要用到的导入库。这里user32.lib和gdi32.lib是 Win32 窗口与绘制的入口winmm.lib提供 timeGetTime 这类多媒体定时器函数ddraw.lib和dxguid.lib是 DirectDraw 7 的 COM 接口入口。示例源文件如果分散在多个 cpp 里就在 cl 后面把所有 cpp 都写上链接阶段再统一处理。参数说明/GX是 VC6 时代的 C 异常处理开关新版编译器可以换成/EHsc/O1是“最小代码尺寸”的优化级别第一次跑我建议用/O1而不是/O2原因是老代码里存在一些依赖副作用的小写法高优化级别下可能出现诡异行为先用低优化确认运行正常再慢慢补优化不迟。这一步最常见的报错是“cl 不是内部或外部命令”原因是没在 Visual Studio 的开发者命令行环境下执行或者 VC6 的 bin 目录没进 PATH。排查方式很简单先运行where cl确认编译器路径再回过来看环境变量。路径问题解决了编译基本就顺畅了。3.3 验证运行结果窗口、帧率、调色板三个检查点编译成功不等于程序正常。老 DirectDraw 程序在现在的显卡驱动上经常出现“编译过了但一跑就黑屏”的尴尬所以我习惯用三个检查点来验收窗口、帧率、调色板。第一个检查点是窗口。程序运行后要么出现一个带标题的正常窗口要么直接切到全屏并显示渲染画面。如果窗口一闪而过先看是不是 WM_QUIT 逻辑被触发或是初始化失败后主动 PostQuitMessage 退出。常见做法是在源码的窗口创建处临时加一行SetWindowText(hwnd, CheckPoint 1)如果在任务栏看到这个标题说明窗口创建已经通过。第二个检查点是帧率。很多第二版示例会在标题栏或屏幕角落输出 FPS这个数据能直接反映主循环是否在滚动。如果 FPS 显示为 0 或者画面死住问题大多出在消息循环的阻塞上比如错误使用了 GetMessage 而程序又没有新的窗口消息到来导致逻辑永远不执行。第三个检查点是调色板。老示例有不少是 8 位色模式下写的运行在 32 位色桌面环境时表面格式和调色板可能对不上表现就是画面颜色发绿或者整体偏色。这种现象不是源码缺文件而是现代显示环境不再默认支持 8 位色模式需要手动把源码里的像素格式改成 16 位或 32 位再编译。这三个检查点跑通过一遍这份源码在你的机器上就算基本能用了。后续再深入研究具体示例时就有了稳定的基准环境。4. 核心源码模块解读从初始化到游戏主循环的骨架4.1 DirectDraw 初始化与模式设置省略后的常见错误第二版源码里的图形初始化几乎都是从 DirectDraw 7 接口开始。书里的代码今天看依然简洁核心流程就四步创建接口、设置协作等级、设置显示模式、创建表面。把常见代码简化出来大致是这样#include ddraw.h LPDIRECTDRAW7 lpDD NULL; LPDIRECTDRAWSURFACE7 lpPrimary NULL; LPDIRECTDRAWSURFACE7 lpBack NULL; bool InitDirectDraw(HWND hwnd) { HRESULT hr DirectDrawCreateEx(NULL, (void**)lpDD, IID_IDirectDraw7, NULL); if (FAILED(hr)) return false; hr lpDD-SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); if (FAILED(hr)) return false; hr lpDD-SetDisplayMode(640, 480, 16, 0, 0); if (FAILED(hr)) return false; // 接下来的工程还会在这里创建主表面和后缓冲 // 然后通过翻转或 BitBlt 把内容送显。 return true; }逻辑说明DirectDrawCreateEx负责创建 DirectDraw 7 的 COM 接口对象第一个参数传 NULL 表示使用当前活动的显示驱动SetCooperativeLevel是告诉系统你要全屏独占模式这一步必须在SetDisplayMode之前调用否则返回无效模式SetDisplayMode的四个参数依次是宽度、高度、色深和刷新率色深写 0 表示使用桌面当前色深老示例一般写 8 或 16。这里最常见的错误是省略错误判断。老源码为了演示方便很多地方直接忽略 HRESULT一旦显卡驱动不支持指定模式程序就在毫无提示的情况下继续执行随后表面创建失败最后窗口黑屏退出。我在读这类源码时会先把FAILED判断补全再逐步往下走。第 3 个参数如果写 8在 32 位桌面环境下调色板接口基本不可用这也解释了为什么很多老示例在现在的机器上偏色。如果想减少兼容性问题另一种常见做法是把DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN换成DDSCL_NORMAL让程序跑在窗口模式下。这样SetDisplayMode很可能不再需要改由窗口客户区决定显示区域。改动很小但能避开大量现代显卡驱动下的全屏切换问题。4.2 主循环与帧率控制ticks 计数、消息泵与眨眼问题书里讲到的游戏主循环核心不是 GetMessage而是 PeekMessage。两者区别非常关键GetMessage 在没消息时会睡住游戏逻辑就停了PeekMessage 则不管有没有消息都会立刻返回让主循环有机会继续推进渲染。第二版源码里大量使用 PeekMessage 模式简化后的骨架如下for (;;) { if (PeekMessage(msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message WM_QUIT) break; TranslateMessage(msg); DispatchMessage(msg); } else { DWORD now timeGetTime(); if (now - lastTick 33) { UpdateGame(); RenderFrame(); lastTick now; } } }逻辑说明PeekMessage的第五个参数PM_REMOVE表示取走消息取到消息就分发给窗口过程取不到就跳过消息处理直接进入游戏更新分支。timeGetTime返回自系统启动以来的毫秒数用差值判断是否到了该更新一帧的时间这样比空循环忙等要节省 CPU。参数说明33 毫秒大约是 30 FPS想跑 60 FPS 就改成 16想解放性能测试就改成 1。但这里有个隐藏问题timeGetTime的默认精度受 Windows 多媒体定时器分辨率影响Windows 10 默认可能是 15.6 毫秒的粒度。你写 33实际可能跳到 31 或 47帧率忽高忽低。书里源码多数没处理这个细节我一般在WinMain开头加timeBeginPeriod(1)退出前加timeEndPeriod(1)把定时器精度临时提到 1 毫秒。至于“眨眼”问题指的是画面交替闪烁。原因一是调色板切换频繁原因二是翻转表面时机不稳定。老游戏在全屏独占模式下常用回写扫描线等待垂直同步while (lpDD-GetScanLine() 480) { }这段代码的意思是等显示器扫描过第 480 行之后再翻转后备缓冲从而减少撕裂。但它依赖全屏独占模式窗口模式下GetScanLine返回值不可靠。这也是很多老示例一改成窗口模式就疯狂闪烁的原因之一。4.3 输入与音频的简易封装DirectInput 和 waveOut 的读法第二版源码里输入部分一部分示例直接用 Win32 键盘消息另一部分则进入 DirectInput。DirectInput 读键盘的好处是可以同时查询整个键盘状态不存在 Windows 消息在某些窗口失焦时断掉的问题。书里的写法简化后很清晰char keys[256] {0}; if (g_pKeyboard) { HRESULT hr g_pKeyboard-GetDeviceState(sizeof(keys), keys); if (FAILED(hr)) { g_pKeyboard-Acquire(); hr g_pKeyboard-GetDeviceState(sizeof(keys), keys); } if (SUCCEEDED(hr) (keys[DIK_SPACE] 0x80)) { FireBullet(); } }逻辑说明GetDeviceState把键盘当前状态一次性拷入 256 字节的缓冲区每个字节代表一个按键字节最高位为 1 表示按下。Acquire在设备失去焦点时重新获取输入设备但重新获取后必须再调用一次GetDeviceState否则拿到的还是旧状态。参数说明sizeof(keys)直接传缓冲区大小防止驱动写入越界DIK_SPACE是空格键的扫描码常量换成 DIK_LEFT、DIK_UP 就能读取方向键。老代码里这种按键判断遍布主循环和现在的“事件驱动”思路完全不同它是每一帧主动问设备“你按下什么了”这也直接决定了老游戏在一帧逻辑耗时过长时容易出现输入延迟。音频封装在第二版源码里相对简单多见 DirectSound 的 CreateSoundBuffer、Lock、Write、Unlock、Play 这一套流程。我特别提醒一个坑示例代码为了节省篇幅经常在 Play 之后马上 Release 缓冲区这在某些声卡驱动上可能引发重复释放崩溃。解决办法是把 Release 和置空放到渲染循环之外直接模仿源码里常见的 SAFE_RELEASE 宏先判断指针非空释放后立刻把指针置为 NULL。5. 避坑排查老源码在现代系统上的已知问题与解决顺序5.1 现象双击 exe 后黑屏或分辨率闪一下就退回桌面这是老 DirectDraw 全屏程序在现代 Windows 上最常见的翻车现场。现象分两种一种是程序启动后屏幕直接黑掉但进程还在后台另一种是分辨率切换一下画面还没出现就退回桌面。原因有两层第一全屏独占模式DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN和现代桌面合成器DWM不兼容切换独占模式时被强制退回第二源码里指定的显示模式不是当前显卡驱动支持的排列组合。解决顺序是先检查 SetDisplayMode 的实际返回值用工具弹出对话框显示 HRESULT 错误码。最常见的错误对应DDERR_INVALIDMODE也就是显卡不支持当前分辨率。这时把宽高改成 640x480、色深改成 16 通常能过如果改完还是黑屏就把协作等级改成DDSCL_NORMAL放弃全屏直接让渲染输出到窗口客户区。这两步解决掉大约九成黑屏问题。5.2 现象提示 Cannot create DirectDraw 或 DirectDraw 初始化失败现象是程序启动后弹出一个错误框或者命令行窗口打出一行DirectDraw Init Failed。原因可能是显卡驱动还在用 Windows 7 时代的老调用也可能被系统 DDRAW 兼容层拦截。需要注意现代 Windows 自带的 ddraw.dll 已经是经过兼容重写的版本不再等于 XP 时代的原版很多老游戏用新接口包装模拟老接口导致DirectDrawCreateEx返回的接口虽然存在但部分能力又是空壳。解决顺序是先确认显卡驱动没有禁用硬件加速然后检查源码链接的是不是老版 ddraw.lib如果链接到了新版 SDK 的导入库接口 GUID 可能对不上最后再考虑用兼容层工具把老 DirectDraw 调用重定向到 GDI 或 OpenGL 后端。我一般不会一上来就上兼容层因为兼容层改的是外部拦截逻辑没办法解决源码里本身对接口的错误用法。5.3 现象编译报错无法打开包含文件 ddraw.h 或 d3d.h这是配置环境时最直接的报错原因只有一个预处理器找不到 DirectDraw 头文件目录。你可能装了最新 Windows SDK但最新 SDK 里 ddraw.h 的位置和命名可能改过也可能被完全移到了兼容目录。源码里写的是#include ddraw.h搜索顺序依次是当前工程目录、/I 指定目录、环境变量 INCLUDE全找不到就报错。解决顺序先确认源码包自带的 DXInclude 目录是否存在存在就把这个目录放到 /I 的第一位不存在就下载一个对应 DirectX 7 时代的头文件包放进去之后重新编译。不要试图用最新 DirectX 12 SDK 的头文件去兼容名称虽然一样内部结构差异会导致后面一堆编译错误。这条解决掉之后后续报错会少很多。5.4 现象链接错误 LNK2005 或 offset 相关符号重复定义链接阶段报 LNK2005最典型的原因是 CRT 库版本冲突。VC6 生成的导入库和新版编译器默认使用的 C 运行库都导出了某些全局符号链接器在两个库之间撞了车。老源码工程里有些会手工指定/NODEFAULTLIB或者直接链接libcmt.lib、msvcrt.lib一旦换编译器这些固定库名就会引发重复定义。解决顺序优先删掉工程里手写的所有 CRT 库名让编译器自己决定如果还冲突再检查是不是同一份代码里同时包含了两个版本的 ddraw 头文件。另一种可能是你自己把DXGuid.lib和dxguid.lib同名大小写不同的两个文件都塞进去了链接器无法区分也会报重复外部符号。清理掉只留一个即可。5.5 现象源码注释显示乱码或编译警告字符常量冲突现象很直观打开 .cpp 文件中文注释全变成乱码或者编译时警告character constant too long。原因是源码文件用 GB2312 保存而现代 Windows 文本编辑器默认按 UTF-8 读取。这不影响编译器解析字符串字面量但会影响你阅读代码的可读性。解决顺序用支持编码切换的编辑器把文件编码切到 GB2312 或 GBK 再保存一次如果代码里有代码页依赖可以在文件头加一行#pragma code_page(65001)但这会改变整个文件的执行字符集不建议新手操作。我一般直接使用编辑器里的“以编码重新打开”功能选择简体中文GB2312即可不需要改文件本身。6. 向前移植把第二版示例改造为现代 Windows 项目的最小步骤这套源码的终点其实不是“让它跑起来”而是把它改造成今天还能继续迭代的形态。一个最小可行的移植路径是先把 DirectDraw 的全部渲染调用换成一个与设备无关的位图缓冲区再通过 BitBlt 一次送到窗口。第一步把工程 WinMain 改成标准的 Win32 窗口程序注册窗口类时不需要再追求全屏窗口样式直接写成 WS_OVERLAPPEDWINDOW。第二步准备一块内存位图当作后备缓冲用 CreateDIBSection 获取像素指针这样老代码里 Lock/Unlock 表面内存的写法可以保留大半。第三步把渲染逻辑全部改写为对像素指针的读写不再是 DirectDraw 表面。第四步在 WM_PAINT 或主循环中用 BitBlt 把内存位图推到窗口客户区。关键代码骨架是RECT rc {0, 0, 640, 480}; HDC hdc GetDC(hwnd); HDC memDC CreateCompatibleDC(hdc); HBITMAP bmp CreateDIBSection(hdc, bmi, DIB_RGB_COLORS, (void**)pixelBuffer, NULL, 0); SelectObject(memDC, bmp); // 老源码里对表面像素的读写全部换成 pixelBuffer 指针。 BitBlt(hdc, 0, 0, 640, 480, memDC, 0, 0, SRCCOPY);这套改造做完DirectDraw 依赖就只剩下初始化那一两处之后可以逐步用现代 API 替换也可以干脆保留这套 GDI 底层做原型验证。它比直接跳上 Direct3D 要平滑得多因为书里的大部分游戏逻辑根本不在乎像素是怎么上去的。从那以后我每次打开这类老光盘源码包都会强制走一遍固定流程解压到英文路径确认共享库目录补全 include 和 lib先编译一个最小示例再谈改造。这套流程救过我不少次希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑