尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
VC6 GDI横版过关游戏源码解析:整图加载与碰撞检测实现
简介基于Visual C与GDI实现的横版过关游戏完整源码玩法与画面参考经典超级玛丽面向毕业设计、项目实训等场景适合需要掌握C面向对象特性类、继承、多态与Windows GDI图形编程的在校学生。资源共32个文件以cpp源文件、h头文件为主辅以bmp位图素材、ico图标和txt地图配置压缩包仅161KB是一个可用VC6直接打开编译的完整工程工程内头文件、源程序、图片及地图文件分类存放便于按模块阅读。目前已有459人学习下载。代码覆盖角色移动跳跃、碰撞检测、地图分关读取、键盘交互等核心机制并展示了GDI绘制位图、定时器驱动动画、游戏地图管理等完整流程其中地图以文本形式保存每关单独加载并非一次性拼接这种设计对理解关卡扩展与数据驱动开发很有帮助。对准备课程设计、毕业答辩或初次尝试游戏开发的同学来说既能参考C游戏逻辑的编写方式也能学习GDI在图形交互中的实际应用是一份结构清晰、便于二次开发的示例。1. 结论先行这不是地图拼接游戏而是一关一张整图的横版过关源码先给结论这份用 Visual C 6.0 和 GDIGraphics Device Interface编写的横版过关游戏源代码核心思路反常识——它不在地图拼接算法里打转而是把一关做成一张完整大图碰撞数据写进 map1.txt运行瞬间一次加载。这种“整图 文本逻辑”的做法让美术出图快、逻辑调整不用动图非常适合做毕业设计和项目实训的 C 游戏参考。它适合三类人一是找 C、GDI 相关毕设或项目实训源码想看到完整运行链路的同学二是接手老工程、对着 dsw 却点不动的开发者三是想搞懂 GDI 怎么写动画、碰撞检测的学习者。功能不算多但地图加载、角色控制、碰撞修正、用户交互这些游戏基础设施全齐了。后面每一章沿着工程结构、核心代码、编译运行、避坑、改造逐步展开。2. 架构与工程拆解一份 VC6 工作区里藏着的完整图纸2.1 技术选型不是炫技是够用的图形接口毕设选型很多时候看机房装了啥。某高校的实训环境给的是 VC6DirectX SDK 并不是每台机器都有而 GDI 作为 Windows API 的一部分随系统自带打开工程就能画。所以这份源码用 GDI 做渲染不是退而求其次而是在 VC6 环境下最稳的方案。对 640x480 分辨率的 2D 平台跳跃来说GDI 的 BitBlt 性能完全够用瓶颈通常在碰撞检测写法和无效区更新策略不在绘制函数本身。明白这一点后面读代码就快了。GDI 在这个项目里的角色集中在三处位图装载LoadImage/CreateBitmap、位块传输BitBlt/StretchBlt和文本绘制DrawText。横版过关游戏需要频繁更新画面区域GDI 没有硬件加速也没有场景图但它能高效地做“把一张位图画到窗口”这件事配合双缓冲就能解决刷屏问题。剩余逻辑比如键盘状态、玩家位置、碰撞盒子全部由 C 代码直接管理不走引擎所有状态一目了然这也是毕设答辩时最容易被问清楚的架构。2.2 目录结构从 dsw 到 map1.txt 的文件地图解压这份资源看到的是标准 VC6 工作区布局文件作用mario01.dsw / mario01.dspVC6 工作区与工程文件双击 dsw 打开mario01.cpp / mario01.h主程序入口、窗口过程、游戏主循环gamemap.cpp / gamemap.h地图数据加载、碰撞体链表管理bitmaptool.cpp / bitmaptool.h位图装载与透明贴图封装texttool.cpp / texttool.h文本绘制与 trace 日志输出myclock.h计时器封装控制帧节奏mario01.rc / mario01.ICO资源脚本与程序图标map / map1.txt关卡数据目录第一关逻辑层pic / 多个 bmp背景整图、角色序列帧、道具图标trace.txt运行期调试记录文件值得留意的是 map 目录下只有 map1.txt说明源码默认只有一关可玩“1关加载1次地图.txt”这个备注在文件结构上直接体现。图片资源分得极细map.bmp 是整张背景mapsky.bmp 是天空层pre1.bmp 是前景装饰role.bmp 是角色的多帧序列图。资源命名的层次感就是代码里绘制顺序的提示懂了这个顺序后面换图片素材时就不会贴错层。2.3 程序主线从 WinMain 到 WM_PAINT 的帧循环这份工程是 MFC 外壳入口在 InitInstance真正的游戏循环不在 while而在 Windows 消息循环和 WM_TIMER 的配合里。窗口过程收到定时器消息就推进逻辑帧收到 WM_PAINT 就重绘画面两者通过 InvalidateRect 解耦。// mario01.cpp 简化结构窗口过程里靠 WM_TIMER 推动逻辑帧 case WM_TIMER: if (!g_game.IsPause()) { g_game.UpdatePlayer(m_deltaTime); // 读键盘 → 算加速度 → 更新坐标 g_game.CheckCollisions(); // 遍历 map1.txt 生成的碰撞盒 g_game.UpdateCamera(); // 摄像机跟随角色限制滚动范围 InvalidateRect(hWnd, NULL, FALSE); // 请求重绘但不立即刷新 } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc BeginPaint(hWnd, ps); g_game.DrawScene(hdc); // 内部分层 BitBlt 角色序列帧 EndPaint(hWnd, ps); break; }这段代码是整份工程的中枢。WM_TIMER 负责逻辑帧WM_PAINT 只做读取和绘制不承担计算。逻辑帧里改了玩家坐标渲染帧拿来画画面卡顿时角色不会瞬移。m_deltaTime 一般取 50ms 或 100ms对应 20FPS 或 10FPS 的刷新上限。老项目为了控制 CPU 占用常用 SetTimer(hWnd, 1, 50, NULL)这个数值直接影响手感数值越小越跟手但 CPU 消耗越大。毕设演示时建议 50ms答辩机器普遍配置不高太高的刷新率反而暴露双缓冲没做好的闪烁。3. 核心模块拆解地图加载、角色动画与碰撞修正的实现链路3.1 地图加载整图一次性读入碰撞数据交给文本先回答那个关键设计为什么说“不是地图拼接的是1关加载1次地图”。传统 Tile 地图的做法是把少量小图元按矩阵拼出整关地块数据写在大数组里。这份工程反过来美术直接把一整张长条 BMP 画完运行时代码把整图读入内存同时用 map1.txt 描述哪些区域是实心的、哪里放道具、哪里生成敌人。这样地图的逻辑层和美术层完全分离改个砖块位置只需编辑 txt不用动图。// gamemap.cpp 关卡数据加载一次打开 map%d.txt逐行读取 bool CGameMap::LoadFromFile(int level, const char* dir) { char path[128]; sprintf(path, %smap%d.txt, dir, level); // 拼出 map1.txt 路径 FILE* fp fopen(path, r); if (!fp) return false; // 文件不存在就加载失败 char line[128]; int type, x, y, w, h; while (fgets(line, sizeof(line), fp)) { if (line[0] ; || line[0] \n) continue; // 注释/空行跳过 if (sscanf(line, %d %d %d %d %d, type, x, y, w, h) ! 5) continue; Block* b new Block(type, x, y, w, h); m_blocks.AddTail(b); // CObList 链表方便动态增删 } fclose(fp); return true; }地图数据一次解析完之后每个关卡的碰撞体都存在 m_blocks 链表里。碰撞检测每帧遍历链表关卡长度几十个块时开销完全可忽略。这里不用固定数组是因为做第二关时块的数量不确定链表增删更灵活。每一行五个字段按顺序是类型、左上角 x、左上角 y、宽度 w、高度 h。类型有约定比如 1 代表实心砖块、2 代表金币、3 代表敌人出生点、4 代表终点旗杆。这个约定在工程内部形成契约后面加新类型只需要扩展 switch主循环不用改。这个方案直接收益是美术出图快逻辑数据调整不用改图代价是换一关就要换一整张大图内存占用偏高。但毕设项目里“一关一张图”足够跑出效果。注意 Block 类在 gamemap.h 里继承了 CObject这是放进 CObList 的前提如果自己动手改成 CListBlock* 也能跑但序列化能力会丢失。3.2 角色驱动键盘状态、加速度和序列帧动画角色逻辑对应 mario01.cpp 里的 Player 相关代码和 role.bmp 位图。role.bmp 通常是一行多帧的序列站立、奔跑、跳跃各占若干帧绘制时按玩家方向键切换帧索引形成跑步动画。这个工程的绘制封装在 bitmaptool.cpp 里先用掩码位图处理透明再 BitBlt 到目标 DC透明色一般用品红 RGB(255,0,255)素材里尽量避免这种颜色。// 玩家移动逻辑做了加速和摩擦比匀速更像游戏 void CGame::UpdatePlayer(double dt) { bool left (GetAsyncKeyState(VK_LEFT) 0x8000) ! 0; bool right (GetAsyncKeyState(VK_RIGHT) 0x8000) ! 0; bool jump (GetAsyncKeyState(VK_SPACE) 0x8000) ! 0; m_accX 0.f; if (left) m_accX - m_walkAccel; // 向左加速度 if (right) m_accX m_walkAccel; // 向右加速度 m_velX m_accX * (float)dt; // 速度积分 if (m_velX m_maxSpeed) m_velX m_maxSpeed; if (m_velX -m_maxSpeed) m_velX -m_maxSpeed; m_posX m_velX * (float)dt; // 位置积分 if (jump m_onGround) { // 只有在地面才能起跳 m_velY -m_jumpSpeed; // 负方向向上 m_onGround false; } m_velY m_gravity * (float)dt; // 重力每帧累加 m_posY m_velY * (float)dt; }这里把横版过关的手感拆成了四个参数walkAccel 决定加速快慢maxSpeed 封顶上限jumpSpeed 决定起跳力度gravity 决定下落手感。四个数值配合起来角色就不再是“匀速移动 瞬时跳跃”的演示程序而是有滞空感和惯性的游戏角色。dt 是帧时间用秒做单位VC6 里一般用 GetTickCount() 前后取差值得到。0.05 秒的帧间隔下跳一个 2 块高的台阶大概需要 250 到 350 毫秒滞空如果觉得跳得不够高优先调 jumpSpeed 或 gravity而不是改地图。序列帧的绘制顺序也有讲究先画背景层 map.bmp再画前景装饰 pre1.bmp最后画角色和道具。这个顺序在 DrawScene 里是固定的原因很简单——角色必须在装饰之上、背景之下否则会出现角色被前景遮住半边身子的视觉 bug。换素材时保持帧宽度一致否则跑步动画会跳。3.3 碰撞修正先移动后回退分方向把角色“压”出障碍碰撞是横版过关最容易翻车的地方。这份工程的处理方式是先更新坐标再逐个碰撞体判断相交发现进入砖块后把位置按最小位移推出去。之所以不用“碰撞前预测”是因为平台跳跃里角色可能从四个方向撞向砖块预测法要维护一堆边缘状态不如回退法简单直接。// 玩家与实心块碰撞比较重叠量往最小的一侧推出 void CGame::ResolveCollisions(CGameMap* map) { POSITION pos map-m_blocks.GetHeadPosition(); while (pos) { Block* b map-m_blocks.GetNext(pos); if (b-type ! BLOCK_SOLID) continue; // 只处理实心类型 CRect rcBlock(b-x, b-y, b-x b-w, b-y b-h); CRect rcPlayer((int)m_posX, (int)m_posY, (int)(m_posX m_width), (int)(m_posY m_height)); if (!rcPlayer.IntersectRect(rcPlayer, rcBlock)) continue; int overlapLeft (rcPlayer.right - rcBlock.left); int overlapRight (rcBlock.right - rcPlayer.left); int overlapTop (rcPlayer.bottom - rcBlock.top); int overlapBottom (rcBlock.bottom - rcPlayer.top); int minOverlap min(min(overlapLeft, overlapRight), min(overlapTop, overlapBottom)); if (minOverlap overlapTop m_velY 0) { m_posY (float)(b-y - m_height); // 落在砖块顶面 m_velY 0.f; m_onGround true; // 重置跳跃可用 } else if (minOverlap overlapBottom m_velY 0) { m_posY (float)b-y b-h; // 头顶撞墙 m_velY 0.f; } } }先移动后回退的问题在于穿透方向不唯一所以按重叠量最小的一侧决定推出去的方向。判断 m_velY 的正负可以区分“从上方落下”和“从下方顶起”这样角色站在砖块上时 onGround 会被置回 true起跳条件成立。这里有个隐藏细节水平方向的回退被省略了实际工程里还应该有一段把 m_posX 推出砖块左右的逻辑否则角色贴墙走时会卡进墙里 1 像素看起来像被墙“吸住”。这类实现的直角边碰撞对斜面地形不友好但毕设体量下“能踩、能顶、不穿墙”已经合格。4. 编译运行指南在老工具链下把游戏跑起来4.1 用 VC6 打开工作区dsw、dsp、ncb 的角色分工拿到这份源码最直接的方式是安装 VC6 后双击 mario01.dsw。VC6 的工程体系由 dsw工作区、dsp项目、ncb类浏览器缓存、opt窗口状态组成。dsw 是入口dsp 里记录了源文件列表和编译选项ncb 和 opt 删掉没关系重新打开会自动生成这是接手别人工程混进一堆缓存文件时不必担心的原因。编译前看一眼 dsp 里的设置工程配置建议选 Win32 Release 或 Win32 Debug字符集保持默认的多字节字符集。这份资源没有用到 UNICODE强行切成 Unicode 会导致 CString 和文件操作的编码全乱掉出现中文乱码或路径打不开。如果装了 VC6 绿色版或精简版缺失库时会提示找不到 MFC42D.dll直接去同目录检查是否有 MFC 头文件即可这个错误几乎都来自不完整安装路径。另外VC6 在较新系统上编译时常会误报 scanf 这类函数不安全这是工具链年代差异造成的警告不是工程本身的逻辑错误不用理它。4.2 工作目录与图片加载失败的排查GDI 游戏最常见的黑屏细节不是代码错误是工作目录不对。gamemap.cpp 里 sprintf 拼出来的是相对路径比如 map\map1.txt 和 pic\map.bmp这些路径都以当前工作目录为基准。从 VC6 里 F5 调试时没问题因为 IDE 会把工作目录设为工程目录但直接去资源管理器双击 exe 就会花屏或退出因为 exe 的当前目录是它所在目录而 bmp 在 pic 子目录下相对路径失效。cd /d E:\mario01 dir pic\*.bmp mario01.exe运行前先确认 exe 与 pic、map 目录的相对位置这三个目录必须同级。如果非要让 exe 独立运行可以把资源路径改成绝对路径或者用 GetModuleFileName 算出 exe 所在目录再拼路径后者更稳妥。图片格式也有要求VC6 的 CImage 不是标配工程里的 bmp 全部是 8 位或 24 位无压缩位图不要混入 PNG 或 JPG。换素材时用画图程序另存为 24 位 BMP 再替换。判断是不是路径问题最快的方法是在 WM_PAINT 里加一个临时 DrawText输出当前工作目录一目了然。4.3 逻辑调试从 trace.txt 和 Output 窗口看运行痕迹工程里带了一个 trace.txt这是老式 printf 式调试的产物。texttool.cpp 里封装了写日志的函数代码埋点会把玩家坐标、碰撞结果、地图加载状态追加进去。跑不起来的阶段盯着 Output 窗口不够用因为 GDI 游戏的日志在 WM_PAINT 里可能被重复刷屏落盘才看得全。排错顺序建议是能编译 → 先看 trace.txt 有没有 “load map fail” → 再看窗口闪不闪 → 最后才调手感参数。多数问题不是算法错了是某个位图路径没加载出来。提示写完日志记得 fflush 强制刷新缓冲区否则程序崩溃时最后几条记录会丢。这里还要提一个 Release 和 Debug 的差别。Debug 版跑得动但 Release 版跑不动八成是某个变量没初始化VC6 在 Debug 下会自动清零栈内存Release 不会。我拿到这种老工程第一件事就是用 Release 配置编译一遍如果只有 Debug 能跑就去查构造函数里所有成员变量是否都有初始值。5. 避坑记录GDI 游戏在编译和调试中的典型翻车现场5.1 现象双击 dsw 打不开VC6 直接闪退原因VC6 的 IDE 在较新系统上兼容性差常见于 Win7 以上版本窗口初始化时崩溃。解决不要硬顶先确认 VC6 组件完整然后右键 exe 设置兼容模式运行或者干脆装 XP 虚拟机在里面编译。这是最省时间的路老工程拿到手先别纠结 IDE 版本跑起来才是目的。5.2 现象运行后画面闪烁像老幻灯片原因WM_PAINT 里直接贴位图没有双缓冲GDI 每次擦背景加重绘刷新不同步。解决在 WM_PAINT 里创建内存 DC把整帧画面画到兼容位图上最后一口气 BitBlt 回窗口。另外把 WM_ERASEBKGND 直接 return TRUE禁止系统擦背景闪烁会进一步减轻。双缓冲的代价是一帧多拷贝一次整图但 640x480 分辨率下开销可以忽略。5.3 现象map1.txt 里的中文注释变成乱码部分路径读不到原因VC6 默认 ANSI 编码而文件被编辑器存成了 UTF-8 带 BOM 格式sscanf 解析行时遇到 BOM 头会读入一个不可见字符导致字段错位或跳过。解决地图文本统一用记事本另存为 ANSI 编码行尾保持 CRLF 不要用 LF。改完文件后重新检查第一行有没有乱码符号有就去掉。代码层面可以在主循环里对 BOM 做一次过滤但治本还是统一编码。5.4 现象角色跳上砖块后持续上下抖动甚至被卡住原因碰撞修正后 onGround 状态与 m_velY 没有同步下一帧重力又把角色压回砖内产生抖动。解决每次落地修正时把 m_velY 归零并设置 m_onGround true同时把垂直位置精确置到地块顶面坐标。有的写法会在落地后加一个极小的正偏移但这属于临时手法不如把重叠判定改成“允许 1 像素误差”更干净。这个坑排查时要特别注意速度值是否残留残留速度是抖动的首要嫌疑。5.5 现象Release 版运行正常但 Debug 版卡顿严重原因Debug 版的迭代器检查和内存校验会让 STL 和 MFC 容器操作慢好几倍m_blocks 链表每帧遍历在 Debug 下放大明显。解决毕设答辩时用 Release 版演示Debug 版留给开发阶段。这不是项目 bug是工具链特性但第一次遇到的人多半会误判成死循环。6. 二次开发思路从改关卡到换角色把手感调成自己的先把关卡加出来。复制 map1.txt 改名 map2.txt用记事本打开把所有实心砖的 x 坐标整体右移 100 像素给新关卡的布局腾出空间然后把出生点移到新位置替换背景图 map.bmp 为第二关的整图。代码侧不需要动GameMap::LoadFromFile 的 level 参数已经支持 map2.txt。copy map\map1.txt map\map2.txt notepad map\map2.txt逻辑说明关卡切换由 level 变量控制通关后执行 LoadFromFile(level 1)文件不存在就弹胜利框。再往深做一个存档点就不只是加关卡了需要记录角色当前坐标和已吃金币这属于后续扩展但工程里的链表结构已经留了数据挂载的位置。改完关卡后每次编辑地图文本都做一次“编码体检”——用记事本另存为 ANSI再确认没有 BOM 头这是第五章踩坑教育出来的习惯。换角色要动 role.bmp。保持帧总数和帧宽高不变直接替换整张图如果角色尺寸变了顺带改 m_width 和 m_height 以及碰撞盒宽高。换图后重点看透明色区域有没有残留品红边GDI 透明贴图对颜色敏感素材边缘最好预处理成透明色同色。调整手感的四个参数在 UpdatePlayer 代码里标得已经很清楚walkAccel、maxSpeed、jumpSpeed、gravity。想跳得高一点就加大 jumpSpeed 或减小 gravity想让移动更跟手就加大 walkAccel 并适度放开 maxSpeed。每次改完跑一遍第一关重点验证两步台和深坑处的碰撞。从那以后我每次拿到老工程都会先原版完整跑通一遍再动第一个参数。这样回头看每处改动都能对上号不会把“我改的 bug”和“原来就有的毛病”混在一起。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

