1. XenForo 1.5.14 中文版——支持中文搜索!现已发布!查看详情
  2. Xenforo 爱好者讨论群:215909318 XenForo专区

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

本帖由 漂亮的石头2020-10-02 发布。版面名称:知乎日报

  1. 漂亮的石头

    漂亮的石头 版主 管理成员

    注册:
    2012-02-10
    帖子:
    486,336
    赞:
    46
    [​IMG] hanjie zou,C++的子集 阅读原文

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

    tprpix,一个我自己做着玩的「C++ 徒手撸的」 自娱自乐级游戏项目

    [​IMG]

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



    与你的问题相似,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.... )再配合一个构建工具,来管理一个项目。

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

    [​IMG]

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

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

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

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

    Linkers and Loaders

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

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

    Cpp_Empty_Project

    (夹带私货时间~)

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

    2.选择图形库

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

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

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

    LearnOpenGL 英文版中文版

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

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

    3.主函数 和 主循环

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

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

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

    intmain(intargc,char*argv[]){// // init modules// ...// // Main Loop while(!exit()){//...}// // end everything// ...}

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

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

    http://gameprogrammingpatterns.com/game-loop.htmlhttps://dewitters.com/dewitters-gameloop/

    4.游戏模块

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

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

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

    5.在实践的过程中强化 「bypass」 思维

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

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

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

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

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

    6.「能跑的轮子就是好轮子」

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

    阅读原文
     
正在加载...