平衡蓝图和C++

图片

在本文中,我们将探索如何打造符合最佳编码实践的项目,在哪种情况下将代码转为C++更有利。如何在遵循最佳实践的同时使用类,以及如何向设计师和美术师公开变量、函数和事件,以提高开发效率。



1.平衡蓝图和C++:概述


你将学习:
● 了解蓝图和C++的不同优势  
● 遵循最佳编程实践并创建一个的项目
● 了解何时将代码转换为C++更有利
● 编写类并遵循最佳编程规范  
● 向设计师和美术师暴露变量、函数、事件,以便他们在开发中使用。

什么时候适合用C++?什么时候适合用蓝图?两者同时使用时,应该注意些什么?

虚幻引擎的许多系统都基于蓝图和C++开发,所以在语言的选择上,你可能会比较迷茫。在本文中,我们将介绍虚幻引擎中的蓝图和C++,阐述其各自优势,以及如何让它们协同工作,让开发变得更轻松。

本文主要面向有一定行业和游戏开发经验的学习者。虽然不要求你精通虚幻引擎的方方面面,但熟悉引擎有助于你更好地融会贯通。

本文将用到Lyra初学者游戏包中的代码片段。学习本文无需你下载该项目。学完本文后,如果你想进一步探索这个项目,可以访问https://unrealengine.com/marketplace/zh-CN/product/lyra

比较蓝图和C++


学习如何选择用蓝图和C++来构建功能。

如果你刚接触虚幻引擎,可能会倾向于使用你最熟悉的编程语言来创建项目,而且从始至终只用一种语言。但是,如果你知道蓝图和C++各自的最佳使用时机,则能让你的整体开发更加均衡。我们先来简单了解一下虚幻引擎中的C++和蓝图。

蓝图


蓝图是虚幻引擎自创的一种节点化编程语言,它在使用上和C++不同。使用蓝图时,你需要连接各种变量、函数、事件,以此为Actor创建功能(如下图所示)。你还可以借助这些函数和事件,让不同的Actor蓝图相互通信、传输数据。 

图片

蓝图是基于节点的编程语言


蓝图并非完全与C++无关。蓝图在虚拟机层上运行,而虚拟机本质是一种将蓝图解译为C++的独立进程。这意味着,蓝图Actor可能需要占用更多的算力。在后文中,我们会探讨如何降低或消除这种额外开销。但现在,你只需知道蓝图是一种节点化的C++编程方法。

节点化编程有很多好处:可以快速构建代码原型,无需其他IDE,让你专注创意迭代而无需过早关注代码细节。在搭配使用蓝图和C++时,你可以将C++中的函数、事件或变量暴露给蓝图,以便非程序员也能调用C++中的功能。蓝图还能让你快速在项目中编写出更简洁、更轻量的代码,而无需掌握大量编程知识。这是你在选择蓝图和C++时需要了解的重要一点。

蓝图并不局限于创建Actor类——它还能用于创建Gamemode、Game State、Character、Pawn等各种类。蓝图的组件、宏、函数库等功能,还能让你轻松复用各种功能,实现更快、更灵活的设计。总之,蓝图本质上是一种可视化的C++代码,旨在让不熟悉代码的开发者也能编写代码。

图片
Lyra武器中的远程武器蓝图代码。

Lyra初学者游戏包(简称Lyra)中的游戏角色、弹药和其他“轻量级”交互功能都采用蓝图实现。在游戏中,这些功能不像武器或伤害系统需要访问核心代码,因此完全可以用蓝图来设计。下文会说到,C++代码可以暴露给蓝图。许多核心功能都能在蓝图中使用。蓝图不是虚幻引擎的一种封闭式编程语言,而是一种让非程序员编程的方法,团队中的所有人都能通过蓝图扩展C++代码。

C++


使用C++,你可以访问更底层、更广泛,更核心的虚幻功能。你可以修改引擎和编辑器的核心功能,以满足你的项目需求。如果你想修改引擎,改进管线,你需要用到C++!

