c++怎么做c++游戏编程

原标题:如何编写 C++ c++游戏编程引擎

朂近我在用 C++ 写c++游戏编程引擎再用这个引擎做了一个移动端小c++游戏编程跳一跳(Hop Out)。下面是截自我的 iPhone6 的一个小片段

跳一跳是我想玩的c++游戲编程类型:3D卡通外观的复古街机c++游戏编程。目标是改变每个填充块的颜色就像Q * Bert一样。

Hop Out仍在开发中但引擎的功能已经很完善了,所以峩想在这里分享一些关于引擎开发的技巧

你为什么想要写一个c++游戏编程引擎?可能有很多原因:

你是个修理工喜欢从头开始建立系统,直到系统完成

关于c++游戏编程开发你想了解更多。我在c++游戏编程行业工作了14年现在我仍然在不停的琢磨。我甚至不确定我是否可以从頭开始编写一个引擎因为它与大型工作室的编程工作的日常职责大不相同。我想知道答案

你喜欢控制。对完全按照你想要的方式组织玳码知道一切都在哪里,感到满意

你相信我们这个c++游戏编程产业应该试着去揭开引擎发展的序幕。我们并没有掌握制作c++游戏编程的艺術还离得很远!我们对这个过程的研究越多,改进的机会就越大

2017年的c++游戏编程平台 – 手机,c++游戏编程机和电脑 – 非常强大而且在很哆方面都非常相似。c++游戏编程引擎的开发并不是像过去一样在脆弱和怪异的硬件上挣扎。在我看来更多是关于自己制造出来的复杂性嘚斗争。创造一个怪物很容易!这就是为什么本文建议围绕着保持事情可控的原因我把它分成三部分:

  1. 请注意,序列化是一个很大的课題

这个建议适用于任何类型的c++游戏编程引擎我不会告诉你如何编写着色器,八叉树是什么或者如何添加物体。这些事儿都是我假设伱已经知道而且应该知道 – 这很大程度上取决于你想要制作的c++游戏编程类型。相反我故意选择了一些似乎没有被广泛承认或提及的观点 – 这些是我在试图揭开一个主题神秘面纱时最感兴趣的一些观点。

我的第一条建议是使一些东西(任何东西)快速运行起来,然后迭代

如果可能的话,从一个示例应用程序开始初始化设备并在屏幕上绘制一些东西。就我而言我下载了SDL,打开了Xcode-iOS / Test / TestiPhoneOS.xcodeproj然后在我的iPhone上运行了testgles2礻例。

瞧!我使用OpenGL ES 2.0生成了一个可爱的旋转立方体。

下一步是下载一个其他人制作的马里奥3D 模型。我写了一个快速和粗糙的OBJ文件加载器 – 文件格式并不太复杂 – 并且修改了例程来呈现Mario,而不是一个立方体我还集成了SDL_Image来帮助加载纹理。

然后我实现了一个双摇杆控制器用來操控马里奥(我本来想要创建的是一个双摇杆设计c++游戏编程并不是马里奥。)

接下来我想探索骨骼动画,所以我打开了Blender做了一个觸手模型,并且用一个前后摆动的双骨架来操纵它

此时,我放弃了OBJ文件格式编写了一个Python脚本来从Blender导出自定义的JSON文件。这些JSON文件描述了皮肤网格骨架和动画数据。在C ++ JSON库的帮助下将这些文件加载到c++游戏编程中

一旦这个完成,我回到了Blender并做了更详细的角色设计。 (这是峩创造的第一个被操纵的3D人我为他感到骄傲。)

在接下来的几个月里我采取了以下几个步骤:

  1. 开始将向量和矩阵函数分解成我自己的3D數学库。
  2. 开始将代码移动到单独的“引擎”和“c++游戏编程”库中随着时间的推移,我把它们分成更细粒度的库
  3. 写了一个单独的应用程序将我的JSON文件转换为c++游戏编程可以直接加载的二进制数据。
  4. 最终从iOS版本中删除所有SDL库 (Windows版本仍然使用SDL。)

重点是:在开始编程之前我沒有对引擎架构进行设计。这是一个经过深思熟虑的选择相反,我只是写了实现下一个特性的最简单的代码然后我会查看代码,看看會出现什么自然生成的架构我说的“引擎架构”是指组成c++游戏编程引擎的模块集,这些模块之间的依赖关系以及用于与每个模块交互嘚 API

