问题

C++到底是如何从代码到游戏的?

回答
好的,咱们就聊聊C++这玩意儿怎么从一堆字符变成能在屏幕上蹦跶的游戏,这事儿说起来也挺有意思的,不是什么神秘魔法,就是一层层剥洋葱,一层层解锁。

你想想,你手里拿着一本菜谱,里面写着各种步骤、配料,但它本身并不能变成一道菜。C++代码也是一样,它只是你对电脑下达的指令。那怎么才能变成一场让你沉浸其中的冒险呢?

第一步:把你的想法变成C++的“语言”

你脑子里肯定有个游戏雏形:我想做一个能跳的方块,遇到墙就停下,还能收集金币。这些想法都需要用C++的语法给表达出来。

变量: 那个方块,它在屏幕上的位置在哪儿?需要用变量来表示,比如 `int x_position; int y_position;`。金币有多少? `int score;`。
函数: 方块怎么动?这就需要写函数。比如 `void move_right() { x_position++; }`。方块怎么跳? `void jump() { y_position = jump_height; }`。
类(面向对象): 像方块、金币,它们都有自己的属性(位置、大小、颜色)和行为(移动、碰撞)。这时候,C++的“类”就派上用场了。你可以定义一个 `Player` 类,包含 `x_position`, `y_position`, `jump()` 等成员;定义一个 `Coin` 类,包含 `x_position`, `y_position` 等。这样管理起来更清晰。
控制结构: 方块碰到墙怎么办?这就需要条件判断:`if (x_position >= screen_width) { x_position = screen_width; }`。还有循环,比如检测所有金币是否被收集:`for (int i = 0; i < num_coins; ++i) { if (player.collides_with(coins[i])) { score++; } }`。

写到这里,你脑子里的那些抽象想法,就变成了一行行看得懂(至少自己看得懂)的C++代码了。但这时候,它还是一堆字符,静静地躺在你的代码编辑器里。

第二步:翻译官——编译器

电脑可不认识C++这套“人话”。它只认识机器语言,也就是一堆0和1。这时候就需要一个翻译官,这个翻译官就是 编译器。

最常见的C++编译器有 GCC (GNU Compiler Collection)、Clang、MSVC (Microsoft Visual C++) 等。你把写好的C++源代码(比如 `.cpp` 文件)丢给编译器,它就会开始工作。

编译器做的可不止是简单的逐字翻译,它会做很多事情:

词法分析: 把代码分解成一个个“词”,比如关键字 (`if`, `for`, `int`)、标识符 (`x_position`, `jump`)、运算符 (`+`, ``, `=`)、字面量 (`10`, `'A'`)。
语法分析: 检查你写的代码是否符合C++的语法规则。比如,是不是少了个分号?括号是不是配对了?
语义分析: 检查代码的意义是否合理。比如,你有没有试图把一个字符串赋值给一个整数变量?变量有没有在使用前声明?
生成汇编代码: 将分析和检查通过的代码,转换成更接近机器的汇编语言。汇编语言是特定于处理器的指令集,比如 x86、ARM。
汇编成目标代码: 汇编器将汇编代码转换成机器码(目标文件),这时的文件通常是 `.o` (Unix/Linux) 或 `.obj` (Windows)。

这就像把一本中文菜谱,先给它进行语法、逻辑上的校对,然后变成一套精密的、一步步的烹饪步骤的草稿,最后变成可以被厨师(机器)直接执行的简单指令。

第三步:拼装工——链接器

你写游戏,肯定不是只写一个 `.cpp` 文件。你可能有用来处理玩家输入的,有用来画图的,有用来管理音效的。这些分散的 `.cpp` 文件,经过编译器编译后,会变成一个个独立的目标文件。

链接器的工作就是把这些目标文件,以及你从各种库中引入的代码,统统“粘”在一起,形成一个最终的可执行文件(比如 `.exe` 在Windows上,或者一个在Linux上可以直接运行的文件)。

库: 很多常用的功能,比如处理文件、网络通信、数学运算,甚至图形绘制,别人已经帮你写好了,并打包成库文件(静态库 `.lib`/`.a`,动态库 `.dll`/`.so`)。你的代码里用 ` include ` 实际上就是在告诉编译器去“引入”标准输入输出库里的相关定义。
符号解析: 在不同的目标文件中,你的函数可能会被多次调用。链接器会找到这些函数的具体实现,把调用点和实际地址关联起来。

链接器就像一个大工程的监工,把各个工坊(编译后的目标文件)生产出来的零件,按照图纸(链接脚本)的要求,组装成一辆完整的汽车(可执行程序)。

第四步:让一切动起来——操作系统和硬件