访问源代码就意味着,你可以编写涉及大量计算的功能,或是快速功能,因为你可以访问更底层的功能。你可以创建游戏核心系统,或是需要大量计算或网络操作的系统。
 
图片
Lyra武器系统中的C++代码

Lyra这类多人FPS游戏有个重要特性,就是需要快速响应玩家输入。这种时候,C++的优势就显而易见了。由于是直接访问虚幻引擎的联网代码和内置玩法系统,性能和带宽开销可以大大降低,创造更好、更流畅的游戏体验。这就是Lyra中采用大量基于C++的网络代码的原因。

虽然游戏开发中,你可以只使用蓝图或C++来构建代码,但你会发现,兼用两者能让游戏功能整体更健壮。我们已经了解了蓝图和C++的各自优势,接下来,我们将深入了解这两种语言对项目结构的影响。


2.蓝图和C++的最佳实践


学习最佳实践,提高项目开发效率。

融合蓝图和C++是一种平衡的艺术。构建项目时,应该充分沿用语言中类、函数、逻辑的内在优势,同时又让团队成员充分发挥特长,创建强大功能。在本小节中,我们将探索项目构建时,需要考虑的决策问题。

图片

在编写代码前,你需要弄清楚哪些功能用C++编写,哪些用蓝图。如何设计游戏架构?为此,你要了解你的团队。团队技能在什么水平?这些问题要尽早了解清楚,以确保你践行最佳实践,避免重复工作。

团队分析


正如你的开发决策受制于游戏内容,决策也会受制于团队构成。根据团队成员的开发技能,某种语言通常会比另一种语言更适合,用起来也更顺手。下面是两种典型的团队结构。
 
图片

以设计师或美术师为主的团队


这种团队拥有较强的设计能力和美术创作能力,但编程能力相对较弱。他们可能更倾向于使用蓝图,而不是C++。如果你们团队也是这种情况,建议你先了解团队常用哪些开发工具,然后用C++编写这些工具,再把它们公开到蓝图中。程序员可以通过这种方式为美术师和设计师提供底层功能,同时确保他们不会访问到不需要的部分。

另外,游戏在结构设计上也应该尽量对美术师和设计师友好,便于他们使用蓝图。集中关注游戏中较为复杂、性能开销较大的方面,但也要确保设计师、美术师能够访问函数、工具来实现他们想要的功能。
 
图片

以程序员为主的团队


如果团队中的程序员比设计师(更确切地说是不会编写C++代码的成员)多,游戏架构又应该如何组织呢?

这种团队可能更倾向于使用C++,而不是蓝图。和前文中的案例一样,你要评估团队的需求。程序员应该把重心放在错综复杂的项目功能上,而不是用C++编写大量游戏逻辑。他们可以添加很多C++代码和功能,然后把变量公开给设计师。

设计师和美术师可以使用蓝图来迭代功能,之后再由程序员转换为C++。
你可能会想到第三种情况:独立开发者。如果是这种情况,擅长什么做什么。如果你懂C++,就用C++。如果你懂蓝图,就用蓝图。

Lyra示例


我们以Lyra中的一个武器数据资产为例,来阐释前面所讲的内容。这个资产主要用来控制一个Lyra武器的各种变量和信息。这个武器的大部分代码都是用C++编写的,目的是为了保证网络性能和游戏性能。武器是用C++编写的,但是在蓝图中公开了很多变量、常用功能和工具类函数,以便设计师使用(下文会讨论),如下所示。
 
图片
Lyra中步枪武器的各种功能和属性。

这么做有利于设计师对武器进行自定义,甚至利用公开的工具和变量创造新的武器。程序员只需要为武器编写一种实用的功能,设计师可以在这个基础上扩展。

其他注意事项和最佳实践


在开发项目之前,还要注意下列问题:

何时用C++编写函数和逻辑