这是一个迭代的方法因为它关注于较小的可交付成果。它在编写c++游戏编程引擎时效果非常好因为在每个步骤中,你都有一个正在運行的程序如果在将代码合成到新模块中时出现问题,可以随时将做的更改与以前工作的代码进行比较显然,我假设你在使用某种源玳码管理工具

你可能会认为这种方法浪费了很多时间,因为总是在编写糟糕的代码之后需要清理。但是大部分的清理操作都是将代码從一个.cpp文件移动到另一个将函数声明提取到.h文件中,或者直接进行简单的修改决定事情应该去哪是难点,但是这在已经有代码的时候會更容易决定

我认为用相反的方法:试图设计出一个能够提前完成所有需求的架构,会浪费更多的时间我最喜欢的两篇关于系统过度設计风险的文章是 Tomasz D?browski 的《泛化的恶性循环》 Joel Spolsky 的《不要让架构太空人吓到你》

我并不是说在用代码处理问题之前不应该在纸上进行设計。我也不是说你不应该事先决定你想要的功能比如,我从一开始就知道我想让我的引擎在后台线程中加载所有资源我只是没有尝试設计或实现该功能,直到我的引擎首先加载一些资源

迭代的方法给了我一个比我以前盯着一张白纸冥思苦想更优雅的架构。我的引擎的iOS蝂本现在是 100% 原始代码包括自定义数学库,容器模板反射/序列化系统,渲染框架物理模块和音频混合器。我可以编写每一个模块泹是你可能没有必要自己写所有这些东西。你可能会发现适合自己引擎的许多优秀的开源代码库

在整合事物太多之前要三思

作为程序员,我们尽量避免代码重复喜欢代码遵循统一的风格。不过我认为不要让这些本能凌驾于每一个决定之上。

偶尔要抵制一下 DRY 原则

举个例孓我的引擎包含了几个“智能指针”模板类,与 std :: shared_ptr 类似每一个指针作为一个原始指针的包装,有助于防止内存泄漏

  • <> 是用于具有单个所囿者的动态分配的对象。
  • Reference<> 使用引用计数来允许一个对象拥有多个所有者
  • audio :: AppOwned <> 被音频混音器以外的代码调用,允许c++游戏编程系统拥有音频混音器使用的对象例如当前播放的语音。

这样可能看起来像其中一些类复制了其它的功能违反 DRY(不要重复自己)的原则。事实上在开发早期,我尽可能地重用现有的Reference <>类但是,我发现音频对象的生命周期是由特殊规则来管理的:如果一个音频语音已经完成了一个样本的播放并且c++游戏编程没有指向该语音的指针,那么该语音会被立即到删除排队等待如果c++游戏编程持有指针,则不应删除这个语音对象如果c++游戏编程持有一个指针,但指针的所有者在语音结束之前被销毁这段语音应该被取消,而不是增加Reference <>的复杂性我决定引入单独的模板類,这样更为实用

95% 的时间都在重用现有的代码。但是如果你开始感到麻痹,或者发现自己增加了一件简单的事情的复杂性那就问洎己,代码库中的东西是否应该是两件事

可以使用不同的调用规则

我不喜欢Java的一件事是,它强迫你在一个类中定义每个函数在我看来,这是无稽之谈这可能会使你的代码看起来更加一致,但是它也鼓励过度工程并且不适合我前面描述的迭代方法。

在我的 C++ 引擎中一些函数属于类,有些则不属于类例如,c++游戏编程中的每个敌人都是一个类可能就像你预料的那样,大部分敌人的行为都是在这个类内蔀实现的另一方面,在我的引擎中投射的球体是通过调用 sphereCast() 函数来执行的这是物理命名空间中的一个函数。 sphereCast() 不属于任何类 – 它只是物理模块的一部分我构建了一个系统来管理模块之间的依赖关系,这使得我的代码组织得很好将这个函数包装在一个任意的类中不会以任哬有意义的方式改善代码的组织。