快速排序原理与C语言工业级实现详解

快速排序原理与C语言工业级实现详解

1. 为什么快速排序值得你花20分钟真正搞懂“交换排序算法之快速排序-C语言版(带图详细)”——这个标题里藏着一个被无数初学者低估的真相:它不是一道必须背下来的考题,而是一把能帮你打开算法思维大门的钥匙。我带过不少刚接触数据…

📅 2026/10/18 4:37:13
Spring AI 实战:从 ChatClient 到多轮对话与工程化落地

Spring AI 实战:从 ChatClient 到多轮对话与工程化落地

第一次在 Spring 项目里接大模型,我其实是有点抗拒的。倒不是说抗拒 AI 本身,而是抗拒调一次接口要写的那一堆样板代码:不同模型服务商给的 SDK 风格不一样,参数命名不一样,返回结构也不一样。今天接这家,明…

📅 2026/10/18 4:32:12
从Q-learning到DQN:网格寻宝中的强化学习实战

从Q-learning到DQN:网格寻宝中的强化学习实战

如果你在小区里见过那种“找不到路就原地打转,被同一把椅子卡住三次”的扫地机器人,大概就能秒懂强化学习要解决的核心问题——不是“知道路怎么走”,而是“在没有地图、没有向导的情况下,靠自己试错走出一条路来”。我最近把“强…

