资讯详情

MFC零基础入门:用C++实现键盘控制的迷宫小游戏

📅 2026/11/2 4:40:39 | 华诺云谱 👁 阅读
MFC零基础入门:用C++实现键盘控制的迷宫小游戏
简介面向MFC入门者与C游戏编程初学者的一份简单迷宫游戏项目源码以经典文档/视图架构搭建窗口界面并通过二维网格组织地图、通道与玩家位置适合用来理解Windows消息驱动、菜单与按钮控件交互以及CPen、CBrush等基础绘图环节。压缩包共45个文件、约2.13MB包含头文件与cpp实现、工程配置、资源脚本、位图与图标素材、编译产物及可执行程序另有说明文档和分步截图辅助梳理游戏思路工程可直接运行观察效果也便于对照代码定位迷宫生成、移动判定、路径搜索与状态记录等逻辑。已有379人学习适合作为课程设计参考、MFC练手案例或二次改造的起点。读者可从中获取完整工程结构、界面绘制示例、键盘鼠标交互处理方式以及DFS/BFS等基础算法在游戏中的落地思路对希望从控制台转向图形界面编程的读者较为友好。1. 先搞懂一件事MFC 不是新语言是给 C 配的一整套窗口工具箱很多人第一次看到 MFC 这三个字母脑子里蹦出来的第一个念头是「完了又要学一门新语言」。其实不用。你可以把 C 想成水泥和钢筋MFC 就是一套已经做好的门窗、水管和开关——它本身就是用 C 写成的类库专门帮你把「开一个窗口、画一条线、接一次按键」这些又碎又烦的活打包成一句话就能调用。这份资源里的迷宫游戏就是拿这套工具箱拼出来的小东西一个窗口一片格子一个红方块在里面走。它适合谁C 语法刚学完、想做个看得见摸得着的东西的人上过课被 MFC 作业吓到过、想重新捋一遍的人。它不教你怎么做商业游戏它教你怎么让一个窗口老老实实听你的话。2. 为什么值得动手一个三百行的迷宫游戏藏着多少入门基本功有人会问迷宫这么土的游戏做出来有什么意思。答案有点反直觉正因为它土、它小才适合当第一块砖。真正难的从来不是「迷宫」这两个字而是你要第一次同时面对三件事——一份数据、一套规则、一个窗口并且让它们三个对上号。窗体和数据对不上号的那种痛做过一遍就再也不会忘。2.1 三层结构数据、规则、界面把迷宫拆开看它其实只有三层。第一层是数据。一个二维数组0代表能走的路1代表墙再加上两个整数记住玩家现在在第几行第几列。就这么点东西整个游戏的世界观全在这一小块内存里。第二层是规则。玩家按上箭头的时候先算出「他想去的那一格」然后问三个问题这一格还在棋盘里吗这一格是墙吗都不是的话才真的把人挪过去。规则层不关心颜色也不关心窗口大小它只做判断。第三层是界面。把数组里的每一个格子换算成屏幕上的一块像素方块墙画深色、路画浅色、玩家画红色同时把键盘按下的动作翻译成「上、下、左、右」四个意图交给规则层。这三层分开的好处是后面改东西的时候不会互相牵连。想把墙换成砖块贴图只动界面层想加一个「踩到陷阱就重来」只动规则层想把 10×10 改成 20×20只动数据层。新手最常见的写法是把三层揉成一坨画图的函数里顺便改坐标改坐标的地方顺便判断墙最后谁也不敢动。我在最开始学的时候就是这么干的加一个功能能连锁崩三个地方。在 MFC 这套东西里三层大概落在这些位置| 层 | 负责什么 | 在这个工程里的落脚点 | | 数据 | 存迷宫和玩家坐标 | 视图类或对话框类的成员变量或者先写成全局变量 | | 规则 | 能不能走、有没有通关 | 一个普通函数比如TryMove(int dr, int dc)| | 界面 | 把数据画成像素、接收按键 |OnDraw/OnPaint和OnKeyDown|这张表不用背但建议你新建工程之后对着它看一遍心里有个「我这段代码属于哪一层」的意识比多写一百行都值。2.2 为什么偏偏是迷宫而不是贪吃蛇或俄罗斯方块贪吃蛇当然更酷但它有个门槛它需要定时器。蛇要自己往前爬你得处理「每隔 200 毫秒动一次」还要处理吃到食物变长、撞到自己身上怎么办。俄罗斯方块更狠旋转矩阵、消行、下一个方块预览随便拎一个出来都够写半天。问题不在于这些难不难而在于它们会把两种完全不同的错误混在一起。当你画不出东西的时候你分不清到底是「蛇的位置算错了」还是「MFC 的绘制用错了」。零基础阶段最怕的就是这个——错误不报错只是画面不对你连从哪查都不知道这才是真正的黑匣子。迷宫是回合制的你不按键画面就一动不动。没有定时器没有帧率没有并发。你按下方向键方块要么动了要么没动因果关系清清楚楚。可它同时又保留了完整游戏的四个基本元素——状态、输入、渲染、判定。把这四件事走通一次后面加什么都是在上面加而不是推倒重学。还有一个隐形的好处迷宫的结果能被肉眼验证。数组写得对不对看一眼窗口就知道玩家卡在墙里了一眼就能发现。这种「立刻能看到反馈」的特性在学 MFC 这种窗口编程的时候特别值钱因为 MFC 的报错信息往往含糊得让人想砸键盘。2.3 上手前花十分钟把游戏拆成三张纸在打开 IDE 之前先做一件几乎不花成本的事拿一张纸竖着分三栏。左栏写数据结构就写两行——「一个 10×10 的二维数组0 是路 1 是墙」「两个整数记住玩家在第几行第几列」。中间栏写规则用大白话写清楚「按下上箭头会发生什么」一步一步来先算出目标行是当前行减一再检查目标行是不是小于 0再检查那一格是不是墙都不是就把玩家坐标改过去最后让画面重画一遍。写完之后你会发现这就是一个五步的流程没有任何玄学。右栏写画面元素墙、路、玩家三种颜色格子边长先定 32 像素。这三栏写完你大概会得到一个让自己松口气的结论整个程序要写的东西可能还不到两百行而且每一行你都知道它为什么存在。恐惧感大部分时候不是来自难度而是来自「不知道有多大」。拆开来看到底了就敢动手了。这一步花十分钟能省掉后面两个小时的瞎改我个人觉得是这篇里性价比最高的建议。3. 第一步怎么迈装工具、建工程先让一个空窗口弹出来零基础最容易卡住的地方往往不是代码而是「打开软件之后不知道点哪里」。这一章把这条路铺到你能看见一个窗口为止中间每一步都写清楚包括那些教程里通常默认你会、其实你根本不会的操作。3.1 环境清单装开发工具时那两个必须打勾的选项第一件事确认手上是 Windows 电脑。MFC 是 Windows 平台上的东西换别的系统跑不起来这一点绕不过去别在这上面耗时间。第二件事下载 Visual Studio 安装器社区版就够用不需要任何付费版本。启动之后会看到一个「工作负载」的卡片列表要勾的是「使用 C 的桌面开发」。这一步大部分人都会做对因为它摆在最显眼的位置。第三件事才是真正的分水岭。勾完工作负载之后往右边看那一栏叫「安装详细信息」往下滚找到一个叫「MFC 和 ATL 支持」的选项或者名字类似「适用于最新 v14x 生成工具的 C MFC」的条目把它勾上。它默认是不勾的。这就是新手第一天最常翻的车装完了、建工程了、信心满满按下生成结果第一行红字告诉你「无法打开包括文件 afxwin.h」。这时候很多人的第一反应是「我代码写错了」然后开始胡乱改改到怀疑人生。其实跟代码一点关系都没有就是那个勾没打。提示已经装完了才发现少东西不用卸载重来。打开安装器点「修改」在单个组件里搜 MFC勾上更新就行几分钟的事。装完建议重启一次然后打开软件在新建项目的搜索框里输入 MFC看看有没有「MFC 应用」这个模板。有这条路就通了可以往下走。3.2 新建工程三种 MFC 程序新手先选哪个点「创建新项目」搜 MFC选「MFC 应用」填个名字比如 MazeGame点下一步。接下来会出现一个「应用程序类型」的选择页这里有三个选项值得说清楚因为选错了后面会一直别扭。「基于对话框」程序一启动就是一个窗口里面可以拖按钮、拉文本框所有的绘制都写在这个对话框类的OnPaint里。它的好处是最容易在十分钟内看到结果适合第一次动手。「单文档」自带菜单栏、工具栏中间一大片空白客户区专门留给你画东西绘制写在视图类的OnDraw里。大部分教学用的 MFC 游戏工程都是这个类型。「多文档」一个程序里能开好几个子窗口入门阶段完全用不上跳过。我的建议是先选「基于对话框」。理由很实在你现在要验证的只有一件事——我能不能让窗口出现、能不能在上面画东西。基于对话框这条路最短干扰最少。等你确认通了再去打开资源里的工程看它是哪种。如果发现工程里有一堆名字带 Doc 和 View 的类别慌那就是单文档你只要知道「绘制代码在 View 类的 OnDraw 里」就够了。这两种类型拿到的其实是同一个东西一个能画画的CDC指针方法名一模一样。工程建好之后左边的解决方案资源管理器里会刷出一堆文件看着唬人常用的就那么几个| 文件 / 类 | 一般长什么样 | 它负责什么 | | 应用类 | CMazeGameApp | 程序入口负责把主窗口创建出来 | | 视图类 | CMazeGameView | 画面画在这里OnDraw在这个类里 | | 对话框类 | CMazeGameDlg | 基于对话框的版本里主界面是它 | | 资源文件 | MazeGame.rc | 菜单、图标、对话框布局都存在这里 | | 预编译头 | pch.h老版本叫 stdafx.h | 几乎每个 .cpp 的第一行都要包含它 |入门阶段你真正会反复改的八成只有视图类或者对话框类这一个文件。剩下那些先当它们是背景板不用急着搞懂。很多人一上来就想把每个文件都看明白结果三天过去还在读自动生成的注释热情全耗光了。3.3 编译运行第一次看到空窗口就算通关步骤很短但每一步都有讲究。双击工程目录里的.sln文件打开。不要双击.vcxproj那个是单个工程文件直接打开可能缺依赖会冒出一堆莫名其妙的错。看工具栏上两个下拉框一个是配置Debug / Release一个是平台x86 / x64。选 Debug平台跟着工程原本的设置走别乱换。按 CtrlShiftB 生成解决方案或者点菜单里的「生成」。看下面输出窗口最后一行出现「生成: 成功 1 个失败 0 个」第一关过了。按 F5 运行窗口弹出来能拖动能关闭——这一步的验收标准就这么点。很多人会在这个阶段着急觉得「空窗口什么也没有算什么进度」。恰恰相反空窗口是最重要的一次确认它同时证明了工具装对了、MFC 组件装对了、工程文件没坏。这三件事任何一件出问题你后面写的代码全是白写而且报的错还会把你往错误的方向引。确认之后做个小实验验证你确实能控制画面。找到视图类的OnDraw或者对话框类的OnPaint在里面加一行void CMazeGameView::OnDraw(CDC* pDC) { // 只验证一件事我能不能在这个窗口上留下自己的痕迹 // _T() 让字符串同时兼容 Unicode 和多字节字符集入门阶段养成这个习惯不吃亏 pDC-TextOut(20, 20, _T(hello, maze)); }CDC*可以理解成「一支笔加一块画布」的合体MFC 把它包成了一个类你只需要对着它调方法。TextOut(x, y, 字符串)就是在坐标 (20, 20) 的位置写一行字。注意这个坐标的原点在客户区左上角x 往右增大y 往下增大——这和数学课的坐标系是反的新手第一次画图十有八九在这里绕进去画出来的东西跑到了意料之外的位置。编译、运行、看到那行字你就可以进下一章了。看不到先别动代码翻到第 5 章看第一个坑八成是环境问题。4. 第一个能跑的例子把迷宫画进窗口让方块听键盘的话这一章是整篇的核心从零写到一个能动的小方块。每一步给代码代码后面说清楚它在干什么、参数怎么改、为什么这么写。跟着敲一遍比读十遍都管用。4.1 迷宫在内存里长什么样二维数组加两条约定先做数据层。在视图类或对话框类的文件顶部加这么一段// 约定0 表示可以走的路1 表示墙。 // 第一行、最后一行、第一列、最后一列故意全填 1当作边界。 const int ROWS 10; const int COLS 10; int g_maze[ROWS][COLS] { {1,1,1,1,1,1,1,1,1,1}, {1,0,0,0,1,0,0,0,0,1}, {1,0,1,0,1,0,1,1,0,1}, {1,0,1,0,0,0,0,1,0,1}, {1,0,1,1,1,1,0,1,0,1}, {1,0,0,0,0,1,0,0,0,1}, {1,1,1,0,1,1,1,1,0,1}, {1,0,0,0,0,0,0,1,0,1}, {1,0,1,1,1,1,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1} }; int g_playerRow 1; // 玩家当前所在的行 int g_playerCol 1; // 玩家当前所在的列这几行有三个设计决定值得说一下。第一个是边界全填 1。这不是偷懒是省事。因为四周都是墙玩家永远走不出去你在判断的时候就不需要额外处理行 0这种极端情况代码能少写一半崩溃也能少一半。新手写的第一版通常在边上就崩原因就是没想到这一层。第二个是用全局变量。严肃的写法应该把它们收进类里当成员变量但入门阶段我强烈建议先用全局。理由是你现在要同时学 MFC 的消息机制、绘制机制和指针再叠一层类的封装很容易哪头都顾不上。先让它跑起来跑通了再重构这是正常的顺序。第三个是行列顺序。g_maze[row][col]第一个下标是行从上往下数第二个是列从左往右数。这个顺序一旦定下来就别改改一次要错三处。写完可以顺手加一句调试输出把数组打印一遍确认自己没打错——这一步看起来多余但血泪经验是手敲一个 10×10 的数组错一个数字你会花二十分钟去找「为什么这里走不过去」。4.2 用 CDC 把它们画成格子一个双层循环就够了数据有了接下来是界面层。把OnDraw或OnPaint改成这样void CMazeGameView::OnDraw(CDC* pDC) { const int CELL 32; // 每个格子在屏幕上占 32 x 32 像素 const int LEFT 20, TOP 20; // 整张迷宫的左上角起点 for (int r 0; r ROWS; r) { for (int c 0; c COLS; c) { // 核心换算第几行第几列 - 屏幕上的像素坐标 int x LEFT c * CELL; int y TOP r * CELL; COLORREF color (g_maze[r][c] 1) ? RGB(40, 40, 40) // 墙深灰 : RGB(245, 245, 245); // 路浅灰 pDC-FillSolidRect(x, y, CELL, CELL, color); } } // 玩家单独画放在最后保证它盖在格子上面而不是被盖住 int px LEFT g_playerCol * CELL; int py LEFT g_playerRow * CELL; pDC-FillSolidRect(px 4, py 4, CELL - 8, CELL - 8, RGB(220, 60, 60)); }逐块解释一下。const int CELL 32是格子边长单位像素。LEFT和TOP是整张图在窗口里的起始位置留点边距看着舒服。x LEFT c * CELL和y LEFT r * CELL这两行是整篇里唯一需要背下来的公式。想清楚它的意思第 0 列在最左边第 1 列往右挪一个格子宽第 c 列就往右挪 c 个格子宽。行是同理只不过往下走。把这个换算想通了后面所有「为什么画出来的位置不对」的问题都能自己查。FillSolidRect(x, y, w, h, color)四个参数依次是左上角横坐标、左上角纵坐标、宽度、高度最后一个是用RGB(r, g, b)拼出来的颜色。它会把这个矩形涂满没有边框正好当格子用。玩家那段用了px 4、CELL - 8意思是往里缩 4 像素让红方块比格子小一圈看起来像个人站在格子里而不是把整格涂红。这种小细节不影响功能但会让画面立刻「像那么回事」做东西的成就感有一大半来自这里。如果画出来是一片纯色、什么格子都看不见先检查两件事CELL是不是太小比如不小心写成 3以及数组里是不是全是同一个值。4.3 让键盘真的能动消息映射加 OnKeyDown画面有了接下来接输入。MFC 的输入不是「你去读键盘」而是「系统把按键事件送到你手上」中间靠一张叫消息映射的表来接头。手写的话是两步。第一步在头文件里声明在 cpp 的消息映射表里注册// MazeGameView.h 里类声明的 protected 段加上 afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); // MazeGameView.cpp 里找到 BEGIN_MESSAGE_MAP 那一段 BEGIN_MESSAGE_MAP(CMazeGameView, CView) ON_WM_KEYDOWN() // 把某个键被按下这条系统消息接到 OnKeyDown 上 END_MESSAGE_MAP()第二步把函数实现补上void CMazeGameView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { int nr g_playerRow; // nr / nc 是试探坐标先不写回真身 int nc g_playerCol; switch (nChar) { case VK_UP: nr--; break; // VK_ 开头的是系统定义好的虚拟键码 case VK_DOWN: nr; break; case VK_LEFT: nc--; break; case VK_RIGHT: nc; break; default: CView::OnKeyDown(nChar, nRepCnt, nFlags); // 不是方向键交回父类处理 return; } // 先全部判断再一次性写回。顺序反了就会出现走进墙里的 bug if (nr 0 nr ROWS nc 0 nc COLS g_maze[nr][nc] 0) { g_playerRow nr; g_playerCol nc; Invalidate(); // 告诉系统画面脏了稍后会自动触发 OnDraw 重画 } }几个关键点。nChar就是按下的那个键方向键对应的值叫VK_UP、VK_DOWN、VK_LEFT、VK_RIGHT都是系统预先定义好的常量不用记数字。nRepCnt是重复次数nFlags是一些附加信息入门阶段用不上但函数签名必须写全少一个参数编译不过。default分支里把消息交回父类是个好习惯。你不处理的键别人可能还要处理全吞掉的话以后加功能会莫名其妙失效。Invalidate()这一句是重点。它不会立刻重画只是给窗口打个「脏了」的标记等系统有空的时候再发重绘消息触发OnDraw。所以千万别在OnKeyDown里直接调OnDraw那是在跟系统抢方向盘画面会乱。最后if里的判断顺序是先边界后墙。写成g_maze[nr][nc] 0 nr 0 ...就等着越界崩溃吧因为 C 的是从左往右算的前面那个下标可能已经是 -1 或者 10 了。三段代码拼起来按 F5方向键一按红方块应该就能在格子里挪了。走到墙上不动走到边界不动。这就是一个能玩的最小迷宫。4.4 改三个数字验证你不是在抄能跑之后做一个十分钟的小练习把这三个地方改掉第一把CELL从 32 改成 48重新运行看看格子是不是变大了、窗口是不是装不下了。装不下就说明你还没理解「像素坐标」和「格子序号」是两套东西。第二把ROWS和COLS从 10 改成 20数组要跟着补全。补的时候记得最外圈还是全填 1不然又会走丢。第三把玩家那个RGB(220, 60, 60)换成别的颜色。改完再问自己一个问题为什么改颜色不需要动OnKeyDown里的任何一行答案在第 2 章的三层结构里。能自己答上来说明你不是在抄代码而是真的看懂了各层的分工。这个判断力比会写代码本身更重要因为它决定了你下一个功能是自己写出来的还是继续找别人抄。5. 常见问题排查新手最容易踩的五个坑提前把后悔药备好下面这五个是入门阶段出现频率最高的。每一条都按「看到什么现象 → 真正的原因 → 怎么处理」写遇到时对号入座就行不用从头猜。5.1 编译期就卡住两个让你怀疑自己写错代码的报错坑一现象是按下生成第一行红字写着「无法打开包括文件: afxwin.h: No such file or directory」。原因很单纯装工具的时候没勾 MFC 组件或者勾了但装的是另一套生成工具编译器根本不知道 MFC 在哪。解决方式是回到安装器点「修改」在单个组件里搜 MFC勾上更新重启软件。这一类错误跟你的代码一个字都没关系别去改代码改了只会越改越乱。坑二现象是报「预编译头文件来自编译器的早期版本」或者一堆pch.h相关的错。原因是中间目录里缓存了上一次编译的.pch文件而编译器版本或者设置变了缓存对不上。解决办法是走一遍「生成 → 清理解决方案」再「重新生成」。如果还不行去工程目录把Debug本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