然后是动态调度这是一种多态的形式。我们经常需要为一个对象调用一个函数而不知道该对象的确切类型。 C ++程序员的第一本能是用虚函数定义抽象基类然后在派生类中重写这些函数。这是有效的但这只是一种技术。还有其他动态调喥技术不会引入额外的代码,或带来其他好处:

  • C ++ 11引入了std :: function这是存储回调函数的一个简便方法。也可以编写自己的std :: function版本这样在调试中不會那么痛苦。
  • 许多回调函数可以用一对指针来实现:一个函数指针和一个类型不确定的参数它只需要在回调函数中进行明确的转换。你茬纯C语言库中经常看到
  • 有时候,底层类型实际上是在编译时已知的你可以绑定这个函数调用而不用额外的运行开销。 Turf是我在c++游戏编程引擎中使用的一个库它非常依赖这种技术。例如看到turf:: Mutex,这只是针对特定平台类的定义
  • 有时,最直接的方法是自己构建和维护一个原始函數指针表我在我的音频混音器和序列化系统中使用了这种方法。Python解释器也大量使用这种技术如下所述。
  • 你甚至可以将函数指针存储在散列表中使用函数名称作为关键字。我使用这种技术来调度输入事件如多点触控事件。这是记录c++游戏编程输入并用重放系统回放的策畧的一部分

动态调度是一个很大的课题。我只是想表明有很多方法来实现它。你编写的可扩展底层代码越多(这在c++游戏编程引擎中很瑺见)越会发现替代方法越多。如果你不习惯这种编程C语言编写的Python解释器是一个很好的学习资源。它实现了一个强大的对象模型:每個PyObject都指向一个PyTypeObject每个PyTypeObject都包含一个用于动态分配的函数指针表。如果你想直接跳转到其中的话定义新类型的文档是一个很好的起点。

注意序列化是一个大问题

序列化是将运行时对象转换为字节序列的操作换句话说,就是保存和加载数据

对于许多c++游戏编程引擎来说,c++游戏編程内容以各种可编辑的格式创建例如.png,.json.blend或专有格式,然后最终转换为特定于平台的可以快速加载到引擎的c++游戏编程格式流水线中嘚最后一个应用通常被称为“炊具”。炊具可能被集成到另一个工具甚至分布在几台机器上。通常炊具和一些工具是与c++游戏编程引擎夲身一起开发和维护的。

在建立这样的流水线时每个阶段的文件格式的选择取决于你。你可以定义自己的一些文件格式这些格式可能會随着添加引擎功能而变化。渐渐地可能会发现有必要保持某些程序与以前保存的文件兼容不管什么格式,你最终都需要用C++来序列化它

用C ++实现序列化有无数种方法。一个相当明显的方式是将加载和保存函数添加到要序列化的C ++类可以通过在文件头中存储版本号来实现向後兼容,然后将这个数字传递给每个加载函数这是可行的,尽管这样代码可能维护起来比较繁琐

// 加载预期的成员变量

// 仅当正在加载的攵件版本是2或更大时才加载新的变量

通过反射(特别是通过创建描述C ++类型布局的运行时数据),可以编写更灵活不容易出错的序列化代碼。想要快速了解反射如何进行序列化请看一下开源项目Blender是如何实现的。

从源代码构建Blender时有许多步骤。首先编译并运行一个名为makesdna的洎定义实用程序。该实用程序解析Blender源代码树中的一组C语言头文件然后以SDNA的自定义格式输出所有C定义类型的汇总。这个SDNA数据作为反射数据链接到Blender本身,并保存在Blender写入的每个.blend文件中从这一刻开始,每当Blender加载一个.blend文件就会将.blend文件的SDNA与链接到当前版本的SDNA进行比较,并使用通鼡序列化代码来处理差异这个策略使Blender具有令人印象深刻的向前和向后兼容性。你仍然可以在最新版本的Blender中加载1.0版本的文件也可以在旧蝂本中加载新的.blend文件。

像Blender一样许多c++游戏编程引擎及其相关工具都会生成并使用自己的反射数据。有很多方法可以做到这一点:可以像Blender一樣解析自己的C / C ++源代码来提取类型信息你可以创建一个单独的数据描述语言,并编写一个工具来从该语言生成C ++类型定义和反射数据可以使用预处理器宏和C ++模板在运行时生成反射数据。一旦你有反射数据可用有无数的方法来编写一个通用的序列化器。

显然我省略了很多細节。在这篇文章中我只想表明有很多不同的方法来序列化数据,其中一些非常复杂程序员不会像其他引擎系统那样讨论序列化,尽管大多数其他系统依赖于它例如,在GDC 2017给出的96个程序设计讲座中我数了一下,共有31次关于图形11次关于在线,10次关于工具4次关于AI,3关於物理模块2关于音频的 – 但只有一个直接涉及到序列化

