篇寫的是關於UE4的C++方面的小技巧:
1.在構造函數里
//構建組件 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent")); Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera")); //把組件放到其它組件下 VRCamera->SetupAttachment(VROrigin); //下面這條不能用於構造函數中,否則編輯器崩潰或報錯 //VRCamera->AttachToComponent(VROrigin, FAttachmentTransformRules::SnapToTargetIncludingScale);
2.加載資源
具體細節教程(非本人制作):https://ke.qq.com/course/308721
//同步加載,一般用於少量物體加載 //從內存中讀取文件,但由於還沒從硬盤中讀取,所以內存中沒有(耗時相對較短),因此讀取失敗 UHapticFeedbackEffect_Base* ShakeEffect = FindObject<UHapticFeedbackEffect_Base>(NULL,TEXT("HapticFeedbackEffect_Curve'/Game/VirtualRealityBP/Blueprints/MotionControllerHaptics.MotionControllerHaptics'")); //從硬盤中讀取文件,放到內存中(耗時相對較長) UHapticFeedbackEffect_Base* ShakeEffect = LoadObject<UHapticFeedbackEffect_Base>(NULL,TEXT("HapticFeedbackEffect_Curve'/Game/VirtualRealityBP/Blueprints/MotionControllerHaptics.MotionControllerHaptics'"));
//讀取文件,只能用於構造函數
static ConstructorHelpers::FObjectFinder<UStaticMesh> Object(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));
Sphere->SetStaticMesh(Object.Object);
.h:
FStreamableManager* WealthLoader;
TSharedPtr<FStreamableHandle> WealthHandle;
//各種Class的地址
UPROPERTY(EditAnywhere)
TArray<TSoftClassPtr<UObject>> ClassWealthPaths;
.cpp:
//異步加載,一般用於大量物體加載 void AWelathActor::StreamableManagerOperate() { //創建加載管理器 WealthLoader = new FStreamableManager(); //執行異步加載,添加資源鏈接數組和加載完成回調函數,其中TexturePath為加載內容的地址 WealthHandle = WealthLoader->RequestAsyncLoad(TexturePath, FStreamableDelegate::CreateUObject(this, &AWelathActor::StreamableManagerLoadComplete));
//如果加載的內容是UClass,則需要先在藍圖那賦值要加載的UClass,然后再進行加載
//獲取所有資源路徑
TArray<FSoftObjectPath> ObjectWealthPaths;
for (int i = 0; i < ClassWealthPaths.Num(); ++i)
{
ObjectWealthPaths.Push(ClassWealthPaths[i].ToSoftObjectPath());
}
//進行異步加載
WealthHandle = WealthLoader.RequestAsyncLoad(ObjectWealthPaths,
FStreamableDelegate::CreateUObject(this, &AAsynClassActor::LoadWealthCompleted));
} void AWelathActor::StreamableManagerLoadComplete() { //加載完成后動態修改圖片 TArray<UObject* >OutObjects; WealthHandle->GetLoadedAssets(OutObjects); for (int32 i = 0; i < OutObjects.Num(); ++i) { UTexture2D* WorkTexture = Cast<UTexture2D>(OutObjects[i]); if (WorkTexture) { TextureGroup.Add(WorkTexture); } } }
//加載UClass
void AAsynClassActor::LoadWealthCompleted()
{
//獲取所有Class
TArray<UObject*> WealthObjects;
WealthHandle->GetLoadedAssets(WealthObjects);
for (int i = 0; i < WealthObjects.Num(); ++i)
{
//把Object轉為UClass
UClass* WeathClass = Cast<UClass>(WealthObjects[i]);
//生成AActor,由於這里的地址指向的都是AActor,故這里生成AActor.
AActor* Wealthactor = GetWorld()->SpawnActor<AActor>(WeathClass, FVector(0.f, 0.f, 1000.f)
, FQuat::Identity.Rotator());
//填充到數組
WealthActors.Push(Wealthactor);
}
}
3.通過UObjectLibrary獲取批量內容的地址
.h: class UObjectLibrary* ObjectLibrary; .cpp: void AWelathActor::ObjectLibraryOperate() { if (!ObjectLibrary) { ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, false); //添加到根那,防止被UE4的垃圾回收機制干掉 ObjectLibrary->AddToRoot(); } //搜索所有Texture的路徑 ObjectLibrary->LoadAssetDataFromPath(TEXT("/Game/Resource/UI/Texture/MenuTex")); TArray<FAssetData> TextureData; ObjectLibrary->GetAssetDataList(TextureData); for (int32 i=0; i<TextureData.Num(); ++i) { TexturePath.AddUnique(TextureData[i].ToSoftObjectPath()); } }
4.計時器
.h: FTimerHandle CountdownTimerHandle;
//如果委托事件有參數
void ShiningObject(AStaticMeshActor* Object); .cpp: //事件委托 FTimerDelegate UpdateTextureDele = FTimerDelegate::CreateUObject(this, &AWelathActor::UpdateTexture);
//如果事件有參數
FTimerDelegate UpdateTextureDele = FTimerDelegate::CreateUObject(this, &ABIMVRPawn::ShiningObject, Object); //每0.5秒執行一次事件委托 GetWorld()->GetTimerManager().SetTimer(CountdownTimerHandle, UpdateTextureDele, 0.5f, true); //停止運行定時器 GetWorldTimerManager().ClearTimer(CountdownTimerHandle);
5.隨機數
//產生隨機整數(范圍1~5) int AWelathActor::Rand5() { FRandomStream Stream; Stream.GenerateNewSeed(); //返回值優化 return Stream.RandRange(1, 5); }
6.UE4的智能指針
可參考:https://www.cnblogs.com/timy/p/8685953.html
官方文檔:https://docs.unrealengine.com/en-us/Programming/UnrealArchitecture/SmartPointerLibrary
7.C++與藍圖交互
參考視頻:https://ke.qq.com/course/308721
.h: //藍圖調用,C++實現 UFUNCTION(BlueprintCallable, Category = "FrameWork") void CAFuncOne(int32 Input, bool& Output); //只能在藍圖實現 UFUNCTION(BlueprintImplementableEvent, Category = "FrameWork") void CAFuncTwo(int32 Input, bool& Output); //藍圖和C++都可實現,C++實現需要加后綴_Implementation UFUNCTION(BlueprintNativeEvent, Category = "FrameWork") void CAFuncThree(int32 Input, bool& Output);
//變量在藍圖中可讀寫
UPROPERTY(BlueprintReadWrite, Category = "FrameWork")
int A;
.cpp: void AFWCharacter::CAFuncOne(int32 Input, bool& Output) { } void AFWCharacter::CAFuncThree_Implementation(int32 Input, bool& Output) { }
8. C++中的代碼注釋有中文時的特殊處理
如果不處理,可能會出現在藍圖中調用C++函數時,注釋亂碼的情況或者用C++寫了一個UE4模板,調用此模板時,c++的注釋亂碼的情況
處理方法之一:將文件保存為utf8格式。方法:https://blog.csdn.net/jiegemena/article/details/79369650
9.UE4的內存管理
UObject有一個垃圾回收系統來管理它們。而非UObject派生的(例如UStruct、UUserWidget等)則需要用智能指針來管理它們的生命周期。
垃圾回收系統詳解:https://wiki.unrealengine.com/Garbage_Collection_%26_Dynamic_Memory_Allocation
10.調用&修改參數
盡量用get()、set()來調用修改,直接改參數可能會失敗。(盡管參數是public的)
//修改參數成功 Cast<UStaticMeshComponent>(HighLightThis->GetRootComponent())->SetRenderCustomDepth(WantHighLight); //修改參數失敗 Cast<UStaticMeshComponent>(HighLightThis->GetRootComponent())->bRenderCustomDepth = WantHighLight;
11.打包可能遇到的問題&解決方法

解決:

12. Delay函數調用
.h: //一定要BlueprintCallable UFUNCTION(BlueprintCallable, Category = "RoomVR") void FinishMission(); .cpp: FLatentActionInfo Action; Action.CallbackTarget = this; Action.ExecutionFunction = "FinishMission"; Action.UUID = 123; Action.Linkage = 0; UKismetSystemLibrary::Delay(GetWorld(), 2.0f, Action);
13. 創建動態材質
//獲取材質 UMaterialInterface* HintMaterial = LoadObject<UMaterialInterface>(NULL, TEXT("Material'/Game/Material/SpecialBrick.SpecialBrick'")); //創建動態材質 UMaterialInstanceDynamic* HintMaterialDynamic = UMaterialInstanceDynamic::Create(HintMaterial, nullptr); //修改材質參數 HintMaterialDynamic->SetVectorParameterValue("Color", FLinearColor::Green);
14. 關於打印(Printstring)
//float轉Fstring
UKismetSystemLibrary::PrintString(this, "Value: " + FString::SanitizeFloat(1.23));
//bool轉FString
UKismetStringLibrary::Conv_BoolToString(true);
15. Timeline
.h: UPROPERTY() class UTimelineComponent* MyTimeLine; UPROPERTY() class UCurveFloat* FloatCurve; UFUNCTION() void TimelineCallback(float val); UFUNCTION() void TimelineFinishedCallback(); void PlayTimeline(); UPROPERTY() TEnumAsByte<ETimelineDirection::Type> TimelineDirection; .cpp: AYourClass::AYourClass() { static ConstructorHelpers::FObjectFinder<UCurveFloat> Curve(TEXT("/Game/Curves/C_MyCurve")); check(Curve.Succeeded()); FloatCurve = Curve.Object; } void AYourClass::BeginPlay() { FOnTimelineFloat onTimelineCallback; FOnTimelineEventStatic onTimelineFinishedCallback; Super::BeginPlay(); if (FloatCurve != NULL) { MyTimeLine = NewObject<UTimelineComponent>(this, FName("TimelineAnimation")); //Timeline是來自藍圖的 MyTimeLine->CreationMethod = EComponentCreationMethod::UserConstructionScript; //把它加到組件數組中,從而讓它得以保存 this->BlueprintCreatedComponents.Add(MyTimeLine); //可以作為引用(聯網用) MyTimeLine->SetNetAddressable(); //Set which object the timeline should drive properties on MyTimeLine->SetPropertySetObject(this); MyTimeLine->SetDirectionPropertyName(FName("TimelineDirection")); //Timeline不循環,長度為1秒 MyTimeLine->SetLooping(false); MyTimeLine->SetTimelineLength(1.0f); MyTimeLine->SetTimelineLengthMode(ETimelineLengthMode::TL_LastKeyFrame); MyTimeLine->SetPlaybackPosition(0.0f, false,false); //綁定Timeline運行時,執行的曲線和事件;綁定Timeline結束時的事件。 onTimelineCallback.BindUFunction(this, FName{ TEXT("TimelineCallback") }); onTimelineFinishedCallback.BindUFunction(this, FName{ TEXT("TimelineFinishedCallback") }); MyTimeLine->AddInterpFloat(FloatCurve, onTimelineCallback); MyTimeLine->SetTimelineFinishedFunc(onTimelineFinishedCallback); MyTimeLine->RegisterComponent(); MyTimeLine->Play(); } void AYourClass::Tick(float deltaTime) { Super::Tick(deltaTime); if (MyTimeline != NULL) { MyTimeline->TickComponent(deltaTime, ELevelTick::LEVELTICK_TimeOnly, NULL); } } void AYourClass::TimelineCallback(float interpolatedVal) { // This function is called for every tick in the timeline. } void AYourClass::TimelineFinishedCallback() { // This function is called when the timeline finishes playing. } void AYourClass::PlayTimeline() { if (MyTimeline != NULL) { MyTimeline->PlayFromStart(); } }
16. VS里不能啟動UE4時
出現問題如下圖:

解決辦法:

然后就可以了。
17. 關於LoadObject<UBlueprint>
打包后,應用讀取不了這個藍圖(在編輯器可以),故會出現問題。
解決辦法:
1. 將此藍圖本地化(可能可以,但由於本地化會觸發其它問題,導致打包失敗,故未測試)
2. 如果讀此藍圖是為了獲取它的class,從而動態地生成此藍圖的話,可以用TSubclassOf,然后在項目中賦值。
3. 使用FClassFinder:
.h: //控制器的類 TSubclassOf<ABIMVRController> BIMVRControllerBP; .cpp: static ConstructorHelpers::FClassFinder<ABIMVRController> BIMVRControllerBPFinder(TEXT("Blueprint'/Game/VRPlayer/BIMVRController.BIMVRController_C'")); BIMVRControllerBP = BIMVRControllerBPFinder.Class;
盡量不要用LoadObject<UBlueprint>。
18. UE4文件必須文件夾

當然,沒有代碼的話,Source文件夾不需要。Media文件夾只是做模板的時候需要,其它時候可以不需要。
19. 生成物體
//生成物體 AStaticMeshActor* CopyActor = GetWorld()->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass());
20. 修改Plugins
直接在UE4引擎上改是不行的。
正確做法是把plugins從原Plugin中復制一份,且放進工程里編譯。


21. 制作模板
如果需要制作自己的模板(UE4中直接調用),可參照https://blog.csdn.net/u014532636/article/details/72832926。
注意,如果模板里有自己寫的C++文件,注意這些C++文件名不要與模板名字雷同,如:

如果模板的名字也叫BimVR,那么調用模板時會觸發UE4埋下的陷井:UE4會修改同名的C++文件名字和內容,導致BUG出現。
總之小心命名就好。
22. 項目改名
把項目與VS關掉后,重命名此項目即可。改名后,把VS、Intermediate文件夾、Saved文件夾刪掉,然后右鍵項目重新生成VS文件。
C++里面不需要進行修改,經測試,改名后,未發現什么問題。
23. 在本地文件中寫數據
這段代碼可以生成word文檔,如果文檔絕對位置不存在目標文檔(FileName),則會創造一個該命名的word文檔。
如果有,則會覆蓋原文檔。
bool ABIMVRPawn::WriteFile(FString TestString, FString FileName) { if (FileName.IsEmpty()) { FileName = "MyWord"; UKismetSystemLibrary::PrintString(this, "FileName.IsEmpty"); } //文檔相對位置 FString Path = FString("Res/"); //文檔絕對位置 FString AbsoPath = FPaths::GameContentDir() + Path + FileName + ".doc"; //輸出pdf文件,文件會損壞,估計要按照特定的格式輸入數據 //FString AbsoPath = FPaths::GameContentDir() + Path + FileName + ".pdf"; if (!TestString.IsEmpty()) { if (FFileHelper::SaveStringToFile(TestString, *AbsoPath)) return true; else UKismetSystemLibrary::PrintString(this, "WriteFail: " + AbsoPath); } return false; }
24. 關於指針指向的東西是否有效
眾所周知,空指針會導致應用崩潰。所以,安全起見,使用指針前,先檢查它是否有效。如:
if(!MyGCProtectedObj) return;
但是,在UE4里,僅僅這樣是不夠的,某些情況下,還是會崩。
因為指針可能不是空的,但它指向的是未完全析構的UObject,此時使用此指針也會崩潰,故還要檢查指針指向的物體是否有效:
if(!MyGCProtectedObj) return; if(!MyGCProtectedObj->IsValidLowLevel()) return;
25. UE4的垃圾回收系統
參考官方的解釋:http://api.unrealengine.com/CHN/Programming/Introduction/index.html
首先,UE4的垃圾回收系統是基於反射系統來實現的。
然后,這個系統有個叫根集的概念,該根集基本上是一個對象列表,這些對象是回收程序知道將不會被垃圾回收的對象。
如何添加UObject到根集中?使用UPROPERTY,或者AddToRoot();
UPROPERTY() UObject* MyObject;
void CreateObject()
{
MyObject = NewObject<UObject>();
//手動添加到根集中
UObject* Object;
Object = NewObject<UObject>();
Object->AddToRoot();
}
上述代碼中,我們新建了個UObject,且實例化了。
當我們要刪除它時,我們可以把它變成空指針,然后它原本指向的東西會自動被垃圾回收系統檢測到,且刪除。
MyObject = nullptr;
或者手動刪除它:
MyObject->ConditionalBeginDestroy();
Actor通常不會被垃圾回收。因為Actor會自動成為根集的一部分。
故,我們要手動刪除它:
AActor* TestGCActor;
TestGCActor->Destroy();
注意,上述代碼只是展示了刪除方法,具體應用時,TestGCActor應該先生成出來!
調用Destroy()后,它們將不會被立即刪除,而是在下次垃圾回收時進行清理。
26. 生成物體
AStaticMeshActor* IntroducingActor = GetWorld()->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass());
27. 類命名前綴
-
派生自 Actor 的類帶有 A 前綴,如AController。
-
派生自 Object 的類帶有 U 前綴,如UComponent。
-
Enums 的前綴是 E,如EFortificationType。
-
Interface 的前綴通常是 I,如IAbilitySystemInterface。
-
Template 的前綴是 T,如TArray。
-
派生自 SWidget 的類(Slate UI)帶有前綴 S,如SButton。
-
其他類的前綴為字母F ,如FVector。
28. Widget模塊
如果想新建的類里包含Widget模塊,如:

或者新建一個繼承自UUserWidget的c++類時,
則需要在Build.cs文件中添加UMG模塊:

29. VS的代碼塊
VS編輯器的一個命令:#pragma region 。。。 #pragma endregion。
它可以把一堆代碼視為一個代碼塊,可以收縮或展開這段代碼。
如:
#pragma region Test void ..... void ..... void ..... #pragma endregion
這段代碼塊名字為Test。
30. BindKey
如果想用BindKey來把某個按鍵與事件綁定起來,如:
PlayerInputComponent->BindKey(EKeys::J, IE_Pressed, this, &ARPCCourseCharacter::KeyJEvent);
則需要在Build.cs文件中添加Slate模塊:

31. GetAllActorsOfClass
想獲取當前世界的某個類的所有物體時,可以用GetAllActorsOfClass:
TArray<AActor*> UIArray;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AIntroduceUI::StaticClass(), UIArray);
32. 對TMap進行For-Each
// TMap——迭代器返回鍵-值對 TMap<FName, AActor*> NameToActorMap = GetMapFromSomewhere(); for (auto& KVP :NameToActorMap) { FName Name = KVP.Key; AActor* Actor = KVP.Value; // ... }
請記住,auto 關鍵字不會自動指定指針/引用,您需要自行添加。
33. 調用OnBeginOverLap
在藍圖里是這樣的:

在C++里:
.h:
UFUNCTION()
virtual void CollisonEvent(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep,
const FHitResult & SweepResult);
.cpp:
//碰撞Delegate
FScriptDelegate CollisionDelegate;
CollisionDelegate.BindUFunction(this, "CollisonEvent");
//綁定開始觸碰事件
TouchArea->OnComponentBeginOverlap.Add(CollisionDelegate);
void ALiftingActor::CollisonEvent(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
if (OtherActor)
{
UKismetSystemLibrary::PrintString(this, "Result: " + OtherActor->GetName());
}
}
OnEndOverlap同理。