有时候,你可能需要将某些类或函数隔离开来,禁止团队中的其他成员访问。原因有很多,比如不希望非程序员访问这些代码、保护重要代码等等。但是在哪些情况下一定要这么做?在哪些情况下应该编写只能由程序员修改的C++代码?有些游戏机制更适合用蓝图来编写,特别是在考虑到整体复杂性和运算量时。蓝图更适合编写一些临时或“转瞬即逝”的游戏功能。相反,持续在后台运行的永久功能最好用C++来编写。如果你的游戏包含很多小机制,这些机制只会短暂出现,那么请多用蓝图来实现它们。如果你的游戏包含需要进行大量计算和持续计算的功能和机制,则用C++来实现。

避免构建高开销的蓝图

正如上文提到的,蓝图的性能开销比C++大,因为它们在虚拟机上运行。如果一个蓝图包含大量代码,可能会对项目的性能造成很大压力。如果这类大开销蓝图频繁用到,建议用C++替代,或者将一部分功能转移到C++中,以加快速度。

谨慎使用枚举器和结构体

虽然蓝图支持枚举器(Enumerator)和结构体(Struct),但它们中的有一些如果是蓝图的一部分,则不能在C++中使用。不是所有枚举器和结构体都有这个限制,如果要在C++中使用,就要确保你想使用的枚举器和结构体在C++中可访问。
 
图片

小结


综上所述,在发挥蓝图和C++的最佳功效时,往往伴随着在两者之间找到平衡,而这种平衡会受到多种因素影响。游戏解构和团队结构都会影响你最终选择的语言类型。如果你的游戏包含很多持续运行的机制,或者你的团队更擅长编程,就应该考虑使用C++而不是蓝图。说到底,你应该充分考虑团队和游戏本身的侧重点,以此决定哪些内容用哪些工具开发。


3.使用蓝图和C++优化项目


学会判断什么时候将蓝图转换为C++,以提高项目性能。


前面我们探讨了平衡C++和蓝图的艺术,但是在某些情况下,更适合只用其中一种。在本节中,我们将通过不同的示例来了解,在哪些情况下一种语言比另一种语言更合适。

C++和高开销内容


正如之前所说,C++更适合用来构建游戏中长期需要的函数和工具。但我们没有具体解释原因。蓝图也可以构建同样的持续性功能,但可能会产生额外的性能开销,因为它们要在虚拟机上运行。如果游戏中长期大量存在这种蓝图,你可能要考虑将其中一部分转为C++。有很多方法,比如使用蓝图原生化功能(Blueprint Nativization)。
 
图片
蓝图代码通过虚拟机与C++通信

通过C++,你可以直接访问虚幻引擎的函数和工具类,这意味着你可以更灵活地控制对象,以及决定何时何地进行处理。直接访问是关键。除此之外,你可以用C++类来搭建蓝图的基类,然后用蓝图扩展这些基类,以满足性能目标。Lyra初学者游戏包中就是这么做的,先用C++创建基类,再用蓝图扩展它们的功能。

在Lyra中,很多功能是用C++编写的,因为对联网、持续活跃的功能来说,C++的性能表现更好。就像之前说的,对于需要快速响应枪械命中的游戏,最好直接访问引擎代码,以确保减少延迟,快速做出响应。

此外,涉及大量计算的功能,或者需要持久保存数据的功能(例如在会话间保存和加载数据),最好用C++。虚幻引擎提供了很多优化工具,这些现成工具能帮助你识别哪些功能需要大量算力,进而使用C++。
 
图片
内容浏览器中的Lyra步枪蓝图。

时使用蓝图?


现在你可能会想:为了获得更好的性能,是不是所有内容都应该用C++来编写?但实际并非如此!这还取决于游戏架构以及蓝图在该架构中的用法。Lyra的做法是用C++编写基类和许多底层功能,然后向蓝图公开一部分C++功能。以C++类为基类,创建蓝图基类,并把C++功能继承到蓝图中。这样,设计师可以创建蓝图的子类,根据需要进行扩展,创建他们想要的内容,但是背后仍然有C++功能作为支持,以确保优化性能。

