【翻译】虚幻4蓝图答疑和提示(Blueprints FAQ and Tips)

Posted by SlothSimon's Skytree on July 31, 2017

译者言

Unreal4中用蓝图(Blueprint)全面替代了Kismet和UnrealScript,并且设计者完全可以使用蓝图完成游戏的所有部分而不依赖于C++,于是就有了疑问:什么时候使用C++?什么时候使用蓝图?因为二者看去都可以通向罗马。 基于这个理由,翻译了这篇文章分享给诸位同好。

Blueprints vs C++

Q: 为什么一个全部由蓝图完成的游戏在性能上比主要由C++完成的游戏差?是什么导致的?

A: 蓝图和C++的区别主要在于蓝图运行在“虚拟机”上。这个“虚拟机”并不是真的虚拟机,而是一个抽象概念,代表了它不会直接编译为机器集成代码(注1),而是编译为一个过渡形式,然后才会被翻译为任意运行所在机器的代码。例如,无论你在PC、Mac、Linux、PS4或其他平台,我们都使用同一份生成的蓝图代码。然后这份代码就被翻译为可以运行在每台设备上的原生机器代码。

这么做有几个好处:

  • 你不必有C++编译器
  • 每次更改后,你不必退出编辑器重载,而C++必须退出重载。
  • 你可以使脚本更能容忍错误。比如Access None'sArray Out-of-Bounds错误、死循环等错误可以被当做抛出的异常被捕捉并处理,避免程序崩溃。

在速度上,蓝图并不比UnrealScript慢。实际上,蓝图代码大约比C++慢10-15倍。毕竟好处不是免费的:)大多数情况下,这完全没问题,因为蓝图通常是处理事件的(比如某事发生了,现在我需要做出什么反应),而不是批量处理大量数据。蓝图调用原生C++函数,所以开销几乎完全是我们所谓的“虚拟机”开销(注2)。结点本身并不耗费性能,但是结点的调用却很慢。这就是为什么如果你每次循环要处理1000个东西,我们更可能在C++函数内部实现这个循环而不是在蓝图里。操作一次不费性能,但是操作1000次就非常耗费性能了。

Q: 有什么好例子可以说明游戏机制应该被写入代码而不是蓝图?

A: 我认为任何游戏机制都可以以任意一种方式实现!这完全取决于团队组成,团队里是程序员更多一点还是设计师更多一点。我们认为蓝图使游戏开发平易近人。

总之,没有一个游戏性系统是无法被蓝图实现的。我们一般推荐:

  • 如果你们程序员多,那么让你们的游戏程序员集中精力为你们的游戏特性搭建好的基础系统。比如,他们可以做一个C++原生基础武器类,然后处理诸如开枪、同步复制等,然后开放一些钩子(hooks)给设计师去扩展和修改(比如编辑伤害曲线、开枪时机等)。这种方式下程序员就相当于给设计师们做了一套“游戏API”。

-如果你们设计师多,那么任何东西先让设计师做原型,然后那些蓝图中实现很复杂(如结点太多)但是代码只要几行就能搞定的东西可以被移动到C++代码里,然后转变为一个简单的结点。这种方式使程序员更像优化者。正如我所说,没什么东西是无法在蓝图做的,就算有,它也可以被程序员简单几行代码实现。任何C++ UFunction都可以只用一个关键字开放给蓝图作为结点使用。

注释

注1:原文为

machine code(assembly)

注2:原文为

so the overhead is almost entirely in what we call VM overhead

关于蓝图的一般疑问

Q: 由于类型转换,是否用接口有明显的好处?或者蓝图间的交互依赖于类型转换更好?

A: 是的,当你想对许多不同种类的东西调用同一个函数,用接口很合适。但你别忘了让这些东西继承自同一个父类。

“use”系统是个不错的例子,玩家看到的任何东西都可以通过按按钮使用。这个接口应该有两个成员,CanBeUsedOnUse。玩家如果搜索发现互动范围内有一个物品,就可以通过物品的CanBeUsed接口信息知道是否他指向的东西可以被使用。如果可以,然后再调用OnUse

在这种方式下,任何蓝图可以实现这个接口,然后自己也会成为”Use”系统的一部分。敌人、茶壶、狗、任何东西。如果你准备用类型转换,你必须对每个你添加到Player的物品都做转换,那真是一团糟。

Q: 可以解释下Game ModeGame State的一般用法吗?各自负责什么?尤其在多人游戏中。还有Player ControllerPlayer State,也是相同的问题。通常的回答是“Controller应该处理输入”,但是在多人游戏里会引起问题。

A: Game Mode定义了游戏的规则逻辑,而Game State则表现地像个计分板。所以,如果你拿橄榄球举例,GameMode就会有很多函数,譬如ScoredTouchdown,用于处理开启一个胜利舞蹈、计算应该得多少分数、设置额外分的选择(注1),最后在GameState中写入6分。然后GameState被复制到所有客户端上,所以他们可以看到任何和GameState挂钩的数据。但是,所有的客户端不必明白计分规则,这就是为什么GameMode只会在服务器上的原因。

PlayerController也是类似的,因为它为玩家定义了规则,而PlayerState则记录了所有玩家需要知道的关于自身的信息(分数、名字等)。那些变量的设置、一系列输出在PlayerController中完成后,结果就会被传送给PlayerState

这么做的原因是复制(Replication)操作既昂贵又复杂,所以各种State就用于储存客户端和服务器都需要的信息,而诸如怎么设置、计算都放到服务器上通过PlayerControllerGameMode处理。

Q: 对程序员来说,当创建会被用于生成蓝图的代码类时,“最佳实现”是什么?

A:

  • 只开放蓝图用户需要的函数。如果你暴露地过多,会让菜单选项过多,很难找到你想要的东西。
  • 不要假设蓝图用户是程序员!简化你开放的事件、函数,使其参数尽可能少,只有最重要的信息会被传递。
  • 对函数和参数添加合适的注释,因为它们会成为工具提示。
  • 给你的蓝图用户做个API。在内部处理复杂的了逻辑、流程、状态变化和调用一些先决函数,然后把钩子开放给蓝图。用户不必担心要不要为了功能正确性而调用父函数。
  • 保持简单!让蓝图用户想做啥做啥,只有两种情况需要添加更多功能:a) 提高效率,b) 给他们新东西的权限。

Q: 蓝图和纯代码类(没有蓝图继承自该类)之间通讯的最好方法是什么?

A: 通常,我们通过UBluepeintFunctionLibraries来处理。拿UHeadMountedDisplayFunctionLibrary举例,它处理VR设备的通讯,也没有任何与之关联的蓝图。

注释

注1:原文为

set up the choice for going for the extra point

参考文献