篇写的是关于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同理。