決定什么時候使用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 * Prints a string to the log, and optionally, to the screen 3 * 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. 4 * 5 * @param InString The string to log out 6 * @param bPrintToScreen Whether or not to print the output to the screen 7 * @param bPrintToLog Whether or not to print the output to the log 8 * @param bPrintToConsole Whether or not to print the output to the console 9 * @param TextColor Whether or not to print the output to the console 10 */ 11 UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject", CallableWithoutWorldContext, Keywords = "log print", AdvancedDisplay = "2"), Category="Utilities|String") 12 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 UFUNCTION(BlueprintCallable, Category = "Example Nodes") 2 static void MultipleOutputs(int32& OutputInteger, FVector& OutputVector);
- 向一個已有函數添加新的參數是沒問題的,但是如果你想移除或者改變它們,那么你應該廢棄原有的函數並且添加一個新的函數。確保使用廢棄這個元數據,這樣關於使用新函數的信息就會顯示在藍圖里面了。
1 UFUNCTION(BlueprintCallable, Category="Collision", meta=(DeprecatedFunction, DeprecationMessage = "Use new CapsuleOverlapActors", WorldContext="WorldContextObject", AutoCreateRefTerm="ActorsToIgnore")) 2 static ENGINE_API bool CapsuleOverlapActors_DEPRECATED(UObject* WorldContextObject, const FVector CapsulePos, float Radius, float HalfHeight, EOverlapFilterOption Filter, UClass* ActorClassFilter, const TArray<AActor*>& ActorsToIgnore, TArray<class AActor*>& OutActors);
- 如果一個函數需要用枚舉當作參數,考慮使用'expand enum as execs'這個元數據,這樣可以讓這個節點更容易使用。
1 UFUNCTION(BlueprintCallable, Category = "DataTable", meta = (ExpandEnumAsExecs="OutResult", DataTablePin="CurveTable")) 2 static void EvaluateCurveTableRow(UCurveTable* CurveTable, FName RowName, float InXY, TEnumAsByte<EEvaluateCurveTableResult::Type>& OutResult, float& OutXY);
- 許多耗時的函數(例如移動到這里)應該是潛伏(latent)函數。
1 /** 2 * Perform a latent action with a delay. 3 * 4 * @param WorldContext World context. 5 * @param Duration length of delay. 6 * @param LatentInfo The latent action. 7 */ 8 UFUNCTION(BlueprintCallable, Category="Utilities|FlowControl", meta=(Latent, WorldContext="WorldContextObject", LatentInfo="LatentInfo", Duration="0.2")) 9 static void Delay(UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo );
- 如果可能的話,考慮把函數放入一個共享庫。這樣在多個類之間更方便使用,並且會少一個'target'的引腳。
1 class DOCUMENTATIONCODE_API UTestBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
- 記得盡可能地標記結點為純(pure)的,因為這樣會少產生一個需要連線的執行引腳。
1 /* Returns a uniformly distributed random number between 0 and Max - 1 */ 2 UFUNCTION(BlueprintPure, Category="Math|Random") 3 static int32 RandomInteger(int32 Max);
- 把一個函數標記了常量函數(const)也會避免讓這個藍圖結點產生執行引腳。
1 /** 2 * Get the actor-to-world transform. 3 * @return The transform that transforms from actor space to world space. 4 */ 5 UFUNCTION(BlueprintCallable, meta=(DisplayName = "GetActorTransform"), Category="Utilities|Transformation") 6 FTransform GetTransform() const;