📅 2026/10/18 4:32:12
MORE NEWS

更多资讯

📰

mlx_lm 三行代码加载 Qwen3.6-35B-A3B-OptiQ-4bit-REAP-19B:本地推理快速上手指南

【免费下载链接】Qwen3.6-35B-A3B-OptiQ-4bit-REAP-19B 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/Qwen3.6-35B-A3B-OptiQ-4bit-REAP-19B 点击查看 免费下载 Qwen3.6-35B-A3B-OptiQ-4bit-REAP-19B 是一款面向 Apple Silicon 的 MLX 量化 MoE 大模…

📰

ClickHouse .NET 驱动程序新特性:从 1.0 到 1.3 的演进之路

本文字数:12722;估计阅读时间:32分钟作者:Alex Soffronow Pagonidis编者按: 本文译自 ClickHouse 原博客(链接点文末-查看原文)。 原文围绕「相关技术主题」展开。 文章梳理了驱动在插入、读取和…

📰

TF-IDF+Logistic回归:情感分类Baseline的完整实战解析

很多刚入坑NLP的人都有这种感觉:学了一堆深度模型、BERT、注意力机制,可真到要做情感分类,反而不知道该从哪里下手。今年我陪一个项目组做了个小任务,给短评数据做情感二分类,第一版直接用 TF-IDF Logistic Regressio…

📰

YOLO实战:混凝土裂缝检测从数据集构建到模型部署全流程解析

1. 混凝土裂缝检测到底是什么问题1.1 传统检测方式的痛点在哪里先说点实际的。搞土木结构检测的朋友应该深有体会,桥梁、隧道、水坝外墙这些混凝土结构的定期巡检,最耗时的一环就是裂缝排查。传统做法是检测人员带着裂缝测宽仪、相机甚至望远镜&#xff…

📰

ABAP Cloud本地化日期输出:String Template与CL_ABAP_DATFM组合实践

做 ABAP Cloud 开发的人应该都有体会:日期输出这种东西,老代码里一句WRITE再加个格式选项就完事了,但在云环境里,很多老路子直接被堵死,越是基础功能越要重新设计。这篇文章想聊的就是一个很小但很磨人的点——怎么用 …

📰

Java自学避坑指南:五大常见误区与正确学习路线

网上关于Java学习路线的帖子多到刷不完,但我今天不想再给你列一张“必须掌握的知识清单”,而是想认真聊聊更值得关注的一类问题:Java学习误区。这些坑几乎每一个自学Java的人都会遇到,区别只是有人在里面待了大半年才爬出来&#…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