超级玛丽游戏源码C++:从零搭建横版跳跃游戏工程

发布时间:2026/9/25 23:59:48
超级玛丽游戏源码C++:从零搭建横版跳跃游戏工程 简介这是一份面向游戏开发初学者与C进阶学习者的超级玛丽超级马里奥游戏源码基于C面向对象编程实现适合想通过经典项目理解游戏主循环、角色类设计、地图关卡加载与物理碰撞检测的读者参考。压缩包共49个文件约1.48MB以h头文件与cpp源文件为核心配合obj编译产物、bmp位图素材、txt关卡数据及exe可执行文件完整覆盖从代码到资源的工程结构。源码中可看到游戏主循环、马里奥与敌人角色类、关卡数据结构、碰撞与运动规则、图形音效加载等模块便于分析类与对象的封装方式、内存管理与文件I/O读写关卡数据的实现思路。目前已有3383人学习下载无论新手入门还是老手借鉴都能从中获得游戏开发原理与工程组织方面的实践参考。1. 超级玛丽游戏源码 c从一份能跑起来的工程里看懂横版跳跃的骨架很多人第一次搜「超级玛丽游戏源码 c」心里想的其实是两件事一是想找一份能直接编译运行的完整工程二是想弄明白马里奥那种「踩一下弹起来、顶砖块、吃蘑菇变大」的手感到底是怎么写出来的。我当年也是这么入的坑翻了一堆 c小游戏 的帖子结果拿到的代码要么缺资源、要么依赖一堆看不懂的库编译报错能刷满一屏。后来我才想通真正有价值的不是那份源码本身而是它背后那套「状态机 碰撞检测 瓦片地图」的骨架。这篇笔记就按这个思路走把一份超级玛丽风格的 c 工程从环境、结构、核心循环到踩坑一层层拆开。适合刚学完 c基础、想用 c游戏 练手的人也适合做过小游戏但手感总不对、想回头补物理和碰撞这块的熟手。读完你应该能自己搭出一个能跑能跳的最小可玩版本而不是只会复制粘贴。2. 先想清楚用什么写c 游戏工程的选型与最小依赖2.1 为什么超级玛丽这类横版游戏适合用 SFML 而不是纯控制台纯控制台能写贪吃蛇、推箱子但超级玛丽是实时渲染 逐帧输入 精灵动画控制台根本撑不住。c 做 2D 游戏常见的库有 SFML、SDL2、Allegro还有更重的引擎像 Cocos2d-x。我一般推荐新手从 SFML 起步原因很实在它的 API 是面向对象的sf::Sprite、sf::Texture、sf::Clock这些类名一看就懂配置也比 SDL2 少踩坑。SDL2 更底层、更灵活但你要自己封装纹理管理和事件循环对刚入门的人不友好。至于那些「c小游戏编程100例」里用graphics.h的写法那是 Turbo C 时代的老古董现代编译器基本跑不动别在这上面浪费时间。选 SFML 还有个好处它把窗口、输入、音频、图形打包成一个库你不需要额外再拉一堆依赖。一个超级玛丽的最小可玩版本核心就用到四样东西——渲染窗口、纹理精灵、键盘输入、时间步长。这四样 SFML 全给你备好了。2.2 环境搭建vscode 配置 c 环境跑 SFML 的完整步骤热词里「vscode配置c/c环境」和「dev c」出现频率很高我两种都试过。Dev C 装 SFML 极其麻烦它的编译器版本老链接经常出问题血泪经验是别用它碰第三方库。VS Code 配 MinGW-w64 是目前最顺的路子。下面是我在 Windows 上的实际配置流程。第一步装 MinGW-w64 编译器确认g --version能输出。第二步去 SFML 官网下对应编译器版本的包GCC 版本要和你的一致这点很关键版本不匹配会报一堆 undefined reference。第三步把 SFML 的include和lib路径配进 VS Code 的c_cpp_properties.json和tasks.json。// .vscode/tasks.json 关键片段编译时链接 SFML { version: 2.0.0, tasks: [ { label: build-mario, type: shell, command: g, args: [ -g, main.cpp, player.cpp, level.cpp, // 源文件 -o, mario.exe, -I, C:/SFML/include, // 头文件路径 -L, C:/SFML/lib, // 库文件路径 -lsfml-graphics, -lsfml-window, -lsfml-system // 三个核心库 ], group: { kind: build, isDefault: true } } ] }这段配置的逻辑是-I告诉编译器去哪找SFML/Graphics.hpp这类头文件-L告诉链接器去哪找.a或.lib库文件-lsfml-graphics这些是实际要链接的库。顺序有讲究-lsfml-graphics依赖-lsfml-window-lsfml-window依赖-lsfml-system所以链接顺序不能反反了就会报符号找不到。参数上如果你用的是动态链接dll运行时还得把 SFML 的bin目录加进系统 PATH或者把 dll 拷到 exe 旁边否则双击运行会提示缺 dll。静态链接则是加-static并链接-lsfml-graphics-s这类带-s后缀的库体积大但省心。提示编译报undefined reference to sf::...九成是库路径或链接顺序问题先确认 GCC 版本和 SFML 包版本一致再检查-l的顺序。2.3 工程目录怎么分别把所有代码塞进一个 main.cpp我见过太多「c游戏代码」是单文件几千行改一个跳跃参数要翻半天。超级玛丽这种游戏合理的拆分至少是这几块main.cpp只管初始化和主循环player管马里奥的状态和物理level管瓦片地图和碰撞数据entity作为基类管所有可移动对象。资源文件贴图、音效单独放assets/目录。这样拆的好处是当你调跳跃手感时只动player.cpp里的几个常量不会牵一发动全身。下面这张表是我一般会用的目录结构你可以直接照搬。目录/文件职责改动频率main.cpp窗口创建、主循环、事件分发低player.h/.cpp马里奥状态机、移动、跳跃物理高level.h/.cpp瓦片地图加载、碰撞查询中entity.h实体基类位置、速度、包围盒低assets/贴图、字体、音效低把结构定下来后面加敌人、加道具才有地方放不然代码会越写越乱。3. 核心循环与时间步长为什么你的马里奥一跳就穿墙3.1 固定时间步长让跳跃高度不随帧率变化新手写游戏最容易翻车的地方就是直接用「每帧移动固定像素」。你在 60 帧的机器上跳 3 格高换到 144 帧的显示器上就跳 7 格手感完全变了。正确做法是引入时间步长delta time让移动量 速度 × 时间。但光有 delta time 还不够因为 delta time 本身会波动物理计算会不稳定。我一般用固定时间步长累加器逻辑更新固定按 1/60 秒走渲染按实际帧率走。// main.cpp 主循环固定步长 累加器 sf::Clock clock; const float FIXED_DT 1.0f / 60.0f; // 逻辑固定 60Hz float accumulator 0.0f; while (window.isOpen()) { float frameTime clock.restart().asSeconds(); if (frameTime 0.25f) frameTime 0.25f; // 防止卡顿后大跳 accumulator frameTime; // 逻辑更新可能一帧跑多次保证物理稳定 while (accumulator FIXED_DT) { handleInput(); player.update(FIXED_DT); // 物理用固定 dt accumulator - FIXED_DT; } window.clear(); level.draw(window); player.draw(window); window.display(); }逻辑说明clock.restart()返回上一帧到现在的耗时累加进accumulator。只要累加值够一个固定步长就跑一次逻辑更新并把步长减掉。这样无论渲染帧率是 30 还是 144物理世界永远按 60Hz 推进跳跃高度、下落速度都一致。参数上FIXED_DT取 1/60 是横版游戏的常见值取 1/30 会让手感偏「重」取 1/120 更细腻但计算量翻倍。frameTime那个 0.25 秒的钳制是后悔药防止你拖动窗口或断点调试后一帧积累了几秒的时间导致马里奥瞬移穿墙。3.2 输入处理为什么「按住右键」和「点一下右键」要分开超级玛丽的移动手感一半靠物理一半靠输入。sf::Keyboard::isKeyPressed是查询当前按键状态适合「按住持续移动」sf::Event::KeyPressed是事件适合「按一下触发一次」的动作比如跳跃。如果你把跳跃也写成isKeyPressed按住跳跃键马里奥就会一直往上窜这显然不对。// player.cpp 输入与状态 void Player::handleInput() { // 水平移动按住持续加速 if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { velocity.x ACCEL; // 加速度 } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { velocity.x - ACCEL; } else { velocity.x * FRICTION; // 松手摩擦减速 } // 限制最大速度 velocity.x std::clamp(velocity.x, -MAX_SPEED, MAX_SPEED); } void Player::onJumpPressed() { // 由事件调用只在按下瞬间触发 if (isOnGround) { velocity.y -JUMP_FORCE; // 向上为负 isOnGround false; } }逻辑说明水平方向用加速度 摩擦而不是直接设速度这样起步和刹车有惯性手感更接近原作。ACCEL一般取 800~1200 像素/秒²FRICTION取 0.85 左右每帧乘一次注意这里要配合固定步长。MAX_SPEED决定马里奥跑多快取 200~300 像素/秒比较合适。跳跃用事件触发JUMP_FORCE取 400~600 像素/秒配合重力GRAVITY约 1500 像素/秒²能跳起大约 3~4 个瓦片高。这些数值没有标准答案全靠你反复试我一般会先在纸上算一下「跳跃高度 v²/(2g)」再微调。注意std::clamp需要algorithm头文件c17 起可用。如果你还在用老标准自己写个 if 判断也行。4. 瓦片地图与碰撞检测马里奥踩砖块的判定到底怎么算4.1 用二维数组表示关卡瓦片地图的最小实现超级玛丽的关卡本质是一张网格每个格子要么是空、要么是砖块、要么是地面、要么是问号块。用二维数组存最直观0表示空1表示实心砖2表示问号块以此类推。每个瓦片 32×32 像素马里奥的坐标除以 32 就能换算成格子索引。// level.h 瓦片地图定义 const int TILE_SIZE 32; const int LEVEL_ROWS 15; const int LEVEL_COLS 200; // 0空 1地面 2砖块 3问号块 int tiles[LEVEL_ROWS][LEVEL_COLS] { {1,1,1,1,1, /* ... */}, {1,0,0,0,0, /* ... */}, // ... 其余行 }; // 查询某个像素坐标是不是实心 bool Level::isSolid(int px, int py) { int col px / TILE_SIZE; int row py / TILE_SIZE; if (row 0 || row LEVEL_ROWS || col 0 || col LEVEL_COLS) return true; // 边界外当作实心防止掉出地图 return tiles[row][col] ! 0; }逻辑说明isSolid把像素坐标转成格子坐标再查数组。边界外返回true是个实用技巧这样马里奥走到地图边缘会被挡住而不是掉进虚空。参数上TILE_SIZE取 32 是经典值你也可以用 16 做更精细的地图但碰撞计算量会上升。关卡数据实际项目里不会硬编码在代码里而是从文本文件或 JSON 读但原理一样。4.2 AABB 碰撞与分轴处理解决「贴着墙跳不上去」的玄学碰撞检测用 AABB轴对齐包围盒最简单马里奥一个矩形瓦片一个矩形判断是否重叠。但直接判断重叠然后回退会出现一个经典 bug马里奥贴着墙跳明明没碰到墙却被卡住。原因是水平移动和垂直移动同时处理回退方向搞混了。正确做法是分轴处理先移动 x检测 x 方向碰撞并修正再移动 y检测 y 方向碰撞并修正。// player.cpp 分轴碰撞 void Player::moveAndCollide(float dt, Level level) { // 先处理水平 position.x velocity.x * dt; sf::FloatRect box getBounds(); if (level.isSolid(box.left, box.top box.height/2) || level.isSolid(box.left box.width, box.top box.height/2)) { // 撞墙回退并清零水平速度 position.x - velocity.x * dt; velocity.x 0; } // 再处理垂直 position.y velocity.y * dt; box getBounds(); isOnGround false; if (velocity.y 0) { // 下落检测脚底 if (level.isSolid(box.left box.width/2, box.top box.height)) { position.y (int)((box.top box.height) / TILE_SIZE) * TILE_SIZE - box.height; velocity.y 0; isOnGround true; } } else if (velocity.y 0) { // 上升检测头顶 if (level.isSolid(box.left box.width/2, box.top)) { position.y (int)(box.top / TILE_SIZE 1) * TILE_SIZE; velocity.y 0; } } }逻辑说明水平检测只取马里奥左右两侧中点的像素去查isSolid比查整个矩形快也够用。撞墙后回退到移动前的位置并清零速度避免抖动。垂直方向分上升和下落两种情况下落时检测脚底撞到就把 y 对齐到瓦片顶部并标记isOnGround true上升时检测头顶撞到就把 y 对齐到瓦片底部。这个「对齐」操作是防止马里奥陷进砖块里的关键。参数上getBounds()返回的矩形通常比精灵略小一圈比如左右各缩 2 像素这样视觉上贴着墙但判定不卡属于手感微调。提示分轴处理的顺序建议先水平后垂直这样马里奥在斜坡和墙角的表现更自然。如果你先垂直后水平贴墙跳时容易粘墙。5. 避坑与排查超级玛丽 c 源码编译运行最常见的 5 个翻车点5.1 现象编译通过但运行黑屏什么都不显示原因通常是渲染顺序错了或者纹理没加载成功但没报错。SFML 里如果你先window.display()再window.clear()画面会闪一下全黑。另一个常见原因是sf::Texture对象在函数内创建函数返回后纹理析构精灵引用的是野纹理画出来就是白块或黑块。解决确保clear → draw → display的顺序纹理对象要么作为类的成员变量要么用sf::Texture的静态资源管理器持有。加载纹理后检查texture.loadFromFile的返回值失败就打印路径别默默忽略。5.2 现象马里奥移动速度在不同电脑上不一样原因就是没用固定时间步长直接按帧移动。有的机器 60 帧有的 144 帧速度差一倍多。解决按第 3 章的累加器写法逻辑更新固定 1/60 秒。所有速度、加速度、重力都按「像素/秒」为单位不要按「像素/帧」。5.3 现象马里奥能跳但落地后一直抖或者陷进地面原因是碰撞修正时没有把位置对齐到瓦片边界只是简单回退速度导致下一帧又重叠、又回退来回抖。或者isOnGround判断时机不对落地那一帧没置位。解决落地时把 y 坐标直接对齐到瓦片顶部 - 包围盒高度如第 4 章代码所示。isOnGround每帧先置false检测到脚底碰撞再置true不要跨帧保留。5.4 现象链接时报undefined reference to sf::...一大堆原因有三SFML 版本和 GCC 版本不匹配、库路径写错、链接顺序反了。解决先确认g --version和 SFML 包名里的 GCC 版本一致。再检查-L路径下确实有libsfml-graphics.a。最后确认-lsfml-graphics -lsfml-window -lsfml-system这个顺序依赖方在前被依赖方在后。5.5 现象按跳跃键马里奥连续跳停不下来原因是用isKeyPressed查询跳跃而不是用事件。按住键每帧都触发跳跃。解决跳跃逻辑放到sf::Event::KeyPressed事件里并且加一个isOnGround判断只有落地时才能跳。如果想做「长按跳更高」可以记录按住时长在松开或超时后截断上升速度但这是进阶手感先把基础跳做对。6. 让手感更像原作可变跳跃高度与状态机的进阶写法基础版能跑能跳之后你会发现和原作最大的差距在「跳跃控制感」。原作里轻点一下跳得矮按住跳得高这就是可变跳跃高度。实现思路是跳跃按下时给一个初始向上的速度如果玩家在上升过程中松开了跳跃键就把当前上升速度乘以一个衰减系数比如 0.5让马里奥提前进入下落。这样轻点自然矮按住自然高。// player.cpp 可变跳跃高度 void Player::onJumpReleased() { if (velocity.y 0) { // 还在上升 velocity.y * 0.5f; // 截断上升速度 } } void Player::update(float dt) { velocity.y GRAVITY * dt; // 重力持续作用 // 限制下落最大速度防止穿透 if (velocity.y MAX_FALL_SPEED) velocity.y MAX_FALL_SPEED; moveAndCollide(dt, level); }逻辑说明onJumpReleased在松开跳跃键的事件里调用只对上升阶段生效。GRAVITY取 1500 左右MAX_FALL_SPEED取 600~800防止下落太快穿过薄砖块。这个 0.5 的系数就是手感旋钮调小跳得更矮调大更接近按住的效果。再往上走就是状态机。马里奥至少有这几个状态站立、跑动、跳跃、下落、蹲下、死亡。用枚举加switch管理每个状态决定用哪张精灵图、能不能接受输入。比如跳跃状态下不能再跳死亡状态下忽略所有输入只播动画。状态机写好了后面加「吃蘑菇变大」「踩敌人弹起」都是往状态里加分支不会把代码写成一团乱麻。我自己的习惯是每加一个新机制先在纸上画出状态转移图标清楚「什么事件触发、从哪个状态到哪个状态」再动手写代码。这个习惯帮我省了无数次重构。超级玛丽看着简单但它的手感是几十年打磨出来的你能把跳跃和碰撞这两块调到自己满意就已经摸到横版游戏的门道了。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询