你可以参考Lyra初学者游戏包中的武器和武器生成类,全都来自基于C++类创建的蓝图类,并且有相应的蓝图子类。

在这种方法中,派生类中适合添加轻量级功能;这些功能通常不会对性能产生很大影响。如果某个功能会产生巨大性能开销,它应该转成C++,然后公开给蓝图,供设计师使用。轻量级代码(例如在蓝图中调用C++函数、简单的定时器或设置整数)都无需用到C++。这些功能用蓝图实现更合适,因为它们通常由设计师和美术师掌控,而且是轻量级功能。
 
图片
玩家生成器的蓝图。

我们已经讨论过为什么蓝图的性能略逊于同等的C++类,原因是蓝图在虚拟机上运行。降低这种开销的一个简单方法是禁用蓝图。禁用蓝图的Tick事件,或者降低Tick频率,比如用Timeline或其他事件函数来控制蓝图的生命周期,从而减少性能开销。

最后,如果蓝图占用了大量计算带宽,可以考虑启用蓝图原生化。这个功能可以将蓝图转换为原生C++代码,小幅提升性能。请参阅虚幻引擎相关文档,了解更多内容。

小结


尽管我们会尽量兼用C++和蓝图,但有时候,只适合使用其中一种。接下来,我们将探讨项目核心类应该用哪种语言来构建,并解释原因。


4.根据类来选择使用蓝图或C++


根据对象的类类型来选择蓝图还是C++。

Actor该用什么语言编写?


游戏是由一系列常见的核心Actor和类组成的,比如游戏模式、游戏状态(或游戏实例)、Pawn/角色。这些都是游戏运行所必需的,设置这些Actor和类是每个团队都必须要做的工作。

你可能会想,某些Actor是否就一定适合只用某种固定类型的语言编写。对于核心Actor来说,答案是“对”,一种语言可能比另一种语言更有利。但归根到底,最终取决于团队架构和团队具备的技能。如果团队中程序员比较少,可能会倾向于使用蓝图。我们将以Lyra为例,研究FPS游戏中的核心Actor什么时候适合用蓝图,什么时候用C++。

案例分析


Game State - Lyra的游戏状态使用了C++,因为它负责处理游戏的状态/阶段逻辑。就像之前说的,需要进行大量计算,或涉及持久计算的游戏功能,最好使用C++实现。考虑到潜在的网络处理逻辑(包括客户端和服务器),使用蓝图会影响性能。
 
图片
游戏状态的C++代码。

Pawn/Character  - 观察玩家Pawn,你会发现游戏中的玩家是一个蓝图类的子类,而这个蓝图类继承自一个C++类。它是一个多层继承结构,但是遵循我们之前所讲的:使用C++编写底层功能,暴露给蓝图,再从蓝图派生子蓝图类,再用蓝图扩展功能。这样就可以充分利用两种语言各自的优势:C++的高效,蓝图的迭代和扩展。
 
图片
Lyra玩家Pawn的蓝图代码。

Game Mode - 和上文类似,Lyra的游戏模式也用C++编写。在服务器端,代码需要有足够强大健壮,才能跟上服务器的速度。如果要新建GameMode,你可以创建子类,这样就能保持性能,同时自定义你的体验。
 
图片
Lyra的游戏模式设置。

图中是Lyra的游戏模式设置。当然,如果你的游戏状态无需在服务器端复制,或者不需要处理游戏逻辑,你也可以用蓝图实现。虚幻引擎默认提供了Pawn和Character模板类,其中包含完整的蓝图功能,方便你在其基础上扩展。前面我们反复提到,具体选择应该取决于你的团队架构和团队掌握的技能。C++有C++的好处,但如果你不会C++,也可以用蓝图。Epic Games的VR游戏《Robo-Recall》(2017)就是主要用蓝图开发的!

小节


不同资产适合用不同语言实现,这不仅有利于项目开发,还有利于减轻你和团队的工作负担。不过,这只是改善团队合作的一个方面。接下来,我们将探讨如何进一步搭配蓝图和C++来助力团队协作。


