原文链接:https://hacker noon . com/build-a-game-engine-from-scratch-in-c
游戏开发一直是学生学习高级计算机科学的巨大动力。有些人可能认为游戏是孩子们的最爱,但对于标准的计算机科学课程来说,游戏开发实际上是少数几个可以用到所有知识的领域之一。
游戏开发涉及标准计算机科学课程中的许多内容。
根据游戏的性质,你可能需要深入更具体的专业,比如分布式系统或者人机交互。游戏开发是一项严肃的工作,也是帮助学生学习计算机科学概念的有力工具。
本文将详细介绍用C++创建一个简单的游戏引擎所需要的一些基本构建块,解释游戏引擎需要的主要组件,并给出一些如何从头开始编写游戏引擎的个人建议。
但是,本文不是编程教程,因此不会涉及太多的技术细节,也不会解释所有这些元素是如何通过代码绑定在一起的。如果你想找到一个关于如何编写C++游戏引擎的综合教程,你可以先看看用C++和Lua创建一个2D游戏引擎。
一、游戏引擎是什么?
一般来说,游戏引擎是一套优化视频游戏开发的软件工具。这些引擎可以很小,也可以极简,简单到只提供一个游戏周期和几个渲染功能;当然也可以很大很全面,类似于IDE应用。开发者可以用它来编写脚本、调试、定制关卡逻辑、人工智能、设计、发布、协作,最后不离开引擎从头到尾构建游戏。
游戏引擎和游戏框架通常向用户公开一组API。这些API允许程序员调用引擎函数,执行类似黑盒的困难任务。
为了真正理解这些API是如何工作的,让我们用应用程序来更详细地解释它们。比如游戏引擎API公开了一个名为“IsColliding()”的函数,开发者可以调用这个函数来检查两个游戏对象是否发生碰撞,这种情况并不少见。程序员不需要知道这个函数是如何实现的,也不需要知道正确判断两个形状是否重叠所需的算法。就我们而言,IsColliding函数只是一个黑盒,它会根据这些对象是否发生碰撞来正确返回true或false。以下是大部分游戏引擎向用户公开的一个函数的例子。
大多数引擎抽象冲突检测,并简单地将其公开为真/假函数。
除了编程API,游戏引擎的另一个重要职责就是硬件抽象。例如,3D引擎通常建立在专用的图形API上,如OpenGL、Vulkan或Direct3D。这些API为图形处理单元(GPU)提供软件抽象。
当谈到硬件抽象时,有一些低级库(如DirectX、OpenAL和SDL)提供了对许多其他硬件元素的抽象和多平台访问。这些库帮助我们访问和处理键盘事件、鼠标移动、网络连接,甚至音频。
二、游戏引擎的崛起
在游戏行业的早期,游戏是使用定制的渲染引擎构建的。开发该代码是为了尽可能地从较慢的机器上提高某些系统的性能。每一个CPU周期都至关重要,所以适合多种场景的代码重用或通用函数不是开发者能负担得起的。
随着游戏和开发团队规模和复杂程度的扩大,大多数工作室最终都会在开发的各种游戏中重用某些函数和子程序。工作室开发的内部引擎基本上是内部文件和库的集合,用于处理底层任务。这些功能允许开发团队的其他成员专注于高级细节,如游戏操作、地图创建和关卡定制。
一些流行的经典引擎包括id-Tech、Build和AGI。创建这些引擎是为了帮助特定游戏的开发。他们允许团队的其他成员快速开发新的关卡,添加自定义资源,并动态自定义地图。这些定制引擎也用于为他们的原始游戏修改或创建扩展包。
Id Software软件公司(美国德克萨斯州的一家游戏软件公司)开发了id Tech技术。Id Tech技术实际上是一系列不同引擎的集合,每个引擎的迭代周期都与不同的游戏相关。因此,开发者通常将id Tech 0描述为“Wolfenstein3D引擎”,id Tech 1描述为“末日引擎”,id Tech 2描述为“雷神之锤引擎”等等。
Build是90年代游戏引擎史上的又一个例子。它是由Ken Silverman创建的,用于帮助定制第一人称射击游戏。与id Tech类似,Build随着时间的推移而发展,它的不同版本已经帮助程序员开发了诸如Duke Nukem 3D、Shadow Warrior和Blood等游戏。这三个可以说是利用Build引擎开发的最受欢迎的游戏作品的代表,通常被称为“三巨头”。
由Ken silverman开发的构建引擎正在2D模式下编辑关卡。
游戏引擎在90年代的另一个例子是Manic Mansion的脚本创建实用程序(SCUMM)。SCUMM是LucasArts开发的引擎,是许多经典点击游戏的基础,如猴岛和全速前进。
全速风暴的对话框和操作由SCUMM脚本语言管理。
随着机器的发展和功能的不断增强,游戏引擎也在发展。现代引擎配备了功能丰富的工具,这些工具需要快速的处理器速度、惊人的内存和特殊的显卡。
有了后备电源,现代游戏引擎可以用机器周期来换取更多的抽象。这种权衡意味着我们可以将现代游戏引擎视为一种通用工具,以更低的成本和更短的开发时间来创建复杂的游戏。
三、为什么要制作游戏引擎?
这不是一个奇怪的问题,不同的游戏程序员也有自己的看法。答案取决于游戏的性质、业务需求和其他需要考虑的因素。
开发者可以使用许多现有的免费、强大和专业的商业引擎来创建和部署自己的游戏。那么,既然有这么多游戏引擎可供选择,为什么还会有人费心从头开始做一个游戏引擎呢?
我曾经写过一篇博文“你是自己写游戏引擎还是用现成的?”(https://pikuma.com/blog/why-make-a-game-engine)解释一下程序员从零开始做游戏引擎的一些原因。在我看来,主要原因包括:
学习机会:对游戏引擎如何工作的深刻理解可以让你成为一名优秀的开发者。
工作流控制:您可以更好地控制游戏的特殊方面,并调整解决方案以满足工作流的需要。
定制:能够根据独特的游戏需求定制解决方案。
极简主义:较小的代码库可以减少较大游戏引擎的开销。
创新:您可能需要实现一些其他引擎不支持的全新或非正统的硬件。
四、如何制作游戏引擎
下面将继续讨论游戏引擎的一些组件,引导读者自己编写一个游戏引擎。
1.选择一种编程语言
开发核心引擎代码的编程语言是首选。原始的汇编语言,C,C++,甚至C#,Ja,Lua,JaScript等高级语言都是用来开发引擎的。
编写游戏引擎最流行的语言之一是C++。C++编程语言将速度与使用面向对象编程(OOP)和其他编程范例的能力相结合,以帮助开发人员组织和设计大型软件项目。
因为我们开发游戏的时候,性能通常是很重要的,C++有编译语言的优势。使用编译语言意味着最终的可执行文件将在目标机器的处理器上本地运行。此外,有许多特殊的C++库和开发工具包可用于大多数现代游戏机,如PlayStation或XBox。
开发者可以使用微软提供的C++库来访问XBox控制器。
性能方面,我个人不建议使用虚拟机、字节码或者其他任何中间层语言。除了C++,一些适合编写核心游戏引擎代码的替代方案包括Rust、Odin和Zig。
本文将用C++编程语言构建一个简单的游戏引擎。
2.硬件访问
在MS-DOS等较老的操作系统中,我们通常可以直接操纵内存地址,访问映射到不同硬件组件的特殊位置。例如,如果我想用某种颜色“绘制”一个像素,我所要做的就是加载一个特殊的内存地址,其中的数字代表VGA调色板的正确颜色,显示驱动程序将这种变化转换为物理像素,并转换为CRT显示。
随着操作系统的发展,他们负责保护硬件免受程序员的攻击。现代操作系统不允许修改代码。操作系统为进程提供的内存位置不是允许的地址。
例如,如果您使用的是Windows、macOS、Linux或BSD,您需要向操作系统请求正确的权限,以便在屏幕上绘制像素或与任何其他硬件组件对话。即使是简单的任务,如在操作系统桌面上打开一个窗口,也必须通过操作系统API来执行。
因此,运行一个进程、打开一个窗口、在屏幕上呈现图形、在窗口中绘制像素、甚至从键盘读取输入事件都是操作系统特定的任务。
SDL(Simple DirectMedia Layer)是一个非常流行的库,它可以帮助实现多平台硬件抽象。在游戏开发课上,通过SDL,我不需要为使用Windows操作系统、macOS和Linux系统的学生创建三个不同版本的代码。SDL不仅是不同操作系统之间的桥梁,也是不同CPU架构(Intel、ARM、苹果M1等)之间的桥梁。).SDL库对底层硬件访问进行抽象,并“翻译”我们的代码,以便在这些不同的平台上正确工作。
以下是使用SDL在操作系统上打开一个窗口的简短代码。为了简单起见,代码中没有错误处理部分,但是下面的代码对于Windows、macOS、Linux、BSD甚至RaspberryPi都是通用的。
SDL只是我们可以用来实现这种多平台硬件访问的游戏库的一个例子。SDL是2D游戏和将现有代码移植到不同平台和控制台的流行选项之一。多平台库的另一个流行选项是GLFW,主要用于3D游戏和3D引擎。GLFW库可以很好地与OpenGL、Vulkan等加速3D API进行通信。
3.游戏的主要循环
此时,一旦解决了操作系统解决方案,我们需要创建一个主循环来控制整个游戏。
简单来说,我们通常希望我们的游戏以每秒60帧的速度运行。根据不同的游戏,帧率可能会有所不同,但为了让场景更清晰,胶片上拍摄的电影通常以每秒24帧的速度运行(每秒24个画面在你眼前闪过)。
在游戏过程中,游戏周期会继续运行,我们的引擎需要在每个周期运行一些重要的任务。传统的游戏周期必须确保:
无阻塞地处理输入事件。
更新当前帧的所有游戏对象及其属性。
在屏幕上呈现所有游戏对象和其他重要信息。
然而,一个简单的C++循环对我们来说是不够的。游戏周期肯定和现实世界时间有一定关系。毕竟游戏中的敌人在任何机器上的移动速度都应该是一样的,不管这些机器的CPU时钟速度如何。
控制这个帧率,设置成固定的FPS数,其实很有意思。它通常需要我们跟踪帧之间的时间差,并进行一些合理的计算,以确保我们的游戏在至少30 FPS的帧率下流畅运行。
4.输入事件处理
很难想象如果不从用户那里读取一些输入事件,游戏会是什么样子。所有这些输入事件可能来自键盘、鼠标、游戏板或虚拟现实设备。因此,我们必须在游戏周期中处理不同的输入事件。
为了处理用户输入,我们必须请求访问硬件事件,这必须通过操作系统API来执行。我们可以使用一些著名的多平台硬件抽象库(SDL、GLFW、SFML等。)来处理用户输入。
例如,如果我们使用SDL,我们可以实现轮询事件,然后我们只需要几行代码就可以处理各种输入事件。
再说一次,如果我们使用像SDL这样的跨平台库来处理输入,我们就不必太担心特定于操作系统的实现。不管我们的目标平台是什么,C++代码都应该是一样的。
至此,我们有了一个工作的游戏周期和一个处理用户输入的方法。那么是时候开始考虑在内存中组织游戏对象了。
5.在内存中表示游戏对象。
在设计游戏引擎时,我们需要设计数据结构来存储和访问游戏对象。
程序员在设计游戏引擎时会用到多种技术。一些引擎可能使用简单的面向对象的方法来处理类和继承,而另一些可能将它们的对象组织成实体和组件。
如果想学习更多的算法和数据结构,建议尝试实现这些数据结构。如果你正在使用C++,一个选择是使用STL(标准模板库)并利用许多数据结构(向量、列表、队列、堆栈、映射、集合等)。)附在上面。C++STL很大程度上依赖于模板,所以在实际项目中练习使用模板,看看它们的作用,是一个很好的机会。
看了游戏引擎架构的一些内容,你会发现游戏中最流行的一种设计模式是基于实体和组件的。实体组件设计将游戏场景中的对象组织成实体(Unity引擎中称为“游戏对象”,Unreal引擎中称为“角色”)和组件(我们可以添加或附加到实体上的数据)。
为了理解实体和组件如何一起工作,考虑一个简单的游戏场景。在这个例子中,实体将是我们的主要玩家,包括敌人、地板、投射物等。,而组件将是我们“附加”到实体上的重要数据块,比如位置、速度、刚体碰撞器等。
一种流行的游戏引擎设计模式是将游戏元素组织成实体和组件。
我们可以选择一些组件附加到实体上,如下所示:
位置组件:跟踪实体在世界坐标系中的x-y位置坐标(或3D中的x-y-z)。
速度分量:跟踪在x-y轴(或3D中的x-y-z轴)上移动的实体的速度。
向导组件:通常存储我们应该为特定实体呈现的PNG图像。
动画组件:跟踪实体的动画速度以及动画帧如何随时间变化。
碰撞器组件:这通常与刚体的物理特性有关,定义了实体的碰撞形状(包围盒、包围圆、网格碰撞器等。).
健康组件:存储实体的当前健康值。这通常只是一个数字,或者在某些情况下是一个百分比值(例如,健康进度条)。
脚本组件:有时候我们可以在一个实体上附加一个脚本组件,它可能是一个外部脚本文件(Lua,Python等。),我们的引擎必须在后台解释和执行该文件。
上面给出的是一种非常流行的表示游戏对象和重要游戏数据的方法。现在我们有了实体,我们可以将这些不同的组件“插入”到实体中。
目前市面上有很多书籍和文章讨论实体组件设计的实现方式,以及在这种实现中应该使用什么样的数据结构。我们使用的数据结构和访问它们的方式对我们的游戏性能有直接的影响。开发人员经常提到诸如面向数据的设计、实体组件系统(ECS)、数据局部性等概念。这些想法与我们的游戏数据在内存中的存储方式以及有效访问数据的方式密切相关。
在内存中表示和访问游戏对象可能是一个复杂的话题。根据我的经验,可以手工编写一个简单的实体组件来实现,也可以简单的使用现有的第三方ECS库(Entity-Component-System,“实体-组件-系统”的缩写)。该模型遵循组合优于继承的原则,游戏中的每个基本单元都是一个实体,每个实体都由一个或多个组件组成,每个组件只包含代表其特征的数据)。
目前,市场上有一些流行的现成ECS库。我们可以将它们包含在C++项目中,并开始创建实体和附加组件,而不用担心它们如何在后台实现。C++ ECS库的一些例子是EnTT和Flecs。
个人建议认真对待编程的同学,至少尝试手工实现一次非常简单的ECS。我的理由是,即使您的实现并不完美,从头开始编写ECS系统也会迫使您考虑底层数据结构和相应的性能。
一旦定制的临时ECS实现完成,我建议你只使用一些流行的第三方ECS库(EnTT,Flecs等。),因为这些都是经过业界多年开发测试的专业游戏开发库,可能比我们从零开始写的要好得多。
总之,一个专业的ECS很难靠自己的力量从零开始实现。选择一个经过良好测试的第三方ECS库,添加到游戏引擎代码中,完成你的游戏工作。
渲染
游戏引擎的复杂度在慢慢增加。之前已经讨论了游戏对象在内存中的存储和访问方法,接下来我们需要讨论如何在屏幕上渲染对象。
第一步是考虑用引擎创建的游戏的性质。我们创造的游戏引擎只用于开发2D游戏吗?如果是这样,我们需要考虑渲染精灵,纹理,管理层,也许用显卡加速。2D游戏通常比3D游戏简单,因为2D数学比3D数学简单得多。
如果目标是开发一个2D引擎,你可以使用SDL来帮助多平台渲染。SDL抽象了加速的GPU硬件,它可以解码和显示PNG图像,绘制向导并在我们的游戏窗口中渲染纹理。
如果目标是开发一个3D引擎,你需要定义如何发送一些额外的3D信息(顶点,纹理,着色器,等等。)到GPU。如果要抽象图形硬件和软件,最受欢迎的选项是OpenGL、Direct3D、Vulkan和Metal。当然,决定使用哪种API可能取决于您的目标平台。例如,Direct3D将支持微软平台上的应用程序,而Metal只适合与苹果产品配合使用。
3D应用程序通过图形管道处理3D数据。管道将指定引擎必须如何发送图形信息(顶点,纹理坐标,法线等。)到GPU。图形API和管道还将指定我们应该如何编写可编程着色器来转换和修改3D场景的顶点和像素。
可编程着色器指定GPU应该如何处理和显示3D对象。
每个顶点和每个像素(段)可以有不同的脚本,用来控制反射,平滑,颜色,透明度等等。
至于3D物体和顶点,最好把读取和解码不同网格格式的任务委托给一些第三方库。大多数第三方3D引擎应该熟悉许多流行的3D模型格式,例如一些文件格式,如OBJ、科拉达、FBX和DAE。我的建议是选择。首先是OBJ文件格式,因为有许多测试和支持良好的库可以用C++处理OBJ加载。在这方面,TinyOBJLoader和AssImp是很多游戏引擎的最佳选择。
7.物理引擎
当我们向引擎添加实体时,我们可能还希望它们在场景中移动、旋转和弹跳。
游戏引擎的这个子系统叫做物理模拟。这可以手动创建,也可以从现有的现成物理引擎导入。
在这里,我们还需要考虑我们想要模拟的物理类型。2D物理通常比3D简单,但物理模拟的基础部分与2D和3D引擎非常相似。
如果您只想在项目中包含一个物理库,有几个好的物理引擎可供选择。对于2D物理引擎,我建议调查Box2D和花栗鼠k2D产品。专业稳定的3D物理仿真引擎,品牌都挺好的,包括PhysX,Bullet等库。如果物理稳定性和开发速度对项目至关重要,那么使用第三方物理引擎总是一个不错的选择。
BOX2D是一个非常流行的物理库选项,可以和游戏引擎一起使用。
你不需要写一个完美的物理模拟,但是要确保物体可以正确加速,不同类型的力可以施加到你的游戏物体上。一旦实现了基本的物体运动效果,就可以继续考虑一些简单的碰撞检测和碰撞分析。
对于2D刚体物理,请参考Box2D源代码和来自Erin Catto的介绍。如果你想找一本关于游戏物理的综合教程,请参考《从头开始写2D游戏物理》。
如果想学习3D物理和物理仿真的实现,可以参考大卫·安吉尔·艾伯利写的《游戏物理》这本书。
8.用户界面设计
当提到Unity或Unreal等现代游戏引擎时,我们会想到复杂的用户界面,其中包含许多面板、滑块、拖放选项和其他漂亮的UI元素,可以帮助用户定制游戏场景。UI允许开发人员添加和删除实体,动态更改组件值,并轻松修改游戏变量。
为了清楚起见,我们讨论的是用于开发工具的游戏引擎UI,而不是我们向游戏用户显示的用户界面(如对话框屏幕和菜单)。
游戏引擎不一定需要嵌入到编辑器中,但因为游戏引擎通常用于提高生产力,友好的用户界面将帮助您和团队快速定制游戏场景的关卡和其他方面。
对于新手来说,从头开始开发UI框架可能是游戏引擎制作中最烦人的任务之一。您必须创建按钮、面板、对话框、滑块、单选按钮、管理颜色、正确处理UI事件并始终保持它们的状态。在引擎中加入UI工具会增加应用的复杂度,给源代码管理带来很多麻烦。
如果你的目标是为引擎创建UI工具,那么我建议使用现有的第三方UI库。流行的UI替代工具有Dear ImGui、Qt和Nuklear。
Imgui是一个强大的ui库,它被许多游戏引擎用作编辑工具。
Dearyimg UI是一个很好的选择,它允许我们快速设置引擎工具的用户界面。ImGui项目使用了一种称为“即时模式ui”的设计模式,这种模式被广泛用于游戏引擎,因为它通过加速的GPU渲染与3D应用程序进行良好的通信。
总之,如果想给游戏引擎添加UI工具,建议使用亲爱的ImGui。
9.脚本开发
随着我们游戏引擎的不断发展,一个常见的选择就是使用简单的脚本语言自定义游戏关卡。
想法很简单:我们在原生C++应用中嵌入一种脚本语言,非专业程序员可以使用这种更简单的脚本语言来编写实体行为、AI逻辑、动画和游戏其他重要方面的脚本。
一些流行的游戏脚本语言有Lua,Wren,C#,Python和JaScript。所有这些语言的运行水平都比我们的原生C++代码高得多。无论谁使用脚本语言编写游戏行为,都不需要担心内存管理或核心引擎如何工作的其他底层细节。他们所要做的就是编写游戏中对应关卡的脚本,我们的引擎知道如何解释脚本,在幕后执行高难度的任务。
Lua是一种快速的小型脚本语言,可以很容易地与C/C++项目集成。
我最喜欢的脚本语言是Lua。Lua小而快,非常容易与C和C++原生代码集成。另外,如果我用Lua和“现代”C++,我喜欢用一个名为Sol的打包库(https://github.com/ThePhD/sol2)。Sol库可以帮助人们快速熟悉和使用Lua,并提供了许多辅助功能来改进传统的Lua C-API。
如果我们开发的游戏引擎支持脚本编程,那么我们就可以开始讨论游戏引擎中一些更高级的话题了。脚本编程可以帮助定义人工智能逻辑,自定义动画帧和动作,以及其他可以由外部脚本轻松管理的游戏行为,而不受原生C++代码的控制。
10.声音的
接下来,游戏引擎需要添加的另一个支持元素是音频。
如果我们要读写音频数据,发出声音,就需要通过操作系统访问音频设备。还是那句话,因为人们通常不愿意编写特定于操作系统的代码,所以我建议使用多平台库来抽象音频硬件访问。
SDL等多平台库具有扩展功能,可以帮助引擎处理音乐和音效。
但是,我强烈建议您在确保引擎的其他部分能够正常工作后,再考虑处理音频。控制声音文件的发音可能很容易,但如果过早地开始处理音频同步的问题,即把音频和动画、事件等游戏元素联系起来,事情往往会变得一团糟。
如果真的自己手工写代码,多线程管理的复杂性可能会让音频处理变得非常困难。但是,如果你的目标是编写一个简单的游戏引擎,那么我可能会使用一个专门的库来处理这部分功能。
比如可以考虑将SDL_Mixer、SoLoud、FMOD等优秀的音频库和工具集成到自己的游戏引擎中。
微型格斗场
这个游戏使用FMOD声音库来实现音频效果,如多普勒和压缩。
11.人工智能
最后一个子系统是人工智能。我们可以通过脚本实现人工智能。这意味着我们可以把人工智能逻辑委托给关卡设计师写脚本。另一个选择是在我们游戏引擎内核的原生代码中嵌入一个合适的人工智能系统。
在游戏中,人工智能用于为游戏对象产生响应性、适应性或类似智能的行为。大多数人工智能逻辑被添加到非玩家角色(NPC,敌人)中,以模拟类似人类的智能。
敌人是AI在游戏中应用的一个流行例子。当敌人追逐地图上的物体时,游戏引擎可以通过路径搜索算法或有趣的模仿人类行为来创建抽象效果。
伊恩·米林顿的《游戏人工智能》是一本关于游戏人工智能理论和实现技术的综合性书籍,值得参考。
五、不要贪多求快
在游戏引擎的开发中,最难的一个环节就是大部分开发者没有设定明确的边界,也就是觉得自己达不到“终点线”。换句话说,程序员会启动一个游戏引擎项目,渲染对象,添加实体,添加组件,但最后突然发现一切都很可怕。所以,如果他们不定义某个边界,就很容易添加越来越多的功能,而忽略了全局。如果出现这种情况,游戏引擎很可能永远见不到曙光。
除了缺乏边界,当我们看到代码在我们面前以闪电般的速度增长时,我们自己也很容易被淹没。游戏引擎项目的复杂性可能会迅速扩大。几周后,你的C++项目可能会有几个依赖项,需要一个复杂的构建系统。随着更多的函数被添加到引擎中,代码的整体可读性将继续下降。
所以我的第一个建议是,在编写实际游戏的时候,要一直坚持编写自己的游戏引擎。当你开始并完成游戏的第一次迭代时,你的脑海中应该有一个真实的游戏。这将帮助你设定限制,并为你需要完成的工作指出一条清晰的道路。尽最大努力坚持下去,而不是反复改变需求。
六、稳扎稳打,专注基础
大多数学生在项目开始时都很兴奋,但随着时间的推移,他们开始感到焦虑。如果我们从零开始创建一个游戏引擎,尤其是在使用C++这样复杂的语言时,很容易失去动力。
所以要学会及时享受一些小胜利。比如学习如何在屏幕上成功显示PNG纹理,成功找到两个物体的碰撞等等。关注和理解基础知识总是非常重要的。
对于热爱编程的朋友,不管路有多难,都要坚持走下去!如果你想更好的提升自己的编程核心能力(内功),不妨现在就开始!
编程学习书籍分享:
编程学习视频共享:
整理分享(源代码,项目实战视频,项目笔记,基础入门教程)。
欢迎转行学习编程的伙伴,通过使用更多的素材,学会比自己更快的成长!
如果你对C/C++感兴趣,可以关注边肖,后台私信我:【编程通信】我们一起学习吧!可以获取一些C/C++项目学习视频资料!关键词自动回复已经设置好了,自动获取就好!
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。