蓝图编程指南(翻译)
决定什么时候使用C++或者蓝图有两个主要考虑的因素:
- 速度
- 表达式复杂度
除了这两个因素外,还有整个游戏的复杂程度和团队的组成。如果你的团队中有比程序员更多的美术人员,那么使用的蓝图可能会比C++代码要多很多。相反,如果团队中有很多的程序员,那么他们可能会更喜欢用C++来编写逻辑。而我们希望人们在中间取一个折中点。在英佩,一般流程是这样的,内容创作者会制作一个特别复杂的蓝图,然后程序员会查看这个蓝图并且思考如何把其中的很多工作转换成一个新的蓝图结点,这样就可以把一块的功能转换成C++代码。一个比较好的经验大规模地使用蓝图,然后当它们达到一定复杂度需要对一个功能进行精确描述时(或者当它们对于一个非程序员过于复杂时),或者执行速度指示要转换成C++代码时,把它们转换成C++代码。
速度
对于速度来说,蓝图要比C++慢得多。也并不是说性能就非常得差,但是如果你要的事情需要很多的计算量,或者以一个很大的频率调用时,那么你最好使用C++而不是蓝图。然而,两个组合使用来满足你团队和工程性能的需要也是可能的。如果你的一个蓝图有很多功能,那么你可以把其中的一部分放入C++代码中用来提升运行速度,但是保留其它的部分用来确保灵活性。如果性能分析结果显示蓝图中的某个操作比较耗时,那么你就需要考虑把它移植到C++中,然后把其它的留在蓝图中实现。
复杂度
对于表达式复杂度来说,有些东西在C++中做起来比在蓝图里面容易很多。蓝图在很多方面做的不错,但是有些东西用结点并不是那么容易表示。操作大量的数据,字符串操作,大规模数据的数学计算等都非常复杂,并且在可视化系统中并不容易看懂。这些东西最好放在C++中去实现而不是蓝图中,就是因为它们更容易查看并且容易理解到底要做什么。表达式复杂也是一个多人系统最好在C++中实现的一个原因。
例子
由于不同的功能适合实现的方式不同,有的在C++实现比较好,有的在蓝图实现比较好,这里有一些例子告诉C++程序员和蓝图创作者如何共同工作来制作一个游戏。
- 程序员可以在C++中创建一个Character类,它会定义一些自定义事件,然后蓝图可以用来扩展Character类,并且设置网格和设置一些默认值。示例可以查看ShooterGame示例工程中角色控制和敌人的实现。
- 一个能力系统的基类在C++中实现,但是设计者可以创建通过创建蓝图来做具体的事情。在StrategyGame示例中,在C++中定义了一个炮塔(turret)基类,但是火焰喷射器,加农炮等都是在蓝图里面定义的。
- 一个可拾取物,它的'Collect'和'Respawn'函数都是蓝图可实现的(BlueprintImplementableEvent)事件,它们可以用来让设计者来重写用来 生成不同的粒子发射器和声音。ShooterGame和StrategyGame里面都有这样创建可拾取物的示例。
创建一个蓝图API:技巧
当创建一个暴露给蓝图使用的API时有几个需要注意的点:
- 默认参数在蓝图里面得到了很好的支持
1 2 3 4 5 6 7 8 9 10 11 12 | /** * Prints a string to the log, and optionally, to the screen * If Print To Log is true, it will be visible in the Output Log window. Otherwise it will be logged only as 'Verbose', so it generally won't show up. * * @param InString The string to log out * @param bPrintToScreen Whether or not to print the output to the screen * @param bPrintToLog Whether or not to print the output to the log * @param bPrintToConsole Whether or not to print the output to the console * @param TextColor Whether or not to print the output to the console */ UFUNCTION(BlueprintCallable, meta=(WorldContext= "WorldContextObject" , CallableWithoutWorldContext, Keywords = "log print" , AdvancedDisplay = "2" ), Category= "Utilities|String" ) static void PrintString(UObject* WorldContextObject, const FString& InString = FString(TEXT( "Hello" )), bool bPrintToScreen = true , bool bPrintToLog = true , FLinearColor TextColor = FLinearColor(0.0,0.66,1.0)); |
- 尽量编写返回很多参数的函数而少用返回结构体的参数。这里有一小段代码来展示如何创建多个输出引脚。
1 2 | UFUNCTION(BlueprintCallable, Category = "Example Nodes" ) static void MultipleOutputs(int32& OutputInteger, FVector& OutputVector); |
- 向一个已有函数添加新的参数是没问题的,但是如果你想移除或者改变它们,那么你应该废弃原有的函数并且添加一个新的函数。确保使用废弃这个元数据,这样关于使用新函数的信息就会显示在蓝图里面了。
1 2 | UFUNCTION(BlueprintCallable, Category= "Collision" , meta=(DeprecatedFunction, DeprecationMessage = "Use new CapsuleOverlapActors" , WorldContext= "WorldContextObject" , AutoCreateRefTerm= "ActorsToIgnore" )) static ENGINE_API bool CapsuleOverlapActors_DEPRECATED(UObject* WorldContextObject, const FVector CapsulePos, float Radius, float HalfHeight, EOverlapFilterOption Filter, UClass* ActorClassFilter, const TArray class aactor*= "" >& OutActors); class > |
- 如果一个函数需要用枚举当作参数,考虑使用'expand enum as execs'这个元数据,这样可以让这个节点更容易使用。
1 2 | UFUNCTION(BlueprintCallable, Category = "DataTable" , meta = (ExpandEnumAsExecs= "OutResult" , DataTablePin= "CurveTable" )) static void EvaluateCurveTableRow(UCurveTable* CurveTable, FName RowName, float InXY, TEnumAsByte float & OutXY); |
- 许多耗时的函数(例如移动到这里)应该是潜伏(latent)函数。
1 2 3 4 5 6 7 8 9 | /** * Perform a latent action with a delay. * * @param WorldContext World context. * @param Duration length of delay. * @param LatentInfo The latent action. */ UFUNCTION(BlueprintCallable, Category= "Utilities|FlowControl" , meta=(Latent, WorldContext= "WorldContextObject" , LatentInfo= "LatentInfo" , Duration= "0.2" )) static void Delay(UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo ); |
- 如果可能的话,考虑把函数放入一个共享库。这样在多个类之间更方便使用,并且会少一个'target'的引脚。
1 | class DOCUMENTATIONCODE_API UTestBlueprintFunctionLibrary : public UBlueprintFunctionLibrary |
- 记得尽可能地标记结点为纯(pure)的,因为这样会少产生一个需要连线的执行引脚。
1 2 3 | /* Returns a uniformly distributed random number between 0 and Max - 1 */ UFUNCTION(BlueprintPure, Category= "Math|Random" ) static int32 RandomInteger(int32 Max); |
- 把一个函数标记了常函数(const)也会避免让这个蓝图结点产生执行引脚。
1 2 3 4 5 6 | /** * Get the actor-to-world transform. * @return The transform that transforms from actor space to world space. */ UFUNCTION(BlueprintCallable, meta=(DisplayName = "GetActorTransform" ), Category= "Utilities|Transformation" ) FTransform GetTransform() const ; |