5.将C++功能暴露给蓝图以便协作


将C++功能暴露给蓝图,方便团队合作。

团队中未必所有人都精通编程。即使人人都会,一些轻量级功能也不应该用C++代码实现。将项目的部分功能暴露给美术师、设计师以及其他团队成员,有助于加快开发迭代,方便团队发挥创意。在本节中,我们将进一步讨论这个话题,并将展示实际项目中的一些方法。

“公开”还是“不公开”


C++中的函数、变量、事件和数据都可以暴露给蓝图,供整个团队访问。将C++暴露给蓝图,就可以在蓝图中调用和/或使用C++函数、变量、事件和数据。这意味着,如果程序员编写了一个C++事件,比如玩家穿过门时会触发一个事件,那么我们就可以在蓝图中暴露并响应这个事件。美术师和设计师可以在蓝图中扩展该事件,然后创建他们想要的功能。
 
图片
在C++中将代码暴露给蓝图。

这是蓝图和C++最强大的特性之一,但在用前有几点要考虑清楚。首先,你的游戏架构要支持这种类型的功能暴露。如果当前使用的C++类包含重要信息,禁止公开,团队中的其他人就无法访问这些数据。

此外,游戏架构不仅要支持程序员,也要支持美术师和设计师的工作流程。如果美术师需要用变量来控制光源,那么就应该暴露这些变量。他们不需要访问这些变量的底层计算逻辑,只需要控制变量本身。这能为美术师和设计师带来更大的自由度和灵活性。在构建项目底层的时候,要认真考虑团队的需求,从长远角度来说也大有裨益。

你可以在Lyra武器中看到公开的变量。武器的主要功能——什么时候发射子弹、子弹击中目标后会发生什么等等——都用C++编写,并且是“隐藏”的。这样可以确保代码不会被非程序员意外修改,从而破坏开发。这些代码用C++编写的,然后再公开到蓝图中,以确保联网体验的流畅。

如果在虚幻引擎编辑器中打开Lyra的武器蓝图,你会发现设计师可以调整大量公开数据,例如弹药数量、弹匣大小、重生时间等等。设计师可以自行调整平衡性,设计弹夹容量时不需要程序员协助。这不仅节省了设计师的时间,也节省了程序员的时间。
 
图片
Lyra步枪实例中的属性。

在确定是否公开这个问题上,最简单的方法是问自己和团队:“设计师会不会调用这个函数?”或者“设计师需要调用哪些事件?”尽早、经常地考虑这些问题有利于C++底层构建,改善团队的工作流程。

对编程产生的益处


采用这种工作方式有一个额外好处。只公开特定的事件和函数,可以让设计师和美术师与程序员的工作解耦。核心代码可以“隐藏”起来,程序员可以在不频繁干扰其他团队成员的情况下工作。这可以大幅改善工作流程,尽可能避免干扰其他团队成员的工作。

小节


与设计师合作,了解他们的需求,编写出他们需要的复杂底层代码。然后,只公开他们需要的功能。打破只有程序员可以修改变量和函数的思维,这能帮助你走向成功,开发出更好的项目。

向美术师和设计师公开变量、函数、事件等,让他们能够根据自己的需求修改,不需要寻求程序员的帮助。这可以为他们带来更大的创作自由,加快开发速度。接下来,我们将回顾本文的所有内容。


6.平衡蓝图和C++:总结


我们谈到了项目中C++和蓝图的平衡的艺术。不要过度使用一种语言,因为单靠一种语言不足以应对所有情况。我们还介绍了用特定语言来创建特定的类能让你事半功倍,谈到了如何正确地构建和使用这些类和资产。我们还介绍了平衡C++和蓝图的好处,结合C++与蓝图可以为团队赋能,使开发流程更加顺利。

学完本文后,你可以试着用你掌握的C++和蓝图知识构建虚幻引擎项目了。

想学习更多技能吗?我们在Epic开发者社区为大家提供了很多学习资源,这些资源可以帮助大家继续学习!


图片