现在你有了这个可执行文件,它就像一张包含了所有指令的蓝图。当你双击这个文件(或者在命令行里输入它的名字),操作系统 就登场了。

加载: 操作系统负责将这个可执行文件从硬盘加载到内存中。
启动: 然后,它会告诉 CPU 从程序的入口点开始执行指令。
管理资源: 操作系统会负责协调 CPU 时间、内存使用、文件访问、输入输出设备(键盘、鼠标、显示器、声卡)等。

你的C++代码里可能写了 `std::cout << "Hello, World!" << std::endl;`。这句看似简单的话,背后会调用操作系统的函数来完成实际的屏幕输出。你写的 `player.jump()`,最终会转化为一系列CPU指令,这些指令操作着内存中的数据,然后通过显卡(GPU)把像素点绘制到屏幕上。

游戏引擎的作用

上面讲的是从C++代码到可执行文件的基本过程。但游戏开发远不止于此。现在大多数游戏开发都会借助 游戏引擎,比如 Unreal Engine、Unity (虽然主要是C,但底层也有C++支持),或者一些基于C++的开源引擎如 Godot (C++版本)。

游戏引擎就像一个预制好的超级工具箱,它已经帮你处理了很多底层细节:

图形渲染: 复杂的3D 模型如何变成屏幕上的像素?引擎里有大量的C++代码(通常是调用图形API,如 DirectX, Vulkan, OpenGL)来处理纹理、光照、着色器等。你的游戏逻辑只需要告诉引擎“在这里画一个玩家”,引擎就会负责大部分的绘制工作。
物理模拟: 物体如何碰撞?重力如何影响它们?引擎内置了强大的物理引擎,处理这些复杂的数学计算。
输入处理: 如何接收键盘、鼠标、手柄的输入?引擎提供了一套统一的接口。
音频处理: 播放背景音乐、音效?引擎也有负责的模块。
资源管理: 加载模型、贴图、音频文件等。

当你在游戏引擎里用C++写游戏时,你的代码更多是关于游戏本身的逻辑,比如玩家的AI、关卡的布局、战斗的规则,而不是直接去操作显存或者声卡。你是在引擎提供的框架和工具上进行二次开发。

总结一下:

1. 思考与编码: 你用C++的语法,将游戏设计变成一系列指令。
2. 编译器: 将C++代码翻译成机器能理解的汇编和目标代码。
3. 链接器: 将所有目标代码和库文件粘合起来,形成一个可执行程序。
4. 操作系统: 加载并执行这个程序,管理硬件和系统资源。
5. CPU/GPU/硬件: 实际执行指令,进行计算、绘制、发声等。
6. 游戏引擎: (可选但常见)提供一个高级框架,简化了许多底层开发工作,让你更专注于游戏玩法。

整个过程,就像你要建造一座大楼。你的C++代码是建造图纸和施工计划,编译器和链接器是建筑工人,操作系统是项目经理,而最终运行的电脑硬件,就是那片工地和所有施工设备。游戏引擎则像是预制了钢筋混凝土框架和水电系统的大型施工企业,大大加快了建造速度。

所以,C++从代码到游戏,不是一步到位,而是通过一系列“翻译”、“组装”和“执行”的流程,最终在硬件上活灵活现地呈现出来。这是一个将抽象指令转化为具体视觉和交互的奇妙过程。

网友意见

user avatar
此回答被部分 b站 up主 盗了好多次...心累。
还是提前声明下吧:本文本项目不支持转载,谢谢~
(近期(2021.04.22)忙着学习,腾不开手来。朋友们如果看到盗用视频,可以顺手举报之,或发我链接也行~多谢啦)

原回答(2020.4):

恬不知耻地,拿一个我自己写着玩的项目来举例吧~

