ARTICLE DETAIL

资讯详情

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

C#练手项目实战:六个WinForms小游戏带你掌握游戏循环与GDI+编程

C#练手项目实战:六个WinForms小游戏带你掌握游戏循环与GDI+编程 简介这份C#小游戏项目合集包含飞机大战、俄罗斯方块、贪吃蛇、拼图游戏、连连看和五子棋七款经典游戏面向刚入门C#的编程学习者通过完整可运行的源码与逐行注释帮助理解面向对象设计、窗体应用、事件机制及GDI绘图等核心知识点。资源包共1286个文件压缩后74.9MB其中包含92个cs源代码文件、388个xml配置、146个dll依赖库以及89张png图片素材还带有wav和mp3音效文件、11个可直接运行的exe程序及配套的pdb调试文件便于读者对照学习与断点调试。目前已有1280人学习下载。通过实际运行和改造这些游戏读者可以掌握游戏循环搭建、碰撞检测、键盘鼠标交互、消除判定算法、棋盘逻辑判断等具体实现技巧并能学会如何整合图片、声音等资源编写完整的游戏界面对巩固C#语法、熟悉Visual Studio项目结构和提升编程兴趣都很有帮助。1. 从语法到游戏六个能跑起来的 C# 练手项目很多初学者学完 C# 语法后卡在同一个地方面向对象、委托、事件都看了但一打开 Visual Studio 不知道写什么。这六个小游戏项目正好补上这段距离——飞机大战、俄罗斯方块、贪吃蛇、拼图、连连看、五子棋每个都用 WinForms GDI 实现代码注释密度比一般教程代码高不少适合拿来做「读代码 → 改代码 → 重写代码」的练习。项目里包含完整解决方案文件用 Visual Studio 2022 直接打开就能跑。无论你是刚学完 C# 基础、准备找上位机或桌面开发相关工作还是想理解游戏循环、碰撞检测、二维数组这些概念在真实代码里长什么样这几百个文件都值得花两个晚上拆一遍。2. 飞机大战与 GDI游戏循环、双缓冲和键盘状态2.1 游戏循环是怎么实现的飞机大战是整个项目里最适合先读的一个因为它最接近真实游戏的结构一个循环、一堆对象、每帧更新、每帧绘制。窗体上放了一个System.Windows.Forms.TimerInterval 设在 16 或 20 毫秒对应大约 50 到 60 FPS。每次 Tick 触发GameLoop()里面做三件事处理输入、更新所有对象位置、刷新画面。这个结构和你以后写 C# 上位机循环采集数据时遇到的问题是同一类——界面刷新卡顿往往就是循环里做了耗时操作后面会具体说。玩家飞机的移动没有用键盘事件而是在KeyDown和KeyUp里维护一个布尔状态字典。这样做的原因是事件本身是瞬时的如果你在KeyDown里直接改坐标按键按住时不会连续移动只有按下瞬间动一下。项目里的做法是// 键盘状态字典KeyDown 时置 trueKeyUp 时置 false private DictionaryKeys, bool keyStates new DictionaryKeys, bool(); private void Form1_KeyDown(object sender, KeyEventArgs e) { keyStates[e.KeyCode] true; } private void Form1_KeyUp(object sender, KeyEventArgs e) { keyStates[e.KeyCode] false; } // 在游戏循环中统一读取而不是在事件里直接移动 private void UpdatePlayerPosition() { int speed 8; if (keyStates.ContainsKey(Keys.Left) keyStates[Keys.Left]) player.X - speed; if (keyStates.ContainsKey(Keys.Right) keyStates[Keys.Right]) player.X speed; }这里的关键在于把「输入采集」和「状态更新」拆开。KeyDown只负责记录按键是否被按住移动逻辑全部放在游戏循环里执行。speed 变量代表每帧移动的像素数如果觉得飞机移动太快或太慢优先调整这个值而不是改 Timer 的 Interval——因为 Interval 改动会影响所有对象的运动速度包括敌人、子弹和背景滚动。2.2 双缓冲和碰撞检测如果你直接在这个项目里把 Timer 的 Interval 改成 1 毫秒会发现画面严重闪烁。这不是 GDI 绘图性能不够而是没开双缓冲。窗体设置了DoubleBuffered true它的原理是在内存中先创建一张与屏幕等大的画布所有绘制操作都在这张画布上完成绘制结束后一次性拷贝到屏幕。这个技巧在你以后写 C# 上位机自定义控件、写工业组态界面时同样适用——任何频繁Invalidate()的自绘控件闪烁第一反应都是开双缓冲。碰撞检测用的是最简单的轴对齐包围盒AABB方式也就是判断两个矩形的范围是否相交。代码里抽取了公共方法// 判断两个矩形是否相交用于子弹命中敌机、敌机撞玩家 private bool IsCollision(Rectangle a, Rectangle b) { return a.IntersectsWith(b); }Rectangle.IntersectsWith是 System.Drawing 自带的方法它判断的是两个矩形四条边是否交叉。这个方法的判定很「粗」适合飞机大战这种不太需要精确的场合。如果你的游戏里子弹很小、敌机形状不规则这种做法会让人觉得「明明没打中却掉血」那时再换成圆形碰撞检测——比较圆心距离和半径之和。提示项目里所有游戏都共享同一个双缓冲绘制模式读懂飞机大战的循环结构后面五个项目就只是数据结构和规则不同了。3. 俄罗斯方块与贪吃蛇数组旋转、队列和状态转移3.1 二维数组与方块旋转的数学俄罗斯方块是这套项目里数组运用最集中的部分。游戏主区域是一个int[rows, cols]0 表示空非 0 表示有方块落下。每种方块保存为Point[]相对坐标集合旋转操作就是算矩阵旋转。旋转不能改一个点就立刻写回原数组否则会积累误差正确做法是现将点复制到临时位置验证旋转后是否合法不越界、不重叠合法才应用// 逆时针旋转一个方块w 为旋转前宽度 private Point[] Rotate(Point[] cells) { int w 4; // 方块矩阵宽度标准方块为 4x4 Point[] rotated new Point[cells.Length]; for (int i 0; i cells.Length; i) { // 逆时针旋转公式 (x, y) - (y, w - 1 - x) rotated[i] new Point(cells[i].Y, w - 1 - cells[i].X); } return rotated; }旋转公式(x, y) - (y, w - 1 - x)看起来简单但方向容易弄反。顺时针和逆时针两种旋转会得出不同的贴墙方式项目中用的是逆时针。写完旋转逻辑后建议用 I 字形和 L 字形方块各测一次重点看靠墙旋转时是否被卡住很多俄罗斯方块实现都会有「踢墙」处理——方块靠右边时旋转会越界常规做法是旋转后发现越界就将方块左移一列再试。3.2 贪吃蛇的链表移动贪吃蛇实现里最有价值的部分是它的移动方式。刚学的人会把蛇身存成数组每次移动把每个身体段都往前挪一格。这种做法能用但低效项目里用的是队列QueuePoint思路头部加入新位置尾部移除一格整个蛇身自然就「平移」了。由于 C# 的Queue没有直接访问队尾元素的方法代码里用了计数变量记录尾巴位置// 蛇移动head 是新头部坐标tail 是当前尾部坐标 private void MoveSnake(Point head) { QueuePoint newBody new QueuePoint(snakeBody); newBody.Enqueue(head); // 新头部入队 Point tail newBody.Dequeue(); // 尾部出队 // 先判断下一步是否咬到自己再真正更新蛇身 if (snakeBody.Contains(head) !head.Equals(tail)) { GameOver(); return; } // 只有吃到食物时才不删除尾部实现增长效果 if (head.Equals(foodPosition)) { newBody.Enqueue(tail); // 保留原尾部实现蛇身加长 } snakeBody newBody; }注意移动和吃食物的顺序。如果先判断吃食物再移动会出现「蛇头到了食物位置但没吃到」的错位。项目中的Contains(head)判断包含了「新头部是否与旧蛇身重叠」的逻辑但要把特殊情况排除——蛇尾巴刚好移走后新头到达这个位置不算撞到。3.3 游戏状态机的关键点飞机大战、俄罗斯方块、贪吃蛇三个游戏都有暂停和结束状态项目中用枚举GameState区分状态。这个模式建议尽快掌握它是小型游戏最基础的结构。微信小游戏、Unity 游戏里同样采用状态机制只是写法更复杂。状态机体系下Timer的 Tick 事件里通常这样组织逻辑// 游戏循环的骨架任何游戏都适用 private void GameTimer_Tick(object sender, EventArgs e) { switch (gameState) { case GameState.Running: Update(); CheckCollision(); Render(); break; case GameState.Paused: // 只绘制暂停画面不执行 Update RenderPauseScreen(); break; case GameState.GameOver: // 停止移动等待用户按键重新开始 break; } }Update()里更新坐标和逻辑Render()里画图二者分离。很多初学者把绘制和更新混在同一个方法里导致调试时不知道是位置变了没画出来还是画了但位置没变。4. 拼图、连连看与五子棋洗牌算法、路径搜索和评估函数4.1 拼图的洗牌与可解性拼图解谜的实现有细节值得拆开看。它采用的是一维数组存储图片索引再按网格尺寸换算成拼图块坐标。这张图在初始化时会打乱顺序但「随机打乱」和「可解的打乱」不是一回事。如果只是用Random.Shuffle把数字随便打乱大约一半的布局是永远拼不回来的。某些项目不处理只是闷头开始游戏用户拼到最后发现无解体验很差。统计上判断拼图是否可解要看逆序数再加上空格所在的奇偶行// 判断 3x3 或 4x4 拼图是否可解 private bool IsSolvable(int[] tiles, int colCount) { int inversions 0; for (int i 0; i tiles.Length - 1; i) { if (tiles[i] 0) continue; // 0 表示空白块 for (int j i 1; j tiles.Length; j) { if (tiles[j] 0) continue; if (tiles[i] tiles[j]) inversions; } } // 空白行从底部数colCount 为奇数时只看逆序数 int blankRow Array.IndexOf(tiles, 0) / colCount; bool isOddCols (colCount % 2 1); if (isOddCols) return (inversions % 2 0); // 偶数列时逆序数奇偶与空格所在行奇偶需相反 return (inversions colCount - blankRow) % 2 1; }这段代码在初学时会觉得是「数学题」但它教会一个更重要的工程习惯校验输入比处理错误更值得花时间。拼图无解是逻辑问题不是异常问题运行时无法恢复。4.2 连连看的路径判定最多拐两次弯连连看是这个项目里算法含量最高的一个。它的核心是一道搜索题两个格子能否消除取决于它们之间是否存在一条路径——这条路径最多经过两个拐点、且整条路径上不能有其他图案。判断两个格子是否能连通的代码可以读一读// 简化版检测横向/纵向/单拐/双拐路径 private bool CanConnect(Point p1, Point p2) { // 1. 先看同一行或同一列是否直连无障碍 if (p1.X p2.X || p1.Y p2.Y) { if (IsLineEmpty(p1, p2)) return true; } // 2. 单拐点用 (p1.X, p2.Y) 或 (p2.X, p1.Y) 做中间点 if (IsLineEmpty(p1, new Point(p1.X, p2.Y)) IsLineEmpty(new Point(p1.X, p2.Y), p2) IsEmptyCell(new Point(p1.X, p2.Y))) { return true; } // 3. 双拐点遍历每一行/列作为中间线用两个中间点连接 // 遍历二维数组的行和列找到两条线段和一条中间线都为空的情况 return HasTwoCornerPath(p1, p2); }IsLineEmpty的实现就是沿直线从起点扫描到终点遇到非空格子就返回 false。双拐的情况则要遍历棋盘上每条空白行和列把两个单拐路径组合起来。这段代码是逐步优化的好素材——先实现直连和单拐再加双拐每加一档逻辑都要重新验证之前能通过的情况。4.3 五子棋的 AI评分表和简单贪心五子棋的 AI 没有用复杂算法而是一个经典的贪心评分策略。每次轮到电脑时遍历每个空位分别计算该位置对己方和对方的「价值」在横向、纵向、两条对角线四个方向上统计以该位置为中心向两端连续同色棋子的数量再按连子数给分。这个项目里最有学习价值的是评分表的设计棋型示例评分活四_OOOO_100000冲四_OOO_X或XOOO_10000活三_OOO_两边空5000眠三_OO_OX或XOO_O_1000活二_OO_至少一端有 2 空500电脑按「防守优先」还是「进攻优先」取决于这个分数怎么组合使用。项目里常见的写法是「取空位中己方最高分与对方最高分之和最大的位置」这样既不会只进攻不顾防守也不会只会堵别人。如果你想改造成更有攻击性的 AI可以把权重调成己方分数 × 1.1 再比较但别调太高否则会变成无限冲四的「搅局流」。5. 把这六个项目真正变成自己的代码5.1 从哪里开始读最不劝退面对六个项目加一堆.cs文件不要按顺序一个个读。我建议按这个顺序飞机大战 → 贪吃蛇 → 拼图 → 俄罗斯方块 → 连连看 → 五子棋。飞机大战帮你建立「循环和事件」模型贪吃蛇帮你理解「数据驱动表现层」拼图让你认识到「初始化也有合法性」俄罗斯方块是算法密集区适合单独攻读连连看的路径搜索是面试级难度五子棋 AI 可以放在最后当加分项。读每个项目时先找入口Program.cs里的Application.Run或者 WinForms 的Main方法接着找到主窗体的构造函数和 Timer 事件。项目中代码注释非常详细很多关键步骤上方都用中文写清楚了「这里的作用是什么」「为什么要这样写」按注释行往下追逻辑线即可。5.2 改一个数字看一个全局影响初学者最容易犯的错是「看懂逻辑」却从不验证自己的理解。主动改几个参数来验证理解把飞机大战的speed从 8 改成 40观察碰撞判定是否会变困难——你会发现飞机「瞬移」式移动时每帧移动距离大于子弹宽度会出现子弹「穿过」飞机的视觉效果这是快速移动物体共同的检测盲区。把贪吃蛇的 Timer Interval 从 100 改成 300区分「速度慢」正确做法是调整 Interval 而不是在MoveSnake里加Thread.Sleep。把拼图从 4x4 改成 3x3IsSolvable里的奇偶逻辑是否仍然生效。把五子棋的活三和活二评分互换观察 AI 的走棋风格变化。每个改动只用一行但理解深度差很多。5.3 封装一个自己的 GameBase 类读完全部六个项目后你可以尝试把它们共性的东西抽出来。举一个可以从飞机大战开始做的「窗体基类」所有游戏都有初始化、更新、绘制、暂停、结束五件事你可以在System.Windows.Forms.Form上定义这几个虚方法然后让各个游戏的窗体继承它。等你真的动手重构时会发现「为什么原来要把循环和绘制拆开」这个问题答案在自己写代码时逐渐就清楚了。这个项目最有价值的地方在于六个游戏覆盖了 GDI 绘制、键盘事件、数组状态维护、简单搜索算法、贪心策略这些桌面开发常见知识点。能把每个项目的注释读懂并改出一版自己的玩法C# 的面向对象和常用集合类基本就能算掌握了后续再去接触 WPF、Unity 或者上位机开发不会再有「看不懂别人的代码结构」那种卡顿感。本文还有配套的精品资源点击获取
返回列表