ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VC++6.0编写迷宫小游戏:从migong.rar到完整可玩项目实战

VC++6.0编写迷宫小游戏:从migong.rar到完整可玩项目实战 简介这份资源是面向C初学者与VC6.0入门者的迷宫小游戏完整工程源码围绕经典MFC框架讲解如何从零搭建一款可玩的迷宫游戏。压缩包共65个文件约153KB包含16个cpp源文件、17个h头文件、17个bmp位图素材以及ico图标、rc资源脚本、dsp与sln工程文件等覆盖迷宫生成、角色移动、界面绘制与工程配置各环节。项目采用二维数组表示迷宫结合深度优先搜索等算法生成唯一出入口界面左侧小地图提供全局视野右侧大地图展示详细路径并通过方向键控制兔子角色移动、检测碰撞防止穿墙。代码按迷宫类与游戏窗口类拆分借助MFC消息映射处理键盘事件结构清晰便于对照学习。目前已有213人学习下载适合想通过小项目掌握C基础、MFC应用与图形界面事件处理的读者参考实践。1. 从一份 migong.rar 说起VC6.0 编小游戏到底还能不能跑翻出一份名为migong.rar的压缩包解压后是几个.dsw、.dsp和一堆.cpp、.h文件双击.dsw用 VC6.0 打开按 F7 编译运气好直接生成一个走迷宫的小窗口程序——这就是很多人在找的「vc6.0编写游戏」资源最典型的形态。它不是什么引擎工程也不是 Unity 那种拖拽式开发而是用 Win32 API 加 GDI 画图在一个WinMain里手写消息循环把迷宫地图画成一个个矩形格子用键盘方向键控制一个小方块移动。适合谁适合想搞明白「游戏循环到底长什么样」的 C 初学者适合被 vc6.0 编小作业卡住、需要一份能编译能跑的参考代码的人也适合想回头看看二十年前 Windows 桌面小游戏怎么写的老手。它解决的不是「做个商业游戏」而是「用最原始的工具把游戏跑起来」这件事。2. 拆开 migong.rarVC6.0 工程结构与 GDI 绘图原理2.1 工程文件到底哪个能双击解压之后你会看到一堆后缀很多人第一次就卡在这里不知道点哪个。VC6.0 的工程体系里真正能双击打开的是.dswDeveloper Studio Workspace它管着整个工作区.dspDeveloper Studio Project是具体项目文件记录源文件列表和编译选项.opt是本地配置换台机器就失效可以删。常见做法是直接双击.dsw如果报「无法打开」就打开 VC6.0File → Open Workspace手动选.dsw。如果连.dsw都没有只有.cpp那就 File → New → Projects → Win32 Application建一个空工程再把.cpp和.h用 Add to Project → Files 加进去。文件后缀作用能不能删.dsw工作区双击入口不能.dsp项目配置源文件清单不能.opt本地个性化设置可以.cpp / .h源码与头文件不能.rc资源脚本图标菜单视情况Debug / Release编译输出目录可以清2.2 迷宫地图怎么用二维数组存这类小游戏的地图几乎都是硬编码的二维数组1表示墙0表示通路再留一个起点和终点。下面这段是常见写法我按能直接编译的方式整理过// 迷宫地图1 为墙0 为通路2 为终点 #define MAP_W 15 #define MAP_H 15 int g_map[MAP_H][MAP_W] { {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, {1,0,0,0,1,0,0,0,0,0,0,0,0,0,1}, {1,0,1,0,1,0,1,1,1,1,1,0,1,0,1}, {1,0,1,0,0,0,1,0,0,0,1,0,1,0,1}, {1,0,1,1,1,1,1,0,1,0,1,0,1,0,1}, {1,0,0,0,0,0,0,0,1,0,0,0,1,0,1}, {1,1,1,0,1,1,1,1,1,1,1,0,1,0,1}, {1,0,0,0,1,0,0,0,0,0,1,0,0,0,1}, {1,0,1,1,1,0,1,1,1,0,1,1,1,0,1}, {1,0,0,0,0,0,1,0,0,0,0,0,1,0,1}, {1,1,1,1,1,0,1,0,1,1,1,0,1,0,1}, {1,0,0,0,1,0,0,0,1,0,0,0,1,0,1}, {1,0,1,0,1,1,1,1,1,0,1,1,1,0,1}, {1,0,1,0,0,0,0,0,0,0,1,0,0,0,2}, {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} };MAP_W和MAP_H控制地图尺寸改大改小都要同步改数组内容否则越界。2是终点标记判断胜利时遍历或直接记录终点坐标都行。参数上唯一要注意的是数组维度顺序g_map[y][x]里第一个下标是行y第二个是列x画图时别写反写反了地图会转 90 度这是血泪经验。2.3 GDI 画格子与双缓冲VC6.0 编小游戏绕不开 GDI。最朴素的画法是在WM_PAINT里对每个格子调Rectangle但这样每按一次键就闪一次体验很差。常见做法是加双缓冲先在内存 DC 上画好整张图再一次性BitBlt到窗口 DC。// 双缓冲绘制避免闪烁 void DrawGame(HWND hwnd) { HDC hdc GetDC(hwnd); HDC memDC CreateCompatibleDC(hdc); HBITMAP memBmp CreateCompatibleBitmap(hdc, MAP_W * CELL, MAP_H * CELL); HBITMAP oldBmp (HBITMAP)SelectObject(memDC, memBmp); for (int y 0; y MAP_H; y) { for (int x 0; x MAP_W; x) { HBRUSH brush; if (g_map[y][x] 1) brush CreateSolidBrush(RGB(60, 60, 60)); // 墙 else if (g_map[y][x] 2) brush CreateSolidBrush(RGB(255, 200, 0)); // 终点 else brush CreateSolidBrush(RGB(240, 240, 240));// 通路 RECT r { x * CELL, y * CELL, (x 1) * CELL, (y 1) * CELL }; FillRect(memDC, r, brush); DeleteObject(brush); } } // 画玩家 HBRUSH pb CreateSolidBrush(RGB(0, 120, 255)); RECT pr { g_playerX * CELL, g_playerY * CELL, (g_playerX 1) * CELL, (g_playerY 1) * CELL }; FillRect(memDC, pr, pb); DeleteObject(pb); BitBlt(hdc, 0, 0, MAP_W * CELL, MAP_H * CELL, memDC, 0, 0, SRCCOPY); SelectObject(memDC, oldBmp); DeleteObject(memBmp); DeleteDC(memDC); ReleaseDC(hwnd, hdc); }CELL是每个格子的像素边长一般取 20 到 30。CreateSolidBrush每次循环都创建再DeleteObject这是 GDI 对象泄漏的高发点漏删几次程序就画不出来了。BitBlt的最后一个参数SRCCOPY表示直接拷贝别改成SRCINVERT否则颜色会反。双缓冲的核心价值是把 N 次屏幕刷新压成 1 次闪烁问题基本消失。2.4 键盘消息与游戏循环VC6.0 的 Win32 程序没有现代引擎那种while(true)游戏循环它靠WM_KEYDOWN消息驱动。在WndProc里处理方向键改玩家坐标判断目标格是不是墙不是墙就更新并InvalidateRect触发重绘。case WM_KEYDOWN: switch (wParam) { case VK_UP: TryMove(0, -1); break; case VK_DOWN: TryMove(0, 1); break; case VK_LEFT: TryMove(-1, 0); break; case VK_RIGHT: TryMove( 1, 0); break; } InvalidateRect(hwnd, NULL, FALSE); // FALSE 表示不擦背景配合双缓冲 break; void TryMove(int dx, int dy) { int nx g_playerX dx; int ny g_playerY dy; if (nx 0 || nx MAP_W || ny 0 || ny MAP_H) return; // 边界 if (g_map[ny][nx] 1) return; // 撞墙 g_playerX nx; g_playerY ny; if (g_map[ny][nx] 2) MessageBox(NULL, 到达终点, 恭喜, MB_OK); }InvalidateRect第三个参数传FALSE很关键传TRUE会先擦背景再重绘双缓冲就白做了。TryMove里先判边界再判墙顺序不能反反了会数组越界。这套消息驱动的结构就是「vc6.0 编小」最核心的骨架理解它比抄代码重要。3. 从零编译到跑起来VC6.0 环境配置与常见报错处理3.1 新建工程与导入源码如果你拿到的只有散装.cpp按这个流程走。打开 VC6.0File → New → Projects 选项卡选 Win32 Application填工程名Location 选一个纯英文路径点 OK弹窗选「An empty project」。然后 File → New → Files 选 C Source File命名后 Add to Project 勾上把源码粘进去。或者 Project → Add To Project → Files把已有的.cpp、.h加进来。工程建好后Project → Settings → Link 里确认没有多余的库依赖Win32 小游戏一般只用user32.lib、gdi32.lib这两个默认就链。3.2 编译报错逐条对照VC6.0 的报错信息很老但翻来覆去就那几类。下面这张表是我这些年攒下来的对照遇到直接查。报错信息真实原因处理方式cannot open file xxx.h头文件路径不对或没加进工程检查文件是否在工程目录Project → Settings → C/C → Preprocessor 加路径unresolved external symbol _WinMain16建成了 Console 工程重建为 Win32 Application或改 Link 里的入口for loop initial declaration used outside C99 mode在 .c 文件里写for(int i...)改后缀为 .cpp或把变量声明提到循环外fatal error C1083: Cannot open precompiled header没生成 stdafx 或设置错Project → Settings → C/C → Precompiled Headers 选 Not usingLNK2001: unresolved external symbol __imp__xxx缺库Project → Settings → Link → Object/library modules 补库名unresolved external symbol _WinMain16这个坑最经典本质是工程类型选错编译器在找控制台入口main但你的代码写的是WinMain。改工程类型比重写代码快。3.3 在虚拟机里跑老工具VC6.0 在 Win10/Win11 上能装但兼容性玄学常见问题是装完打不开、一点「打开」就闪退、编译时提示no compile tool。稳妥做法是丢进虚拟机装个 Windows XP 或 Windows 7 的 32 位系统VC6.0 在里面跑得最稳。虚拟机分配 1GB 内存、20GB 磁盘就够装完记得装 VMware Tools 或 VirtualBox 增强功能不然分辨率很难看。源码通过共享文件夹传进去编译产物再传出来。这条路虽然绕但省去了在新系统上跟兼容性死磕的时间。3.4 编译产物与运行验证编译成功后Debug 或 Release 目录下会生成.exe。直接双击能跑但注意它依赖的运行时库。VC6.0 默认动态链接msvcrt.dll这个库在 XP 之后系统里都有一般不用额外带。如果换到别的机器报缺 DLL就在 Project → Settings → C/C → Code Generation 里把 Use run-time library 改成 Multithreaded静态重新编译体积会大一点但独立。验证方法很简单跑起来按方向键方块能动、撞墙不动、到终点弹框三条都满足就算通了。4. 避坑与排查VC6.0 编小游戏最容易翻车的五个点4.1 现象程序一运行窗口就闪退原因WinMain里消息循环写错或者WndProc里WM_DESTROY没调PostQuitMessage消息循环拿不到退出消息直接崩。解决检查消息循环是不是标准写法GetMessage返回 0 才退出WM_DESTROY里必须有PostQuitMessage(0)。MSG msg; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return msg.wParam;4.2 现象画面疯狂闪烁按键一次闪一次原因WM_PAINT里直接对窗口 DC 画图且InvalidateRect传了TRUE。解决上双缓冲InvalidateRect第三参数传FALSE背景刷在WM_ERASEBKGND里返回 1 屏蔽掉。4.3 现象编译通过但一按键就崩原因TryMove里数组下标越界或者玩家坐标初始值就是墙。解决初始坐标设成通路格TryMove先判边界再判墙顺序不能反。加个ASSERT在 Debug 下能提前暴露。4.4 现象GDI 对象泄漏画着画着不显示了原因CreateSolidBrush、CreatePen这类创建的对象没DeleteObject。Windows 每个进程 GDI 对象有上限默认一万个超了就画不出来。解决谁创建谁删除循环里创建的尤其要盯紧能提到循环外复用的就别在循环里建。4.5 现象换台电脑就报缺 msvcrtd.dll原因Debug 版依赖调试运行时库那东西不是每台机器都有。解决发布用 Release 版或者把运行时库改成静态链接。Debug 版只在自己机器上调试用别拿去分发。5. 进阶技巧把 migong 改成能玩的完整小游戏5.1 加计时与步数统计光能走迷宫太素加个计时和步数就有游戏感了。用SetTimer每秒发一次WM_TIMER在消息里累加秒数步数在TryMove成功时自增画在窗口标题或状态栏上。// WM_CREATE 里启动定时器间隔 1000 毫秒 SetTimer(hwnd, 1, 1000, NULL); // WndProc 里处理 case WM_TIMER: g_seconds; UpdateStatus(hwnd); // 自己实现把秒数和步数拼成字符串 SetWindowText break; // TryMove 成功移动后 g_steps;SetTimer的第三个参数是毫秒1000 就是 1 秒。WM_TIMER不精确但对小游戏够用。UpdateStatus里用wsprintf拼字符串注意缓冲区开够char buf[64]起步。5.2 用 BFS 自动寻路验证地图想确认地图是不是有解或者想加个「提示」按钮BFS 最直接。从起点开始一层层扩展记录每个格子的前驱找到终点后回溯路径。// BFS 求最短路径返回步数-1 表示无解 int BfsSolve(int sx, int sy, int ex, int ey) { int dist[MAP_H][MAP_W]; memset(dist, -1, sizeof(dist)); int qx[MAP_W * MAP_H], qy[MAP_W * MAP_H], head 0, tail 0; qx[tail] sx; qy[tail] sy; tail; dist[sy][sx] 0; int dx[4] {0, 0, -1, 1}, dy[4] {-1, 1, 0, 0}; while (head tail) { int cx qx[head], cy qy[head]; head; if (cx ex cy ey) return dist[cy][cx]; for (int i 0; i 4; i) { int nx cx dx[i], ny cy dy[i]; if (nx 0 || nx MAP_W || ny 0 || ny MAP_H) continue; if (g_map[ny][nx] 1 || dist[ny][nx] ! -1) continue; dist[ny][nx] dist[cy][cx] 1; qx[tail] nx; qy[tail] ny; tail; } } return -1; }队列用数组模拟head和tail两个指针tail不会超过格子总数。dist初始化为 -1 兼作访问标记省一个visited数组。返回 -1 说明地图设计有问题终点被墙围死了这时候就得回去改地图数组。5.3 地图数据外置成文件硬编码数组改起来烦把地图存成文本文件程序启动时读进来改地图就不用重编译。格式很简单第一行宽高后面每行一串 0/1/2。// map.txt 格式第一行 15 15之后 15 行每行 15 个字符 bool LoadMap(const char* path) { FILE* fp fopen(path, r); if (!fp) return false; int w, h; fscanf(fp, %d %d, w, h); if (w ! MAP_W || h ! MAP_H) { fclose(fp); return false; } for (int y 0; y h; y) { char line[64]; fscanf(fp, %s, line); for (int x 0; x w; x) { g_map[y][x] line[x] - 0; if (g_map[y][x] 2) { g_exitX x; g_exitY y; } } } fclose(fp); return true; }fscanf读%s会跳过空白正好按行读字符串。line[x] - 0把字符转数字这是 C 里最省事的转换。读进来后顺手记录终点坐标省得每次遍历。文件路径用相对路径.exe和map.txt放同一目录不然找不到。5.4 一个我自己的习惯早些年我改这类小游戏总想着一次加一堆功能结果编译报错几十条根本不知道哪条引起的。后来养成习惯每加一个功能就编译一次跑一次确认没问题再加下一个。地图外置、BFS 寻路、计时统计这三样我都是分开验证的任何一步编译不过就立刻回退绝不带着错误往下写。从那以后我每次动 VC6.0 的老工程都强制走一遍「改一点、编一次、跑一次」的循环省下来的排查时间比多敲几行代码值多了。希望帮到你。本文还有配套的精品资源点击获取
返回列表