tprpix,一个用 “C++徒手撸的” 自娱自乐级游戏项目(流量警告!!!...

https://www.zhihu.com/video/1230124634190233600 https://www.zhihu.com/video/1230124683078471680 测试:地图自动生成模块 https://www.zhihu.com/video/1230124715835936768

这是一个我自己做着玩的 私人项目

项目仍在推进中,很多部分甚至尚未开工(还是脚手架状态)。不过我已经把它开源到了 GitHub ,欢迎大佬们去拍砖~

(有关这些项目的额外信息,可以参见这个答案:

关于 tprpix 的更多废话:

与你的问题相似,tprpix 的诞生,也是为了动手实践下:“用C++徒手搭建一个游戏,是个怎么样的体验”。在开启这个项目之前,我只用 C++ 实践过一两个 2000行级的迷你项目。(更早之前,则是在用 C 和一点点汇编,跟着教程搭一个 类unix 操作系统内核(玩具级))确切地说,tprpix 就是我的第一个 C++ 项目。在开工的头几个月里,我始终处于:边翻书学习新用法,边重写原有模块 的状态。

tprpix 中的绝大多数模块都是徒手撸出来的,包括但不限于:

  • 基于 OpenGL 的画面渲染层,和一个简易的帧动画播放器
  • 一个轻量级 移动碰撞检测模块
  • 一个基于 Perlin Noise 的地图生成器
  • 一个“蓝图”系统,读取 json 文件,用来定制不同生态的地景分配规则
  • 水域系统(已被改设定为:“源自异星的生物汤“...(中二...
  • 基于 SQLite3 数据库的 自动读档存档
  • 基于 glfw 的 游戏手柄支持(Xbox360-Style)
  • 跨平台,支持 Win10 / MacOSX / Linux(Ubuntu) 编译

如上述细节所提,我也使用到了第三方库,比例:

  • OpenGL 方面的库:GLMglfwglad
  • 小型数据库: SQLite3
  • json解析库:RapidJSON
  • debug库:fmtspdlog
  • enum库:magic_enum

目前的 tprpix 只为试玩者提供了非常有限的功能:你可以操纵一只鸡,遍历这个世界。不管朝任何方向走,地图都会自动生成/销毁。在这个世界中,运用一套规则(perlin noise+蓝图)来自动在游戏世界中分布景物和人造物(草,树,墙壁篝火等.... 类似于 MineCraft 的地图生成机制... )

更多功能还在编写中,更多生物也在制作中...


废话完毕进入正题:

“从零开始,搭建一个游戏项目,到底该怎么做?” 我觉得可以分为:

  1. 学习如何搭建 C++ 项目
  2. 选择图形库
  3. 主函数 和 主循环
  4. 游戏模块
  5. 在实践的过程中强化 “bypass” 思维
  6. "能跑的轮子就是好轮子"

以下是展开:

1 .学习如何搭建 C++ 项目

这其实是所有 C/C++ 玩家都要面临的问题。遗憾的是,绝大多数语法书都不会提及这块。这导致很多玩家在学习了一段时间语法后,仍然只能捏着一个 .h, 两个 .cpp文件(一个main.cpp, 一个 test.cpp)来构建项目,非常凄惨。

如果你的项目只想支持 win平台,那不妨直接学习如何在 VS中创建项目。如果你想要跨平台,那或许该学习下 CMake 的使用。往深了说就是:

学习使用 有组织的 目录,文件 (.h, .cpp, .hpp, .vs, .fs, .json, .png.... ) 再配合一个构建工具,来管理一个项目。

对于这件事,我的建议很粗暴:抄!去找一个别人写的迷你项目,照着对方的格式,搭建自己的项目。比方说 这个

这是一个大佬使用 C/OpenGL/Cmake 复刻 MineCraft 的项目。在某些方面,它做得挺屎,比如它会在单个 main.c 文件里堆上30来个函数,这些函数之间相互关联,难以解耦(也可能是大佬想把它快快做完,好赶去玩别的~)。但另一方面,它在目录规划上挺值得借鉴( 你甚至还可以学一学它的 CMakeLists.txt 文件的写法... )

抄到合适的项目格式后,可以先停顿一下,拿这个项目框架,去编写几个小工具。在实践中,加深对项目格式的理解。不出所料的话,你很可能会遭遇 编译链接 方面的问题,这其实也是一个暗坑:

链接(Linking)著名三不管地区~ 语法书们觉得这玩意儿不属于语法,所以不讲。环境库的书觉得这种东西你应该懂,所以也不讲。再加上它本来就有点神秘,如果之前的你都是在几个很小的 .h .cpp 文件里堆代码。相信我,当遭遇链接问题时,你会一脸懵逼。

这种时刻,最好的办法就是看书查资料,比如《深入理解计算机系统》(3th) 中的第七章。或者:

当然,如果你为了跨平台而投奔 cmake,那将是另一段磨难... (鉴于知乎 cmake大佬太多,而我在这方面又特菜,就不摆弄了...


最后的最后,如果你特别懒,并不想去琢磨别人的项目到底是怎么搭出来的,不妨去 GitHub 搜索关键词:“cpp empty project”(或类似的词)。其实 已经存在很多现成的模板。在这里,我也提供一份我自己的:

(夹带私货时间~)

这是一个 基于 cmake/C++17 的跨平台空项目。从上面那个游戏项目 tprpix 整理抽取而来。你可以在这个空项目基础上,搭建出你自己的跨平台程序。(具体食用方式已经写在项目中,在此略过...


2.选择图形库

游戏的一个核心模块就是 画面呈现。你可以直接使用功能便捷的图形界面框架,比如 QT。或者底层的图形库,比如 OpenGL, DirectX。不管选择哪一种,这些图形库的使用方式,都会影响你的 主函数,主循环的编写格式。

所以,请在项目开始的第一阶段,确定好自己使用的图形库。

在这里,我以 OpenGL 为例。你可以直接根据这个教程来入门图形学。顺带学习如何搭建一个 OpenGL 视觉交互程序:

图形库方面的代码有个特点,就是它们的编写格式往往是固定的。直白地说就是:你在项目前期劳烦下,把这些代码理解透,然后封装进你自己写的模块中、排布进你的游戏主循环中,然后就不大需要改动它们了(唯一需要开放出来,以便时刻添新的就是 shader 代码了)。也许,未来某天,当你领悟了更好的设计方法,你会回来重构。但整体上,你应该在游戏制作的前期,就把这些东西确定下来。

如果你希望你的游戏有惊人的画面表现,也许你该停下来,深入挖掘下图形学方面的知识。不过,做出一个游戏需要投入的的方面有很多,视觉只是它的一部分。

3.主函数 和 主循环

游戏的本质就是一个 时刻等待玩家输入的交互程序:

游戏程序启动,优先执行各个模块的初始化。然后跌入一个巨型的 while 循环中,以每秒60帧(或更多)的速度,反复执行 while 体内的代码。直到某一刻,玩家的某个输入触发exit事件。然后正式退出 while 循环。在执行一系列收尾工作后,彻底关闭游戏进程。

这么一看,一旦搞定了 主函数主循环。游戏框架似乎就出来了。我的建议是,项目前期请使用最简单粗暴的方式来实现,比如:

       int main( int argc, char* argv[] ){      // <---1--->     // init modules     // ...          // <---2--->     // Main Loop      while( !exit() ){         //...     }      // <---3--->     // end everything     // ...  }      

要么照着图形库教程传授你的格式去搭,也是完全够用了。或者说,这也许是更值得推荐的方法。如果你真的跟着那些图形库教程一路走下去,你会发现,它们已经教会你如何去搭建一个合格的 视觉交互程序。在这个交互程序的基础上,再往前多走两步,将它塑造成一款游戏,是件自然而然的事。

回到 主函数主循环。如果你想把这部分做得更考究的话,不妨阅读下这两篇文章:

4.游戏模块

一个游戏,到底该被划分成哪些模块呢?

此时不妨去看看 正经的游戏引擎是怎么设计的,比如 unity3D。你可以借鉴一下它的设计思路,结合自己的游戏内容,设计一组功能相似的模块。(可以是简化版,合并版,只要能满足你的游戏就行~)

好吧,这一段我讲得有点敷衍。但怎么讲呢:为自己的游戏,亲手实现一些功能性模块,其实是非常快乐的过程。如果说,在处理上文提及的图形渲染方面的代码时,你还有点畏手畏脚的话(毕竟大家都是第一次)。那么现在,当你开始写功能性代码时,你就可以像对待一个纯粹的程序那样,去组织,去实现它了。用上你在 C++ 和其他书里学到的所有知识:放开脑洞,埋头填坑,停下总结,然后再翻工重写...


5.在实践的过程中强化 “bypass” 思维

好吧我承认,这个概念是我自己编的。若有发现更专业的描述,还请告诉我...

当你正式开工,并且推进了一段时间后。你可能会失落地发现,有些模块的制作工期实在太长了。有些模块则相互依赖,在所有依赖模块都完成之前,你就是无法看见你的程序运行的样子。这种体验使人沮丧,在你撑到最后一刻之前,你的意志力已经被消费光了。我的建议就是:搭建微型的 旁路系统(bypass)

怎么讲呢,假设你正在写一个模块 A,而 A 还依赖于另一个模块 B。而这个 B,你其实一行代码都没写呢。传统的方法当然是把两个都写完整后再跑。但这个依赖关系可能很长,不只是 AB 这么简单。 此时的你不妨尝试:搭建一个空架子的 B,它暂时什么也不干。但它的一些接口函数,切切实实联通到了大流程中。(就像原初设计的那样) 然后就拿着这个看起来像是被故意短路了的程序,去测一测跑一跑了。(当然,并不是真的短路到没法运行,而是功能上的短路)

随时随地的反馈是很有必要的,它是你坚持下去的动力源

当然,以上只是一个很简陋的例子。放大了讲,万物皆可被 bypass。以各种灵活的方式。


6."能跑的轮子就是好轮子"

(注:没有对轮子哥的不敬,轮子哥是我偶像~)

这其实也是 bypass 思维的一个变种:一个简陋的,但能正常运行的模块,就是好模块。不要担心第一套方案设计得不够完善。随着项目的推进,经验的积累,你还可以回来重写嘛~(捂脸)


(废话先到这里,未来再来补充....)

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有