至少试着想一想你的需求会有多复杂。如果你正在制作一个像Flappy Bird这样的小c++游戏編程只有少数资源.,那么你可能不需要想太多的序列化你可以直接从PNG加载纹理,这样很好处理如果你需要一个向后兼容的紧凑的二進制格式,但不想自己开发可以看看第三方库,比如Cereal或者Boost.Serialization我不认为Google协议缓冲区是序列化c++游戏编程资产的理想选择,但是值得研究

编寫一个c++游戏编程引擎,即使是一个小c++游戏编程引擎也是一个很大的任务。关于这个我可以说的还有很多但是对于这个长度的帖子来说,这真的是我认为最有用的建议:迭代地工作抵制统一代码的冲动,并且知道序列化是一个大问题你需要选择一个合适的策略。根据峩的经验如果忽视这些事情,每一件事情都可能成为一个绊脚石

我喜欢比较这些东西,真的很想听到其他开发人员的意见如果你已經写了一个引擎,你的经验是否让你有什么相同的结论吗如果你没有写,或者只是在构思我也对你的想法也很感兴趣。你认为什么是恏的学习资源哪些部分对你来说看起来很神秘?你可以在下面评论或在Twitter上给我留言!

不关注我们那你会错过很多哦!


本期《一碳科技》为大家带来一篇关于“C++小c++游戏编程”的教程,此次教程是承接上一篇文章的上一篇文章讲的是“坦克大战”小c++游戏編程,但是只是给大家讲了绘制坦克图形的那一部分所以今天这篇文章将带大家继续深入学习如何制作一个小c++游戏编程。



此次教学把上┅次绘制坦克图形的方法重新设计了一遍具体实现方法可以先关注《一碳科技》,私信“绘制坦克图形源码”即可获取源码

在制作C++小c++遊戏编程的过程中,相信大家最多的疑问是关于“如何使坦克转向”的吧其实,实现这个功能并不难只要基础打得牢的小伙伴都可以實现。这里面主要设计两个函数一个是kbhit(),一个是getche()



kbhit()函数的功能是检测当前键盘是否按下,如果键盘被按下了它就会返回一个非零值,反之则返回一个0值。getche()函数用于获取立即获取当前输入控制台的字符这两个函数是怎么配合的呢?kbhit()是一个非都塞函数这是什么意思呢?就是说这个函数不论你的键盘有没有按下他都不会处于等待状态,例如scanf()函数如果你没有向控制台输入的话,它就会一直处于等待状態知道你的“回车”按下,才会继续执行下一段代码

if语句检测到kbhit()函数返回一个非零值之后,就会调用getche()函数获取当前输入控制台的字苻进而使用swicth()语句来判断方向。

要绘制我们的坦克就需要用到EasyX图形库,具体安装方法可以上网查一查有很多教程。

首先我们先用画圖工具画一个坦克出来,具体怎么画依个人爱好决定下面是小编画的一个坦克图形(有点丑啊)。



玩家坦克就是我们操控的坦克而我們还需要画几个“敌方坦克”,所以接下来我们在画一个坦克这个坦克要与玩家坦克区别开来,除此之外我们还需要画一个“炮弹”,小编画了一个正方形充当“炮弹

加载坦克图形并显示,需要用到了两个EasyX图形库的函数分别为loadimage()putimage()函数loadimage()将我们的图片加载到一个IMGE结構体里面而putimage()函数负责将IMAGE里面的图片显示到绘制区域中。

loadimage()可以接收5各参数,在这里小编只使用4个参数就可以了最后一个参数可以默认,第一个参数是IMAGE第二个是资源的地址,第三个是图片的宽第四个是图片的高。



putimage()接收三个参数,第一个是图片显示的X坐标第二个是圖片显示的Y坐标,第三方个参数是IMAGE

以下是坦克绘制的c++游戏编程界面(仅仅是坦克)

我们要如何使坦克移动起来呢,小编这里使用了while()循环每一次循环就是我们c++游戏编程画面的一帧,不断地再循环里面判断坦克移动方向然后通过对坦克图形绘制的X、Y坐标的改变,来使的坦克图形呈现出不断移动的效果

先关注《一碳科技》,然后私信关键字“绘制坦克图形源码”即可

本文仅代表作者个人观点,不代表SEO研究协会网官方发声对观点有疑义请先联系作者本人进行修改,若内容非法请联系平台管理员邮箱cxb5918@学习互联网营销技术请到巨推学院。

我要回帖

更多关于 c++游戏编程 的文章

 

随机推荐