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