UE4 & C++ 笔记

20 篇文章 13 订阅

个人笔记,记录一些之后可能会忘记来查的小点,因为时间跨度比较长,所以内容从浅到深都有,大的点会贴一些觉得比较好的文章,或者自己另外写一篇博客

VS

编辑器

注释:Ctrl+K,Ctrl+C
去注释:Ctrl+K,Ctrl+U
格式化代码:Ctrl+K,Ctrl+F
书签:Ctrl+K,Ctrl+K
上一个书签:Ctrl+K,Ctrl+P
下一个书签:Ctrl+K,Ctrl+N

代码区域划分(#region会自动补全)

#pragma region xxxx

#pragma endregion

全部折叠:Ctrl+M、Ctrl+M
全部打开:Ctrl+M、Ctrl+L
切换当前块的折叠:Ctrl+M、Ctrl+O

折叠当前块:双击这条线的任何地方
在这里插入图片描述
查找文件、符号、类型:Ctrl+, 然后输入f跳转文件,输入#跳转符号
切换.cpp和.h文件:Ctrl+K Ctrl+O
高级查找:Shfit+Ctrl+F
当前文档内查找函数:Alt+M
正则表达式替换:
用括号括出字符串,在替换项内可以使用参数的形式进行使用

Set(.{3,7})Value
UBTFunction::Set$1Value

上一个光标位置:Alt+左
下一个光标位置:Alt+右

配置光标前进后退:
在这里插入图片描述

运行调试

生成:Ctrl+B
取消生成:Ctrl+Pause(Break)
开始运行调试:F5
运行并停在Main函数:F11

断点:F9
继续运行:F5
调试下一步:F10
调试下一步(进去):F11
调试跳出:Shift+F11
附加到进程:Alt+Shfit+P
从进程脱离:Debug - Windows - Processes

调试技巧
在这里插入图片描述

  • 条件断点
    • 如果有去自己尝试的,会发现各种操作都不允许,说是有副作用,或者找不到运算符。

    • 使用成员变量而非函数,类型判断使用C++而非UE,例如:

      dynamic_cast<ANavigationData*>(Object) != nullptr
      
    • 获取UObject所在World:

      ((ULevel*)this->OuterPrivate)->OwningWorld
      
    • 从World获取UNavigationSystemV1的某个属性

      dynamic_cast<UNavigationSystemV1*>(UE::CoreUObject::Private::ResolveObjectHandle(NavigationSystem.ObjectPtr.Handle))->DefaultDirtyAreasController
      
    • 查看TSet的Num:

      S.Elements.Data.ArrayNum - S.Elements.NumFreeIndices
      
    • FString子串

      wcsstr((wchar_t*)MyString.Data.AllocatorInstance.Data,L"Search substring")
      
    • FName子串

      strstr(((FNameEntry&)GNameBlocksDebug[MyFName.DisplayIndex.Value>>FNameDebugVisualizer::OffsetBits]
      	[FNameDebugVisualizer::EntryStride*(MyFName.DisplayIndex.Value&FNameDebugVisualizer::OffsetMask)]).AnsiName
      	,"Search substring")
      

关闭代码优化,更好调试:

UE_DISABLE_OPTIMIZATION_SHIP

// 代码

UE_ENABLE_OPTIMIZATION_SHIP

在这里插入图片描述
在这里插入图片描述

  • 更改执行流:断点后,拖拽左侧的黄色箭头

  • 查看返回值:监视输入$returnvalue

  • 监视变量的修改:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    注意是当前对象的某个成员变化,所以不能用于nullptr

  • 调试占位的bool:先使用&取出地址,然后断点 - 新建 - 数据断点,输入地址,然后字节选择1

在这里插入图片描述
在这里插入图片描述

VSCode

https://blog.csdn.net/jk_chen_acmer/article/details/115001623

VA

快捷菜单:Shfit+Alt+Q
在这里插入图片描述

切换.h和.cpp:Alt+O
注释/去注释:/
查找定义:Alt+G
查找继承/重载链:Shfit+Alt+G
查找符号:Shfit+Alt+S
查找引用:Shfit+Alt+F
查找文件:Shfit+Alt+O

蓝图 / 编辑器

快捷键

技巧

拖动多个引脚:按住Ctrl键并拖动引脚

整理蓝图:
在这里插入图片描述

  • 资源标记为修改:

    UObject::Modify
    UObjectBaseUtility::MarkPackageDirty
    
  • 标记当前关卡为已修改:

    FScopedLevelDirtied	LevelDirtyCallback;
    LevelDirtyCallback.Request();
    
  • 设置 Actor 名字,Actor 所处文件夹

    FActorLabelUtilities::SetActorLabelUnique(Actor, TEXT("MyName"))
    ResultingBattleWall->SetFolderPath(TEXT("A/B"));
    
  • 设置 Actor 无法被拖动

    Actor->SetLockLocation(true);
    
  • 用代码修改蓝图配置:

    bp_cdo = bp_class->GetDefaultObject()
    ...
    EditorAssetLibrary::SaveLoadedAsset(bp_class)
    
  • UE编辑器脚本批量修改选定的资源

  • 编辑器内热更新代码:Ctrl+Alt+F11

  • 快速批量修改资源属性:

    • 多选资源后右键 - Asset Actions - Edit Selection in Property Matrix
    • 可以多选行后批量同时修改
    • 单机Pin图案可以选定想要查看的哪些属性
      在这里插入图片描述

关键字

  • Editor下变灰不可编辑

    UPROPERTY(EditDefaultsOnly, Meta = (EditCondition = "AreaType == ETriggerAreaMode::Circle"))
    FCircleTrigger CircleTrigger;
    

    可以添加EditConditionHides,在不可编辑的情况下,会隐藏该属性

    #if WITH_EDITOR
    // 可以自定义是否可以编辑
    virtual bool CanEditChange(const FProperty* InProperty) const override;
    
    if (!Super::CanEditChange(InProperty))
    {
    	return false;
    }
    //看这个类中是否有这个属性
    if(InProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UXXX, XXX))
    {
    	return bXXX;
    }
    return true;
    
    #endif
    
  • 可以让一个bool类型的变量,变成复选框,更详细的参考:Unreal虚幻引擎属性编辑条件

    UPROPERTY(EditDefaultsOnly, Category="Function | Basic", meta=(InlineEditConditionToggle="MaxTargets"))
    bool bLimitTarget = false;
    // Target count limitation
    UPROPERTY(EditDefaultsOnly, Category="Function | Basic", meta=(EditCondition="bLimitTarget"))
    int32 MaxTargets = 1;
    

    HideEditConditionToggle:隐藏内联复选框(多个会混乱,只保留一个即可)

  • AdvancedDisplay

  • meta = (ClampMin = 10.f, ClampMax = 150.f) 此处0.xf时前面的0不可省略,莫名其妙

  • DefaultToInstanced 和 Instanced

    • Instanced是指生成新的对象时,从CDO的该属性复制一份,而不是单纯的指针指向源地址
    • DefaultToInstanced表示该类型默认就是Instanced的,不用在用的地方再加Instanced
    • 有一个很好的用法:配合EditInlineNew,定义一个不确定类型的支持反射的配置项,Lyra示例:
      UCLASS(EditInlineNew, Abstract)
      class LYRAGAME_API ULyraAbilityCost : public UObject
      
      UCLASS(meta=(DisplayName="Inventory Item"))
      class ULyraAbilityCost_InventoryItem : public ULyraAbilityCost
      
      UPROPERTY(EditDefaultsOnly, Instanced, Category = Costs)
      TArray<TObjectPtr<ULyraAbilityCost>> AdditionalCosts;
      
      • 关于动态类型,可以看下这篇文章 Link,使用 FInstancedStruct,定义动态类型的结构体而不是 UObject

        // 注意基类的析构函数需要改为虚函数
        // 5.1打包后不能成功引用住Object,5.3官方代码有直接带Uobject的,应该不会有这个问题
        UPROPERTY(EditAnywhere, NoClear, Meta = (ExcludeBaseStruct, BaseStruct = "/Script/Chooser.ContextObjectTypeBase"), Category = "Input")
        TArray<FInstancedStruct> ContextData;
        
        STRUCT(DisplayName = "Asset")
        struct CHOOSER_API FAssetChooser : public FObjectChooserBase
        {
        	GENERATED_BODY()
        	
        	// FObjectChooserBase interface
        	virtual UObject* ChooseObject(FChooserEvaluationContext& Context) const final override;
        public: 
        	UPROPERTY(EditAnywhere, Category = "Parameters")
        	TObjectPtr<UObject> Asset;
        };
        
        // TInstancedStruct可以直接获取特定类型的指针
        UPROPERTY(EditAnywhere, Category = Foo)
        TArray<TInstancedStruct<FTestStructBase>> TestArray;
        FTestStruct Temp; // derived from FTestStructBase
        Temp.TurnRate = 15;
        XXX.InitializeAsScriptStruct(FTestStruct::StaticStruct(), (const uint8*)&Temp);
        
  • 类可蓝图化

    UCLASS(Blueprintable)
    
  • 让组件类可以派生蓝图类

    UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent),Blueprintable )
    

Actor

  • 对象创建

  • 开启Tick

    PrimaryActorTick.bCanEverTick = true;
    
  • 销毁时调用

    AActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
    
  • AActor延时销毁

    AActor->SetLifeSpan(3)
    
    FTimerHandle handle;
    GetWorldTimerManager().SetTimer(handle, [this, AActor]() {this->GetWorld()->DestroyActor(AActor); }, 3, 0);
    
  • 获取Actor位置/旋转

    GetActorLocation()/GetActorRotation()
    
  • 设置Actor位置和旋转

    SetActorLocationAndRotation(FVector, FVector)
    
  • 获取Actor方向向量

    this->GetActorForwardVector()
    
  • Actor朝镜头射线位置移动

    TraceLength = 2500.f;
    LerpSpeed_Location = 10.f;
    
    void AXXX::Tick(float DeltaSeconds)
    	APlayerCameraManager* CM = UGameplayStatics::GetPlayerController(GetWorld(), 0)->PlayerCameraManager;
    	FVector Location = CM->GetCameraLocation();
    	FVector Direction = CM->GetCameraRotation().RotateVector(FVector(1, 0, 0));
    	FCollisionQueryParams Params;
    	Params.AddIgnoredActor(this);
    	Params.bTraceComplex = false;
    	if (GetWorld()->LineTraceSingleByObjectType(Hit, Location, Location + TraceLength * Direction, FCollisionObjectQueryParams(ECC_WorldStatic), Params))
    		FVector TargetLocation = UKismetMathLibrary::VInterpTo(GetActorLocation(), Hit.Location, DeltaSeconds, LerpSpeed_Location);
    		SetActorLocation(TargetLocation);
    
  • 旋转线性变化

    RotateSpeed = 45.f;
    LerpSpeed_Rotation = 720.f;
    
    void AXXX::AddRotation(float Rotation)
    	AdditionalRotation += Rotation * RotateSpeed;
    	
    void AXXX::Tick(float DeltaSeconds)
    	float RValue = FMath::Sign(AdditionalRotation) * FMath::Min(DeltaSeconds * LerpSpeed_Rotation, FMath::Abs(AdditionalRotation));
    	AdditionalRotation -= RValue;
    	AddActorWorldRotation(FRotator(0, RValue, 0));
    
  • 获取存在时间

    GetGameTimeSinceCreation()
    
  • 按类型寻找Actor

    #include "EngineUtils.h"
    for (TActorIterator<AMyPlayerController> it(GetWorld(), AMyPlayerController::StaticClass()); it; ++it) {
    	if (it) {
    		(*it)
    	}
    }
    
    UGameplayStatics::GetAllActorsOfClassWithTag(GetWorld(), ACoreRPGCharacter::StaticClass(), TEXT("Player"), TArray<>);
    
  • 生成蓝图类(蓝图类路径后加_C

    UClass* BlueprintVar = StaticLoadClass(AShooter::StaticClass(), nullptr, TEXT("Blueprint'/Game/BP/BP_Shooter.BP_Shooter_C'"));
    AShooter* shooter = GetWorld()->SpawnActor<AShooter>(BlueprintVar);
    
  • 将Actor绑定到另外一个Actor

    shooter->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform);
    shooter->SetActorRelativeLocation(FVector(-60, -80, 120));
    
  • 在蓝图内选择类型(填写蓝图路径),创建某种类型的蓝图类实例

    .h
    	UPROPERTY(EditAnywhere)
    		TSubclassOf<AActor> Creation;
    	UPROPERTY(EditAnywhere)
    		FString Path;
    	AActor* Item;
    .cpp
    	const TCHAR* tmp = *Path;
    	UClass* BlueprintVar = StaticLoadClass(Creation, nullptr, tmp);
    	Item = GetWorld()->SpawnActor<AActor>(BlueprintVar);
    

Component

  • 构造函数内创建组件

    USceneComponent* P = CreateDefaultSubobject<USceneComponent>(TEXT("Name"));
    
    • 不是USenceComponent组件会自动绑定,就上面一行代码即可
    • USenceComponent组件设为Root
      • RootComponent = P;
    • 绑到其他USenceComponent下面(加到Socket下)
      • P->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
  • 设置组件的相对位置

    SetRelativeLocation(FVector)
    
  • 动态创建组件,有两个接口AActor::AddComponent和AActor::AddComponentByClass,都可以通过将bDeferredFinish改为false,进行属性的设定。

    • 两个接口都会将新建的组件存到BlueprintCreatedComponents内部,避免GC
      • UActorComponent::PostInitProperties内也有AddOwnedComponent的操作
    • bDeferredFinish时,需要手动调用FinishAddComponent,其实内部就是OnComponentCreated、RegisterComponent
  • 删除创建的组件:UActorComponent::DestroyComponent

  • 获取组件(获取某种特定类、Tag的组件)

    TArray<UParticleSystemComponent*>Jets;
    GetComponents(Jets ,true); // true表示获取子Actor的组件
    for (int32 i = 0; i < Jets.Num(); i++) {
    	if (!(Jets[i]->ComponentTags.Contains(TEXT("Jet")))) {
    		Jets.RemoveAt(i);
    		i--;
    	}
    }
    
  • 设置组件是否激活

    C->SetActive(true);
    
  • 开启Tick

    PrimaryComponentTick.bCanEverTick = true;
    
  • 开关粒子系统组件的某个发射器

    UParticleSystemComponent* Jet = Cast<UParticleSystemComponent>(GetComponentByClass(UParticleSystemComponent::StaticClass()));
    Jet->SetEmitterEnable(TEXT("Main"), true);
    
  • 胶囊体组件的真实半高

    UCapsuleComponent->GetScaledCapsuleHalfHeight()
    
  • 移除组件

    RemoveFromParent()
    
  • 删除组件

    Widget->UnregisterComponent();
    Widget->DestroyComponent();
    
  • 隐藏

    SetVisibility(false)
    SetHiddenInGame(true)
    
  • 关闭静态网格体碰撞

    FrameCubeMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("FrameCubeMesh"));
    FrameCubeMesh->SetGenerateOverlapEvents(false);
    FrameCubeMesh->SetCollisionProfileName(TEXT("NoCollision"));
    

Pawn/Character And Controller

Controller作为控制器,只需要执行控制部分的功能,即:不管你控制的是哪个角色,都需要的普适性功能

Pawn/Character则是实现Controller不适合实现的其他角色部分

  • 获取当前Pawn的控制器

    GetController()
    
  • 获取指定PlayerController

    #include "Kismet/GameplayStatics.h"
    UGameplayStatics::GetPlayerController(GetWorld(), 0)
    
  • 设置重生时间

    virtual void PostInitializeComponents()override;
    	
    float APlayerControllerInGame::GetMinRespawnDelay()
    {
    	AProjectWPlayerState* PS = GetPlayerState<AProjectWPlayerState>();
    	if (PS && PS->AS) {
    		return 3 + PS->AS->GetLevel();
    	}
    	return 1;
    }
    

AI

Decorator Task C++
UE4 行为树节点C++
AI 感知
AI 感知
UE4:AI‘s MoveTo——代码分析
UE4 HTN插件源码解析
HTN框架介绍、开发心得
UE4 行为树节点C++

从Engine\Source\Runtime\AIModule\Classes\BehaviorTree\Tasks\BTTask_WaitBlackboardTime.h开始了解会比较好

  • 初始化行为树和黑板

    void AMonsterAI::OnPossess(APawn* InPawn) {
    	Super::OnPossess(InPawn);
    	if (RunBehaviorTree(BTree)) {
    		UBehaviorTreeComponent* BT = Cast<UBehaviorTreeComponent>(GetComponentByClass(UBehaviorTreeComponent::StaticClass()));
    		UBlackboardComponent* BB = BT->GetBlackboardComponent();
    		BB->SetValueAsFloat(TEXT("PatrolRadius"), PatrolRadius);
    	}
    }
    
  • 设置黑板键

    	UBehaviorTreeComponent* BT = Cast<UBehaviorTreeComponent>(GetComponentByClass(UBehaviorTreeComponent::StaticClass()));
    	UBlackboardComponent* BB = BT->GetBlackboardComponent();
    	BB->SetValueAsObject("TargetActor", Player);
    
  • 修改寻路网格颜色
    在这里插入图片描述

  • 寻路的路径

    UNavigationSystemV1::FindPathToLocationSynchronously(&OwnerComp, RawStartLocation, RawGoalLocation);
    
    Path->PathPoints[I]
    

光照

  • 光照入门

  • 夜景

  • 环境内各个元素的作用:

    • DirectionalLight:提供平行光源
    • SkyAtmosphere:提供天空盒
    • VolumetricCloud:提供云
    • ExponentialHeightFog:提供远处(地面线下)的漫反射,不至于一坨黑
  • Ctrl+L+鼠标移动:修改 DirectionalLight 的光照方向

    • 与水平线夹角越小,光线越弱,配合天空大气和体积云,可以做出傍晚的感觉
  • PointLight、SpotLight可以修改 Source Radius 让阴影变得柔和,RectLight则是调整 Source Width 和 Source Height

  • 开启光追:Project Setting

    • Default RHI:DirectX 12
    • Dynamic Global Illumination Method:Lumen
    • Reflection Method:Lumen
    • Support Hardware Ray Tracing:True
    • 重启
  • 去除光照贴图:World Setting - Force No Precomputed Lighting:True,再Build Light Only

  • 关闭光照:Affects World:False

  • 环境配置工具:Windows - Env.Light Mixer

    • 可以一键添加 DirectionalLight、SkyAtmosphere、ExponentialHeightFog、VolumetricCloud
  • 材质的Basic Color中的V值,是颜色自身的光线反射率,也就是Max(R,G,B)

    • 在使用Lumen时,V值越高,反射的光线越多,光线射进房间但是其它地方太暗可能就是材质反射率太低
    • 反射率应该在0.04-0.85之间,中间应该是0.18
      在这里插入图片描述
  • 材质快捷键

    • 实数、二元组、三元组(RGB)、四元组(RGBA):1234+鼠标左键
    • 加:A+鼠标左键
    • 乘:M+鼠标左键
    • 除:D+鼠标左键
    • 材质节点:T+鼠标左键
    • 标准化:N+鼠标左键
    • 新建实数参数:S+鼠标左键
    • 新建四元组参数:V+鼠标左键
    • IF:I+鼠标左键
  • 可以通过颜色*实数(光照强度)连接emissive,实现自发光,但推荐仅用于点缀

  • 柔和光源造成的影子:Cast Ray Traced Shadows:Enabled,如果有噪点,将Samples Per Pixel改为4

  • SkyLight:

    • Source Type改为SLS_SpecifiedCubemap后,选择一个Cubemap,就可以使用HDRI的光照
    • Lower Hemisphere Is Black:来自下方的光的颜色是否变为 Lower Hemisphere Color,下图是开启后红色的效果和关闭的效果
      在这里插入图片描述
      在这里插入图片描述
  • 禁用自动曝光:

    • Metering Mode:AEM_Manual
    • Apply Physical Camera Exposure:False
    • 如果比较暗,可以调高整体曝光Exposure Compensation
  • 去除房间内蓝光:

    • ExponentialHeightFog 的 Volumetric Fog 改为 True
  • 增加光的体积感:调高DirectionalLight 的 Volumetric Scattering Intensity可以做到全局的,局部使用其它光源也可以

  • 配置只有走到光方向的位置(窗口)才能看到体积光(不调也可以看需求):调高ExponentialHeightFog的Scattering Distribution

  • 解决Lumen下有一些时隐时现 / 抖动的地方:

    • 后处理体积搜索Lumen:Lumen Scene Lighting Quality改为2,Final Gather Quality改为2,Final Gather Lighting Update Speed改为0.5
    • 或者增加局部的光源,因为这些地方没有直射光只有反射光所以不稳定
  • 星空

    • Engine Content内搜索BP_Sky_Sphere拖入,修改Sun Height为-1即可
      • Cloud Speed:10
      • Cloud Opacity:1
      • Stars Brightness:0.4

数学

  • fabs、PI、FMath::Min

  • 夹角

    float AAIMastermind::GetAngle(FVector A, FVector B)
    {
    	A.Normalize();
    	B.Normalize();
    	return FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(A,B)));
    }
    
  • 两个点的方向Rotator

    UKismetMathLibrary::FindLookAtRotation(FVector, FVector)
    
    FRotator RotationOnEnd = (RawTargetLocation - RawLocationOnStart).Rotation()
    
  • 获取当前旋转体的X方向

    FVector di = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
    
  • 四元数操作

    FQuat OriginalQuat = Temp2->GetComponentQuat();
    FQuat DeltaQuat = FQuat(FVector::RightVector, TurnY/60);
    FQuat DeltaQuat2 = FQuat(FVector::ForwardVector, TurnX / 60);
    FQuat DeltaQuat3 = FQuat(FVector::UpVector, TurnZ / 60);
    FQuat FinalQuat = OriginalQuat * DeltaQuat * DeltaQuat2 * DeltaQuat3;
    Temp2->SetWorldRotation(FinalQuat)
    

Other API

  • 静态加载(构造函数内使用,所以路径只能写死)

    其实ConstructorHelpers内部调用的也是StaticLoad,只不过是将ConstructorHelpers结构体定义成Static,在CDO的时候进行加载后,之后在异步加载对象的时候,就直接使用之前在主线程加载好的对象即可。(StaticLoad不允许在异步线程调用)

    • 资源(UObject对象)

      // 只能在构造函数内使用
      static ConstructorHelpers::FObjectFinder<UTexture> CubeVisualAsset(TEXT("/Script/Engine.Texture2D'/Game/ProjectXX/.../ObjectName.ObjectName'"));
      // Game/ProjectXX/.../ObjectName 也可以
      if (CubeVisualAsset.Succeeded()) CubeVisualAsset.Object;
      
    • // 只能在构造函数内使用
      static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"));
      if (PlayerPawnBPClass.Class != NULL)
      {
      	DefaultPawnClass = PlayerPawnBPClass.Class;
      }
      
  • 动态加载

    • 资源(UObject对象)

      if (UTexture* TextureFound = LoadObject<UTexture>(this, TEXT("Game/ProjectXX/.../ObjectName")))
      
    • UClass* AssetClass = LoadClass<UObject>(nullptr, TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"));
      
  • 设置静态网格体

    SetStaticMesh(UStaticMesh)
    
  • Pawn旋转应用到摄像机

    camera->bUsePawnControlRotation = true;
    
  • 绑定轴

    PlayerInputComponent->BindAxis("Forward", this, &AMe::MoveForward);
    
  • 绑定事件

    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMe::StartJump);
    PlayerInputComponent->BindAction("Jump", IE_Released, this, &AMe::EndJump);
    
  • 是否尝试跳跃

    bPressedJump
    
  • 射线检测

    //DrawDebugLine(GetWorld(), Target, Target + FVector(0, 0, -1000), FColor::Red, true, 5);
    
    FCollisionQueryParams Param(SCENE_QUERY_STAT(YourTag), false, nullptr);
    FHitResult Hit;
    bool bSuccess = GetWorld()->LineTraceSingleByObjectType(Hit, Target, Target + FVector(0, 0, -1000), ECC_WorldStatic, Param);
    if (bSuccess && Hit.Actor != nullptr && Hit.Actor.Get() != nullptr) {
    	GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Yellow, Hit.GetActor()->GetFName().ToString());
    	// 销毁射线检测到的第一个Actor
    	GetWorld()->DestroyActor(Hit.GetActor());
    }
    
  • 胶囊体检测

    FHitResult Temp;
    FVector StartP, EndP;
    TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
    if (UKismetSystemLibrary::CapsuleTraceSingleForObjects(this, StartP, EndP, CapsuleRadius, CapsuleHalfHeight
    	, { UEngineTypes::ConvertToObjectType(ECC_WorldStatic) }
    	, false, { IgnoreActor }, bDrawDebug ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None, Temp, true))
    
  • 画出锥形

    UKismetSystemLibrary::DrawDebugConeInDegrees(Me->GetWorld(), Me->GetActorLocation(), Me->GetActorForwardVector(), SightRadius,
    			SightAngle, SightAngle, 24, FColor(255, 176, 223), 0, 10);
    
  • 画出水平的圆

    UKismetSystemLibrary::DrawDebugCircle(MeshComp, Center, Radius, 36, FLinearColor::Green, Duration, 0, FVector(0.f, 1.f, 0.f), FVector(1.f, 0.f, 0.f), false);
    
  • 各种追踪检测

    #include "Kismet/KismetSystemLibrary.h"
    
    TArray<FHitResult> HitResult;
    TArray<AActor*> IgnoreResult;
    IgnoreResult.Add(this);
    FVector Start = this->GetActorLocation();
    FVector End = this->GetActorLocation() + this->GetActorForwardVector() * 100;
    // 需要进行去重
    UKismetSystemLibrary::SphereTraceMulti(this->GetWorld(), Start, End, 100, 
    	(ETraceTypeQuery)3, true, IgnoreResult, EDrawDebugTrace::ForDuration, HitResult, true, 
    	FLinearColor::Black, FLinearColor::Blue, 60);
    	TSet<AActor*>Set;
    	for (FHitResult& Hit : HitResult) {
    		if(Hit.Actor == nullptr || Hit.Actor.Get() == nullptr)return;
    		ARootCharacter* Role_ = Cast<ARootCharacter>(Hit.Actor.Get());
    		if (Role_) {
    			UE_LOG(LogTemp, Log, TEXT("Projectile Explode to Actor"));
    			if(Set.Contains(Role_))continue;
    			Set.Add(Role_);
    
  • 延时

    #include "TimerManager.h"
    FTimerHandle handle;
    GetWorldTimerManager().SetTimer(handle, []() {}, 3, 0);
    
  • 循环操作

    void ABlockManager::KeyRightStart()
    {
    	GetWorldTimerManager().SetTimer(RightHandle, [this]() {
    		...
    	}, 间隔, 1);
    }
    
    void ABlockManager::KeyRightEnd()
    {
    	GetWorldTimerManager().ClearTimer(RightHandle);
    }
    
    
  • 设置Mesh的材质参数

    Mesh->SetScalarParameterValueOnMaterials("DissolveValue", ToDissolve);
    
  • 设置材质

    int32 matsNum = staticMeshComponent->GetNumMaterials();
    if (matsNum == 1)
    {
    	staticMeshComponent->SetMaterial(0, InMaterial);
    	return true;
    }
    
  • 渐变类

    // By JkChen
    #include "RandomChanger.h"
    
    ARandomChanger::ARandomChanger()
    {
    	PrimaryActorTick.bCanEverTick = true;
    }
    
    void ARandomChanger::BeginPlay()
    {
    	Super::BeginPlay();
    	Enabled = false;
    }
    
    // 效果:在[Min,Max]中随机变化,变化较为柔和
    // 实现:在Min到Max内随机一个作为下一个目标点,TurnCycle时间一个周期
    void ARandomChanger::StartChanger(float StartPoint, float _TurnCycle, float _Min, float _Max) {
    	Enabled = true;
    	Pre = Value = StartPoint;
    	Min = _Min, Max = _Max;
    	NowTime = 0;
    	TurnCycle = _TurnCycle;
    	Nex = FMath::FRandRange(Min, Max);
    }
    
    void ARandomChanger::Tick(float DeltaTime)
    {
    	Super::Tick(DeltaTime);
    	if (Enabled) {
    		NowTime += DeltaTime;
    		Value = (TurnCycle - NowTime) * Pre * (1.0 / TurnCycle) + NowTime * Nex * (1.0 / TurnCycle);
    		if (NowTime > TurnCycle) {
    			NowTime = 0;
    			Pre = Value;
    			Nex = FMath::FRandRange(Min, Max);
    		}
    	}
    }
    
    void ARandomChanger::EnableChanger()
    {
    	Enabled = true;
    }
    
    void ARandomChanger::DisableChanger()
    {
    	Enabled = false;
    }
    
    
    
  • 获取相机位置和朝向向量(命中屏幕中心)

    auto cm = UGameplayStatics::GetPlayerController(GetWorld(), 0)->PlayerCameraManager;
    FVector location = cm->GetCameraLocation();
    FVector direction = cm->GetCameraRotation().RotateVector(FVector(1, 0, 0));
    // 射线为location, location + 1000 * direction
    
  • 子类类型(用于避免互相include)

    TSubclassOf<AActor> Creation
    
  • 通过绑定弹簧臂实现与人物旋转脱离

    CameraBoom->bUsePawnControlRotation = true;
    
    USpringArmComponent* sp = Cast<USpringArmComponent>(GetComponentByClass(USpringArmComponent::StaticClass()));
    if (sp) {
    	shooter->AttachToComponent(sp, FAttachmentTransformRules::KeepRelativeTransform);
    	shooter->SetActorRelativeLocation(FVector(450, 0, 100));
    }
    
  • 角色按键绑定委托

    DECLARE_DELEGATE_OneParam(FDelegateInt, int);
    PlayerInputComponent->BindAction<FDelegateInt, ACastleCharacter, int>("Skill0", IE_Pressed, this, &ACastleCharacter::UseSkill, 0);
    
    //void UseSkill(int idx);
    
    • 原型

      template< class DelegateType, class UserClass, typename... VarTypes >
      FInputActionBinding& BindAction( const FName ActionName, const EInputEvent KeyEvent, UserClass* Object, typename DelegateType::template TUObjectMethodDelegate< UserClass >::FMethodPtr Func, VarTypes... Vars )
      {
      	FInputActionBinding AB( ActionName, KeyEvent );
      	AB.ActionDelegate.BindDelegate<DelegateType>(Object, Func, Vars...);
      	return AddActionBinding(MoveTemp(AB));
      }
      
  • 飞行模式

    void ACastleCharacter::ToggleFly() {
    	// 状态维护
    	UCharacterMovementComponent* CM = GetCharacterMovement();
    	CM->SetMovementMode(MOVE_Flying);
    }
    
    void ACastleCharacter::ToggleWalk() {
    	UCharacterMovementComponent* CM = GetCharacterMovement();
    	CM->SetMovementMode(MOVE_Falling);
    }
    void ACastleCharacter::Jump()
    {
    	bPressedJump = true;
    	JumpKeyHoldTime = 0.0f;
    
    	UCharacterMovementComponent* CM = GetCharacterMovement();
    	if (CM->MovementMode == EMovementMode::MOVE_Flying) {
    		// 飞行按下掉落
    		ToggleWalk();
    	}
    	else if (CM->IsFalling()) {
    		// 空中按下飞行
    		ToggleFly();
    	}
    }
    
    void ACastleCharacter::StopJumping()
    {
    	bPressedJump = false;
    	ResetJumpState();
    }
    
  • 动态读取动态(先加载一个挂载UParticleSystemComponent的空Actor(UParticleSystemComponent的Template不能为空,随便设置一个就行),设置UParticleSystemComponent的粒子系统Template,最后ResetNextTick)

    UClass* BlueprintVar = StaticLoadClass(AActor::StaticClass(), nullptr, TEXT("Blueprint'/Game/Effect/AdvancedMagicFX14/Particles/Mine/BP_ParticleBase.BP_ParticleBase_C'"));
    AActor* ParticleBase = GetWorld()->SpawnActor<AActor>(BlueprintVar);
    	UParticleSystemComponent* sc = Cast<UParticleSystemComponent>(ParticleBase->GetComponentByClass(UParticleSystemComponent::StaticClass()));
    	sc->Template = LoadObject<UParticleSystem>(NULL, TEXT("ParticleSystem'/Game/Effect/AdvancedMagicFX14/Particles/Mine/P_Fire.P_Fire'"));
    sc->ResetNextTick();
    
    ParticleBase->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform);
    ParticleBase->SetActorRelativeLocation(FVector::ZeroVector);
    ParticleBase->SetLifeSpan(3);
    
    static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter"));
    if (PlayerPawnBPClass.Class != NULL)
    {
    	DefaultPawnClass = PlayerPawnBPClass.Class;
    }
    
  • 在不生成对象(Actor)的情况下,获取蓝图的配置

    UPROPERTY(EditAnywhere, Category = "SplineMove")
    TSubclassOf<AActor> SplineContainer;
    // 直接Cast UBlueprintGeneratedClass
    UBlueprintGeneratedClass* Class = Cast<UBlueprintGeneratedClass>(SplineContainer);
    if (Class)
    {
    	USplineComponent* Spline = nullptr;
    	// 获取SimpleConstructionScript
    	if (!Class->SimpleConstructionScript)
    	{
    		return false;
    	}
    	// Node里面存的是Component
    	for (USCS_Node* Node : Class->SimpleConstructionScript->GetAllNodes())
    	{
    		// 该Node的类型
    		if (Node->ComponentClass->IsChildOf(USplineComponent::StaticClass()))
    		{
    			// 组件模板就是用来存你编辑的信息的
    			if (USplineComponent* Spline_ = Cast<USplineComponent>(Node->ComponentTemplate))
    			{
    				Spline = Spline_;
    				break;
    			}
    		}
    	}
    
  • 设置角色速度

    GetCharacterMovement()->Velocity
    
  • 设置暂停

    UGameplayStatics::SetGamePaused(GetWorld(), true)
    
  • 是否暂停

    UGameplayStatics::IsGamePaused(GetWorld())
    
  • 是否是否暂停

    UGameplayStatics::SetGamePaused(GetWorld(), true)
    
  • 暂停切换 / 菜单栏

    void APlayerControllerInGame::ToggleInputMode() {
    	if (UGameplayStatics::IsGamePaused(GetWorld())) {
    		ToggleToGameMode(); 
    	}
    	else {
    		ToggleToUIMode();
    	}
    }
    
    void APlayerControllerInGame::ToggleToUIMode() {
    	widget->SetVisibility(ESlateVisibility::Visible);
    	//UE_LOG(LogTemp,  Log, TEXT("Change to UI"));
    	FInputModeGameAndUI InputMode;
    	InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
    	InputMode.SetHideCursorDuringCapture(false);
    	SetInputMode(InputMode);
    	bShowMouseCursor = true;
    	UGameplayStatics::SetGamePaused(GetWorld(), true);
    }
    
    void APlayerControllerInGame::ToggleToGameMode() {
    	widget->SetVisibility(ESlateVisibility::Hidden);
    	//UE_LOG(LogTemp, Log, TEXT("Change to Game"));
     	FInputModeGameOnly InputMode;
     	SetInputMode(InputMode);
    	bShowMouseCursor = false;
    	UGameplayStatics::SetGamePaused(GetWorld(), false);
    }
    
  • 退出游戏

    //UKismetSystemLibrary::QuitGame(GetWorld(), UGameplayStatics::GetPlayerController(GetWorld(), 0), EQuitPreference::Quit, true);
    GetWorld()->GetFirstPlayerController()->ConsoleCommand("quit");
    //FGenericPlatformMisc::RequestExit(false); // 貌似会关闭整个进程,So,关闭UE4编辑器
    
  • 获取当前世界的tick时间

    GetWorld()->TimeSeconds
    
  • 播放动画蒙太奇,返回动画长度,可以设置播放速率,需要在动画蓝图内添加Slot

    PlayAnimMontage(AttackedAnim)
    
  • 判断类是否存在某成员

    static FProperty* Prop = FindFieldChecked<FProperty>(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); 
    return Prop;
    
    // Returns FName(TEXT("MemberName")), while statically verifying that the member exists in ClassName
    #define GET_MEMBER_NAME_CHECKED(ClassName, MemberName) \
    	((void)sizeof(UE4Asserts_Private::GetMemberNameCheckedJunk(((ClassName*)0)->MemberName)), FName(TEXT(#MemberName)))
    

UMG

在这里插入图片描述

  • 创建UserWidget类

    UPROPERTY(EditDefaultsOnly)
    	TSubclassOf<UUserWidget> PlayerAndStateUI;
    UPlayerStateAndSkillWidget* widget;
    
    widget = CreateWidget<UPlayerStateAndSkillWidget>(GetWorld(), PlayerAndStateUI);
    widget->AddToViewport();
    
    • 可通过以下类创建UserWidget
       UWidget、UWidgetTree、APlayerController、 UGameInstance、 UWorld
      
  • 获取Widget

    GetWidgetFromName(Name)
    
  • 自动绑定控件

    UPROPERTY(meta = (BindWidget)) // 该变量会自动寻找名为SettingImage的按钮进行绑定,如果没找到会编译出错
    	class UButton* SettingImage;
    
  • 按钮绑定事件

    button->OnClicked.AddDynamic(this, &UMyUserWidget::ClickButton);
    
  • 图片绑定事件

    image->OnMouseButtonDownEvent.BindUFunction(this, "UseSpell");
    
  • Set

    text->SetText(FText::FromString(FString(TEXT("Hu Hahaha"))));
    image->SetIsEnabled(0);
    text->SetVisibility(ESlateVisibility::Visible);
    
  • 设置TextBlock颜色

    MyTextBlock->SetColorAndOpacity(FSlateColor(FLinearColor(R / 255.0f, G / 255.0f, B / 255.0f, 1)));
    
  • 鼠标永久可见

    void AMyPlayerController::BeginPlay() {
    	widget = Cast<UMyUserWidget>(CreateWidget(this, Creation));
    	widget->AddToViewport();
    
    	bShowMouseCursor = true;
    }
    
  • Widget移动

    UImage* image = Cast<UImage>(GetWidgetFromName(TEXT("Mouse")));
    UCanvasPanelSlot* slot = Cast<UCanvasPanelSlot>(image->Slot);
    slot->SetPosition(slot->GetPosition() + FVector2D(0, 1));
    
  • 将Widget的位置设置到世界Actor的位置:

    FVector2D ScreenPosition;
    if (UWidgetLayoutLibrary::ProjectWorldLocationToWidgetPosition(GetOwningPlayer(), Target->GetActorLocation(), ScreenPosition, false))
    {
    	if (UCanvasPanelSlot* CanvasSlot = Cast<UCanvasPanelSlot>(Img->Slot))
    	{
    		ScreenPosition -= CanvasSlot->GetSize() / 2;;
    		CanvasSlot->SetPosition(ScreenPosition);
    	}
    }
    
  • 获取屏幕大小

    UGameplayStatics::GetPlayerController(GetWorld(), 0)->GetViewportSize(x_int,y_int)
    
  • 获取鼠标位置

    float px, py;
    if (UGameplayStatics::GetPlayerController(GetWorld(), 0)->GetMousePosition(px, py))
    
  • 获取当前控件在当前窗口大小下的缩放

    UWidgetLayoutLibrary::GetViewportScale(this)
    
  • 判断是否在屏幕内

    bool AMyCharacter::IsPointInMyScrrenViewport(FVector Point, FVector2D& Result)
    {
    	if (GetLocalRole() == ROLE_AutonomousProxy)
    	{
    		APlayerController* Ctrl = GetController<APlayerController>();
    		ULocalPlayer* LP = Ctrl ? Ctrl->GetLocalPlayer() : nullptr;
    		if (LP && LP->ViewportClient)
    		{
    			FSceneViewProjectionData ProjectionData;
    			if (LP->GetProjectionData(LP->ViewportClient->Viewport, eSSP_FULL, ProjectionData))
    			{
    				FMatrix const ViewProjectionMatrix = ProjectionData.ComputeViewProjectionMatrix();
    				FVector2D ScreenPosition;
    				bool bResult = FSceneView::ProjectWorldToScreen(Point, ProjectionData.GetConstrainedViewRect(), ViewProjectionMatrix, ScreenPosition);
    				if (bResult && ScreenPosition.X > ProjectionData.GetViewRect().Min.X && ScreenPosition.X < ProjectionData.GetViewRect().Max.X
    					&& ScreenPosition.Y > ProjectionData.GetViewRect().Min.Y && ScreenPosition.Y < ProjectionData.GetViewRect().Max.Y)
    				{
    					Result = ScreenPosition;
    					return true;
    				}
    			}
    		}
    	}
    	Result = FVector2D::ZeroVector;
    	return false;
    }
    
  • UI设置到屏幕中心

    int32 VX, VY;
    APlayerController* Ctrl = GetController<APlayerController>();
    if (Ctrl)
    {
    	Ctrl->GetViewportSize(VX, VY);
    }
    float Scale = UWidgetLayoutLibrary::GetViewportScale(this);
    // 设到中心,但是UI会处于中心点右下角,需要减去一半大小
    UI->SetPositionInViewport(FVector2D(VX, VY) / 2 -
    	UI->GetRootWidget()->GetDesiredSize() / 2 * Scale);
    
  • 关闭 FocusWidget 边框显示:项目设置 - RenderFocusRule 改为 Never
    在这里插入图片描述

  • 设置Widget的位置(到鼠标位置)(与锚点的偏移会受到窗口大小缩放的影响,所以需要除以缩放)

    UImage* image = Cast<UImage>(GetWidgetFromName(TEXT("Mouse")));
    UCanvasPanelSlot* slot = Cast<UCanvasPanelSlot>(image->Slot);
    auto pc = UGameplayStatics::GetPlayerController(GetWorld(), 0);
    if (image && pc) {
    	float px, py;
    	if (pc->GetMousePosition(px, py)) {
    		UCanvasPanel* cp = Cast<UCanvasPanel>(GetWidgetFromName(TEXT("ScreenPanel")));
    		float scale = UWidgetLayoutLibrary::GetViewportScale(this);
    		slot->SetPosition(FVector2D(px / scale, py / scale));
    	}
    }
    
  • 设置鼠标位置(需要在Build.cs里面加SlateCore)

    UGameplayStatics::GetPlayerController(GetWorld(), 0)->SetMouseLocation(x,y)
    
  • 世界3D Widget

    UWidgetComponent* Widget = NewObject<UWidgetComponent>(this);
    Widget->RegisterComponent();
    UUserWidget* AlertUI = CreateWidget<UUserWidget>(GetWorld(), Asset_AlertUI);
    Widget->SetWidget(AlertUI);
    Widget->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
    // 宽长比过小会挤压UI
    Widget->SetDrawSize(FVector2D(1920, 1080));
    Widget->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
    Widget->SetWidgetSpace(EWidgetSpace::World);
    Widget->SetVisibility(true);
    Widget->SetWorldLocation(xxx);
    Widget->SetWorldRotation(xxx);
    
  • 屏幕2D Widget

    UUserWidget* AlertUI = CreateWidget<UUserWidget>(GetWorld(), Asset_AlertUI);
    AlertUI->AddToViewport();
    AlertUI->SetVisibility(ESlateVisibility::Visible);
    int32 VX = 0, VY = 0;
    APlayerController* Ctrl = GetController<APlayerController>();
    if (Ctrl)
    {
    	Ctrl->GetViewportSize(VX, VY);
    }
    float Scale = UWidgetLayoutLibrary::GetViewportScale(this);
    // 中心点偏移,一定有Scale
    FVector2D EndP = FVector2D(VX, VY) / 2 + 偏移 * Scale;
    // 需要减去Widget自身半长宽,这个使用GetDesiredSize得到
    // 但是在第一次NativeTick之前GetDesiredSize为0,需要调用ForceLayoutPrepass
    AlertUI->ForceLayoutPrepass();
    AlertUI->SetPositionInViewport(EndP - AlertUI->GetRootWidget()->GetDesiredSize() / 2 * Scale);
    float Angle = 45;
    AlertUI->SetRenderTransformAngle(Angle);
    

在这里插入图片描述

FText/FString/FName/TChar/ANSICHAR

  • https://www.cnblogs.com/KillerAery/p/14385574.html

  • 制作字符串:

    FString::Printf(TEXT("%d"), i)
    FString::Printf(TEXT("Blueprint'/Game/ProjectW/Skill/BP_%s.BP_%s_C'"), *(MyFName.ToString()), *(MyFName.ToString()));
    
    #include "Misc/StringBuilder.h"
    TStringBuilder<2048> SB;
    SB << TEXT("If");
    return *SB
    
  • 互转:

    / → F S t r i n g \to FString FString → F T e x t \to FText FText → F N a m e \to FName FName
    F S t r i n g → FString \to FString/ F T e x t : : F r o m S t r i n g ( M y S t r i n g ) FText::FromString(MyString) FText::FromString(MyString) F N a m e ( ∗ M y S t r i n g ) FName(*MyString) FName(MyString)
    F T e x t → FText \to FText M y T e x t . T o S t r i n g ( ) MyText.ToString() MyText.ToString()/ F N a m e ( ∗ ( M y T e x t . T o S t r i n g ( ) ) ) FName(*(MyText.ToString())) FName((MyText.ToString()))
    F N a m e → FName \to FName M y N a m e . T o S t r i n g ( ) MyName.ToString() MyName.ToString() F T e x t : : F r o m N a m e ( M y N a m e ) FText::FromName(MyName) FText::FromName(MyName)/
    字面值 → \to T E X T ( " 1 " ) TEXT("1") TEXT("1") F T e x t : : F r o m S t r i n g ( T E X T ( " 1 " ) ) FText::FromString(TEXT("1")) FText::FromString(TEXT("1")) T E X T ( " 1 " ) TEXT("1") TEXT("1")
  • FString 转 TCHAR*:

    const TCHAR* MyConstTchar = *MyString
    
  • std::string 转 FString

    FString MyString(MyStdString.c_str())
    
  • FString 转 std::string

    std::string MyStdString(TCHAR_TO_UTF8(*MyString))
    
  • FString 转 char*

    char* x = TCHAR_TO_ANSI(*FString())
    
  • FString 转 int/float

    FString MyString = "1108.1110";
    int32 MyInt32 = FCString::Atoi(*MyString);
    float MyInt32 = FCString::Atof(*MyString);
    
  • 测试

    FName MyName = TEXT("1");
    FText MyText = FText::FromString(TEXT("1"));
    FString MyString = TEXT("1");
    
    MyText = FText::FromName(MyName);
    MyText = FText::FromString(MyString);
    MyName = FName(*MyString);
    MyName = FName(*(MyText.ToString()));
    MyString = MyText.ToString();
    MyString = MyName.ToString();
    
    const TCHAR* MyConstTchar = *MyString;
    
  • ON_SCOPE_EXIT,推出当前作用域时执行

    CurrentPlan->bGetNextPrimitiveStepsForRunning = true;
    ON_SCOPE_EXIT
    {
    	CurrentPlan->bGetNextPrimitiveStepsForRunning = false;
    };
    ......
    
  • 其它转ANSICHAR

    const ANSICHAR* InputTags_ANSI = TCHAR_TO_ANSI(*InputTags.ToString());
    
  • 输出地址

    #include "Engine/Engine.h"
    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, FString::Printf(TEXT("AutonomousProxy: 0x%u%"), &Value));
    

UE

  • 查看引擎版本号:Engine\Source\Runtime\Launch\Resources\Version.h

    // 5.3.2
    #define ENGINE_MAJOR_VERSION	5
    #define ENGINE_MINOR_VERSION	3
    #define ENGINE_PATCH_VERSION	2
    
  • UE5 Live Coding编译出错时乱码问题解决方法

  • FORCEINLINE

  • GC

  • Check、Verify和Ensure

  • 配置文件

  • UFUNCTION不能重载(序列化)

  • 添加头文件,以Engine\Source\Runtime\NavigationSystem\Public\NavigationSystem.h为例
    Runtime和Public都是一个很明显的分界线,所以

    #include "NavigationSystem/Public/NavigationSystem.h"
    #include "NavigationSystem.h"
    

    然后到.Build.cs,将"NavigationSystem"加到PublicDependencyModuleNames.AddRange内

  • UObject销毁

    UObject::MarkPendingKill()    //此方法执行后,所有指向此实例的指针将设置为NULL,并在下一次GC时删除
    
  • 增加Insight Trace:

    // 字面量
    TRACE_CPUPROFILER_EVENT_SCOPE(DelaySceneRenderCompletion_TaskWait);
    // 运行时
    TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*(FString(TEXT("test.")) + GetName()));
    // TRACE_CPUPROFILER_EVENT_SCOPE_STR
    // 记录UObject
    #define SCOPE_CYCLE_UOBJECT(Name, Object) \
    	FScopeCycleCounterUObject ObjCycleCount_##Name(Object);
    
    SCOPED_BOOT_TIMING("xxx")
    
  • 开启UFunction Trace,在启动命令行加入:

    -statenamedevents -trace=default,AssetLoadTime
    
    • 具体在:
    #define PER_FUNCTION_SCRIPT_STATS ((STATS || ENABLE_STATNAMEDEVENTS) && 1)
    
    void UObject::CallFunction( FFrame& Stack, RESULT_DECL, UFunction* Function )
    {
    #if PER_FUNCTION_SCRIPT_STATS
    	const bool bShouldTrackFunction = (Stack.DepthCounter <= GMaxFunctionStatDepth);
    	SCOPE_CYCLE_UOBJECT(FunctionScope, bShouldTrackFunction ? Function : nullptr);
    #endif // PER_FUNCTION_SCRIPT_STATS
    
    	SCOPE_CYCLE_UOBJECT(ContextScope, GVerboseScriptStats ? this : nullptr);
    
  • Insight 操作:参考

    • F聚焦某个片段,C垂直方向调整缩放,双击片段高亮同类型片段,双击空白区域取消高亮
    • Ctrl双击片段:选中当前片段的时间区域
  • 打印堆栈信息

    static void PrintStackInfo(int32 Depth = 20)
    {
    	FString Result = "";
    	TArray<FProgramCounterSymbolInfo> Infos = FPlatformStackWalk::GetStack(1, Depth);
    	for (const FProgramCounterSymbolInfo& Info : Infos)
    	{
    		Result += FString::Printf(TEXT("%s %s Line:%d\n"), ANSI_TO_TCHAR(Info.FunctionName), ANSI_TO_TCHAR(Info.Filename), Info.LineNumber);
    	}
    	UE_LOG(LogTemp, Error, TEXT("%s"), *Result);
    }
    
    or
    
    FDebug::DumpStackTraceToLog(ELogVerbosity::Display);
    
  • 前置声明

    • 在.h内加入class MyClass
    • 在.cpp可以include不会造成循环include
  • UE4 重定向模块名或者类名(加入到Config/DefaultEditor.ini),参考

    • 重定向模块名,例如将插件内的类,转移到项目内部,这个时候类所处的模块会发生变化,例如PLUGINA_API变为PROJECTA_API,而这里的PLUGINA和PROJECTA就是两个模块,可以一次性将该模块的所有内容合并到另外一个模块,则使用:
      [CoreRedirects]
      +PackageRedirects=(OldName="/Script/PLUGINA", NewName="/Script/PROJECTA")
      
    • 第二种是重定向某个类,例如重命名cpp类时,原来的cpp类派生出的蓝图类需要重定向,则使用(注意去掉F、A、U等前缀):
      +ClassRedirects=(OldName="/Script/PROJECTA.ClassA",NewName="/Script/PROJECTA.ClassB")
      
  • UInterface

    UINTERFACE(Blueprintable)
    class UReactToTriggerInterface : public UInterface
    {
    	GENERATED_BODY()
    };
    class IReactToTriggerInterface
    { 
    	GENERATED_BODY()
    	public:
    	/** 对激活该对象的触发器体积做出响应。如果响应成功,返回“真(true)”。*/
    	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category="Trigger Reaction")
    	bool ReactToTrigger() const;
    };
    
  • UE Lambda的问题

  • 纯虚函数

    virtual void UseOnce() PURE_VIRTUAL (USkillComponent::UseOnce, );
    virtual bool UseOnce() PURE_VIRTUAL (USkillComponent::UseOnce, return false;);
    virtual bool UseOnce() = 0;  //无法实例化、抽象类
    
  • 加载 / 卸载LevelInstance

    	bool bSuccess;
    	LoadedBlock = ULevelStreamingDynamic::LoadLevelInstance(this, LevelName, LevelLocation, LevelRotation, bSuccess);
    	
    	LoadedBlock->SetIsRequestingUnloadAndRemoval(true);
    	LoadedBlock->MarkPendingKill();
    
  • 异步加载SoftObject,并在加载完成后触发回调:

    TArray<FSoftObjectPath> AssetsToLoad;
    auto Callback = [this, ...]() {};
    FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
    StreamingHandle = StreamableManager.RequestAsyncLoad(AssetsToLoad, Callback);
    
  • RootMotion施加的位置:

    if( HasAnimRootMotion() )
    {
    	const FQuat OldActorRotationQuat = UpdatedComponent->GetComponentQuat();
    	const FQuat RootMotionRotationQuat = RootMotionParams.GetRootMotionTransform().GetRotation();
    	if( !RootMotionRotationQuat.IsIdentity() )
    	{
    		const FQuat NewActorRotationQuat = RootMotionRotationQuat * OldActorRotationQuat;
    		MoveUpdatedComponent(FVector::ZeroVector, NewActorRotationQuat, true);
    	}
    
  • 使用UStruct/UScriptStruct指针动态创建结构体对象

    UStruct* Type;
    uint8* RowData = (uint8*)FMemory::Malloc(Type->GetStructureSize());
    // 调用构造函数,初始化虚表
    Type->InitializeStruct(RowData);
    // 可选,反序列化
    ((FXXX*)RowData)->Serialize(Ar)
    
  • 类判断

    • 两个对象类相同:GetClass()
    • 是否属于某个类:GetClass()->IsChildOf(AClass1::StaticClass())
    • 结构体:Value.StaticStruct() == FBlackBoardBool::StaticStruct()
  • 优先队列

    Frontier.HeapPush(NewPlan, FComparePlanCosts());
    Frontier.HeapPop(Plan, FComparePlanCosts());
    
    struct FComparePlanCosts
    {
    	FORCEINLINE bool operator()(const TSharedPtr<FHTNPlan>& A, const TSharedPtr<FHTNPlan>& B) const
    	{
    		return A.IsValid() && B.IsValid() ?
    			A->Cost < B->Cost :
    			B.IsValid();
    	}
    };
    
  • 自定义Sort

    struct FXXX
    {
    	struct FSort
    	{
    		FORCEINLINE bool operator()(const FXXX& A, const FXXX& B) const
    		{
    			return A.Priority > B.Priority;
    		}
    	};
    };
    
    XXXArray.Sort(FXXX::FSort());
    
  • GameFramework Objects Creation Order and Calls
    在这里插入图片描述

  • 各个GameFramework

    /权威主控模拟
    GameInstanceYesYesYes
    GameModeYesNoNo
    GameStateYesYesYes
    PlayerControllerYesYesNo
    PlayerStateYesYesYes
  • UE Network 基础

    • https://stonelzp.github.io/ue4-multiplay-framework/
    • https://alexandrite.top/2020/05/20/ue4-wang-luo-bian-cheng-ue4/
  • 传递函数:

    bool XXX::XXX(TFunctionRef<bool(const int32&)> XXX)
    
    XXX([](const int32& InElement)
    	{
    		return true;
    	});
    
  • FVector初始值(1.xxxxe38)这种,使用Equal无法进行正确判断

  • private: static class UClass * __cdecl ***::GetPrivateStaticClass(void)的几种可能

    • 没有引入模块(插件),需要在.build.cs的PublicDependencyModuleNames里面加对应模块

      PublicDependencyModuleNames.AddRange(
      	new string[]
      	{
      	
      	}
      );
      
    • 引入模块,但是该模块的某个类没有加上XXX_API,所以这个类视为模块内使用,不可被其它模块使用,在以下位置加上即可

      class XXX_API UMyClass
      
  • 强制转换

    operator FVector() const
    {
    	return FVector(X, Y, Z);
    };
    
  • TSparseArray

  • (Editor)调试网络延时:NetEmulation.PktLag 300

  • (Editor)调试网络延时波动:NetEmulation.PktLagVariance 100

  • (Server)时间推进速度:Slomo 0.5

  • 关闭运行时失去焦点后降低帧数:EditorPreference - Use Less CPU when in background

  • 获取所有UObject成员

    TArray<UObject*> ChildObjects;
    GetObjectsWithOuter(AActor, ChildObjects, false, RF_NoFlags, EInternalObjectFlags::PendingKill);
    
  • 遍历反射属性、不使用字面量获取类型名称

    for (TFieldIterator<FProperty> It(Comp->GetClass(), EFieldIteratorFlags::ExcludeSuper); It; ++It)
    {
    		FProperty* Property = *It;
    		if (Property->GetCPPType() == Type1->GetStructCPPName())
    		{
    			FScalableFloat* InData = It->ContainerPtrToValuePtr<FScalableFloat>(Comp);
    			InData->SetValue(Value);
    		}
    		if (Property->GetCPPType() == Type2->GetStructCPPName())
    		{
    			FAttributeBasedFloat* InData = It->ContainerPtrToValuePtr<FAttributeBasedFloat>(Comp);
    			InData->Coefficient = Value;
    		}
    }
    
  • 不使用字面量获取枚举、成员、函数名称

    GET_ENUMERATOR_NAME_CHECKED(EnumName, EnumeratorName)
    GET_MEMBER_NAME_CHECKED(ClassName, MemberName)
    GET_MEMBER_NAME_STRING_CHECKED(ClassName, MemberName)
    GET_FUNCTION_NAME_CHECKED(ClassName, FunctionName)
    GET_FUNCTION_NAME_STRING_CHECKED(ClassName, FunctionName)
    // 函数还可以确认参数类型
    GET_FUNCTION_NAME_CHECKED_TwoParams(ClassName, FunctionName, ArgType1, ArgType2)
    
  • 获取CDO(Class Default Object)

    TSubclassOf<UXXX>XXX;
    XXX->GetDefaultObject<UXXX>();
    
  • 开启PushModel,使用bool判断代替值判断:

    // 定义同步属性
    FDoRepLifetimeParams SharedParams;
    SharedParams.bIsPushBased = true;
    DOREPLIFETIME_WITH_PARAMS_FAST(AXXX, XXX, SharedParams);
    
    // 在修改后手动设置脏
    MARK_PROPERTY_DIRTY_FROM_NAME(AXXX, XXX, this);
    
  • 调节分辨率

    • 命令行

      r.SetRes 2560x1440f/w            // fullscreen or window
      
    • 代码

      // EWindowMode::Windowed
      GEngine->GetGameUserSettings()->SetFullscreenMode(EWindowMode::WindowedFullscreen);
      GEngine->GetGameUserSettings()->SetScreenResolution(FIntPoint(1920, 1080));
      GEngine->GetGameUserSettings()->ApplySettings(true);
      
  • 命令行打包,命令参考

    • 来到Engine\Build\BatchFiles,打开cmd,运行RunUAT即可
  • 打包后LoadObject失败:

    • 原因:因为资源没有被cook进包体(因为是代码去读取,所以没有被检测出引用)。
    • 解决办法:将所在路径加入到项目设置的Additional Asset Directories to Cook内即可
  • 蓝图内修改属性后自动做出调整

    #if WITH_EDITOR && USE_EQS_DEBUGGER
    	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
    #endif //WITH_EDITOR && USE_EQS_DEBUGGER
    
  • 添加全局宏

  • 在UE内添加控制台工程(Programs)

  • 通过config进行类内属性的赋值

  • UE5的对象指针FObjectPtr与TObjectPtr

  • StateTree

  • 蓝图可访问该私有变量:meta = (AllowPrivateAccess = “true”)

  • 创建编辑器模块

  • uproperty 说明符大全

  • ufunction 说明符大全

  • TableRow

  • 开启 Nanite:在StaticMesh上右键 - Nanite - Enable

    • 可以过滤所有的 StaticMesh 一键转换
  • 查看 Nanite 三角面:Lit - 改为Nanite Visualization - Triangles

  • 后处理体积

    • 全局适用:Infinite Extent (Unbound):True
  • 运行时显示碰撞:show collision

  • 导出内存分析数据:游戏内 MemReport -full

  • Enum使用位运算

    enum class EXXX : uint16
    {
    	A = 0x01,
    	B = 0x02,
    	C = 0x03
    }
    
    using T = std::underlying_type_t<EXXX>; // uint16
    EXXX xxx;
    if((T)xxx & (T)EXXX::A)
    { dosomething(); }
    

定义可以Tick的WorldSubsystem

... UXXXSystem : public UTickableWorldSubsystem

	virtual TStatId GetStatId() const override;
	virtual void Tick(float DeltaTime) override;


TStatId UXXXSystem::GetStatId() const
{
	RETURN_QUICK_DECLARE_CYCLE_STAT(UXXXSystem, STATGROUP_Tickables);
}

void UXXXSystem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

定义自定义StatId:

// .h
DECLARE_STATS_GROUP(TEXT("Task Graph Tasks"), STATGROUP_TaskGraphTasks, STATCAT_Advanced);
DECLARE_CYCLE_STAT_EXTERN(TEXT("FReturnGraphTask"), STAT_FReturnGraphTask, STATGROUP_TaskGraphTasks, CORE_API);

DECLARE_STATS_GROUP(TEXT("AbilitySystem"), STATGROUP_AbilitySystem, STATCAT_Advanced);
DECLARE_CYCLE_STAT_EXTERN(TEXT("GetActiveEffectsData"), STAT_GameplayEffectsGetActiveEffectsDuration, STATGROUP_AbilitySystem, );

// .cpp
DEFINE_STAT(STAT_FReturnGraphTask);
return GET_STATID(STAT_FReturnGraphTask);

DEFINE_STAT(STAT_GameplayEffectsGetActiveEffectsDuration);
SCOPE_CYCLE_COUNTER(STAT_GameplayEffectsGetActiveEffectsDuration);

C++

Final
static_cast、dynamic_cast、const_cast和reinterpret_cast
Lambda
隐式转换
宏展开顺序
FILE, FUNCTION, LINE
move forward

  • 函数指针作为返回值

    int Add_Implement(int a, int b)
    {
        return a + b;
    }
    
    int (*Add())(int, int)
    {
        return Add_Implement;
    }
    
    cout << Add()(1, 2); // 3
    
  • 宏:转字符串

    1.:在宏展开的时候会将#后面的参数替换成字符串,如:
          #define p(exp) printf(#exp);
       调用p(test)的时候会将#exp换成"test"
    2. ##:将参数作为字面值,与其它部分连接,作为一个变量名、函数名、类名等
          #define cat(x,y) x##y
       调用cat(var, 123)展开后成为var123.
    3. #@:将值序列变为一个字符
          #define ch(c) #@c
       调用ch(a)展开后成为'a'.
    
  • 传递任意类型的函数:

    template<typename IterateFunc>
    inline void ForeachItem(const IterateFunc& Func)
    {
    	int32* Ptr;
    	Func(Ptr);
    }
    
    ForeachItem([&Ans](int32* Ptr) {++Ans; });
    
  • 宏展开补充

    #define Cb(A,B,C) A##B##C
    #define CC(A,B,C) Cb(A,B,C)
    #define Pi 3.14
    // 展开实参后代入Cb
    CC(__LINE__,_,Pi) -> Cb(5,_,3.14) -> 5_3.14
    // ##相连的部分不再展开,仅始为字符串
    Cb(__LINE__,_,Pi) -> __LINE___Pi
    

    这是为什么 GENERATED_BODY 需要转到 BODY_MACRO_COMBINE后,再转一层 BODY_MACRO_COMBINE_INNER 的原因

  • 局部函数

    struct Helper
    {
    	static void GetEndRotation(const FNavigationPath& Path, FRotator& OutEndRotation)
    	{
    		int32 All = Path.GetPathPoints().Num();
    		if (All > 1)
    		{
    			OutEndRotation = (Path.GetPathPointLocation(All - 1).Position - Path.GetPathPointLocation(All - 2).Position).Rotation();
    		}
    	}
    };
    Helper::GetEndRotation(Path, OutEndRotation);
    
  • 节省函数返回值的Cast

    //类内部:
    FORCEINLINE APawn* GetPawn() const { return Pawn; }
    
    /** Templated version of GetPawn, will return nullptr if cast fails */
    template<class T>
    T* GetPawn() const
    {
    	return Cast<T>(Pawn);
    }
    
  • 判断该类是否继承了某一接口

    //注意:这里是UMyInterface而不是IMyInterface
    AActor::StaticClass()->ImplementsInterface(UMyInterface::StaticClass());
    
  • Lambda

    默认是值捕获,值捕获时捕获当前的值,就算之后值变了,Lambda内还是那个值
    在这里插入图片描述

    auto ChangeToForward = [this](FVector Input)->FVector
    {
    	FVector Forward = this->GetControlRotation().Vector();
    	Forward.Normalize();
    	Forward *= Input.Size();
    	return Forward;
    };
    
  • lambda实现递归:

    // 不用 auto
    std::function<int(int,int)> sum;
    // 捕获自己
    sum = [term,next,&sum](int a, int b)->int {
    if(a>b)
        return 0;
    else
        return term(a) + sum(next(a),b);
    };
    
  • Link Error:自己找找为什么符号没有被找到,可能是以下原因

    • 循环引用
    • Build.cs没加入
    • 插件内部类,没有加XXX_API进行导出
    • 类静态成员变量没有在cpp内实例化
  • SetTimer 传参

     FTimerHandle Handle;
     FTimerDelegate TheDelegate = FTimerDelegate::CreateUObject(this, &AMyGameMode::Func, Para1, Para2);
     // call this->Func after 5s without loop, parameter Player
     GetWorldTimerManager().SetTimer(Handle, TheDelegate, 5.0f, false );
    

枚举

UE4[C++]UEnum枚举相关使用

USTRUCT

[UE4] c++中使用struct
USTRUCT的使用
UStruct 与 Traits
自定义结构体序列化
自定义结构体序列化

TArray、TMap、TTuple、TPair、UEnum

TMap
TTuple
UEnum

  • TMap遍历

    UPROPERTY(EditAnywhere)
    	TMap<TSubclassOf<AMonsterCharacter>, float> SpawnMonsters;
    
    for (TPair<TSubclassOf<AMonsterCharacter>, float>& element : SpawnMonsters)
    {
    	Sum += element.Value;
    }
    
    for (auto It = Groups.CreateIterator(); It; It++)
    {
    	if (!It->Key)
    	{
    		It.RemoveCurrent();
    		continue;
    	}
    
  • TMap Sort

    Ability_Cost.ValueSort([](float A, float B)
    	{
    		return A < B;
    	});
    
  • 自写的UStruct作为TMap的Key时,需要重载等于号(Contains等函数)和GetTypeHash(哈希)

    USTRUCT()
    struct FTwoGameplayTagContainer
    {
    	GENERATED_BODY()
    
    	UPROPERTY(EditAnywhere)
    	FGameplayTagContainer A;
    	UPROPERTY(EditAnywhere)
    	FGameplayTagContainer B;
    	bool operator== (const FTwoGameplayTagContainer& Other) const
    	{
    		return A == Other.A && B == Other.B;
    	}
    	friend uint32 GetTypeHash(const FTwoGameplayTagContainer& A)
    	{
    		return GetTypeHash(A.A.ToString() + "$" + A.B.ToString());
    		// 多个时可以使用 return HashCombine(GetTypeHash(A.OwnerComp), GetTypeHash(A.TemplateNode));
    	}
    };
    
  • ENum暴露蓝图

    UENUM(BlueprintType)
    enum class EAIState : uint8
    {
    	Born 			UMETA(DisplayName = "Born"),
    	Idle			UMETA(DisplayName = "Idle"),
    	InAttack		UMETA(DisplayName = "In Attack"),
    	Attack			UMETA(DisplayName = "Attack"),
    	OutAttack		UMETA(DisplayName = "Out Attack"),
    	Dead			UMETA(DisplayName = "Dead"),
    };
    
  • Enum变量转int、获取字符串

    const UEnum* EnumPtr = StaticEnum<EAIState>(); //FindObject<UEnum>(ANY_PACKAGE, TEXT("EAIState"), true);
    return FString::Printf(TEXT("Set state to %s"), *EnumPtr->GetNameStringByValue((int32)AIState));
    
  • Enum获取DisplayName

    UEnum::GetDisplayNameTextByValue
    
  • 在黑板使用C++定义的ENum,填到Enum Name里面就行了
    在这里插入图片描述

  • TTuple

    TArray<TTuple<float, ARPGAICharacter*>> DisAndAI;
    for(ARPGAICharacter* AI : Elem.Value.AIs)
    {
    	DisAndAI.Add(MakeTuple(AI->GetDistanceTo(Elem.Key), AI));
    }
    DisAndAI.Sort([](TTuple<float, ARPGAICharacter*> A, TTuple<float, ARPGAICharacter*> B)
    {
    	return A.Get<0>() < B.Get<0>();
    });
    
  • TPair

    TPair<int32, int32> X(1, 2);
    X.Key = 2;
    X.Value = 2;
    X = MakeTuple(2, 3);
    
  • FastArray

    会乱序,但是因为使用手动MarkDirty,不用每次全部比较,并且在增加和删除时不会将变动的位置全部发送

    // 结构体继承FFastArraySerializerItem
    USTRUCT()
    struct FXXX: public FFastArraySerializerItem
    {
    	GENERATED_USTRUCT_BODY()
    	
    	UPROPERTY()
    	int32 XXX;
    	// 客户端同步回调
    	void PreReplicatedRemove();
    	void PostReplicatedAdd();
    	void PostReplicatedChange();
    };
    
    // 数组包成一个类,继承FFastArraySerializerItem
    USTRUCT()
    struct FXXXContainer: public FFastArraySerializer
    {
    	GENERATED_USTRUCT_BODY()
    
    	UPROPERTY()
    	TArray<FXXX> Items;
    
    	bool NetDeltaSerialize(FNetDeltaSerializeInfo & DeltaParms)
    	{
    		return FFastArraySerializer::FastArrayDeltaSerialize<FXXX, FXXXContainer>(Items, DeltaParms, *this);
    	}
    		template< typename Type, typename SerializerType >
    	bool ShouldWriteFastArrayItem(const Type& Item, const bool bIsWritingOnClient)
    	{
    		// Type is FXXX, 定制是否需要同步
    		...
    
    		if (bIsWritingOnClient)
    		{
    			return Item.ReplicationID != INDEX_NONE;
    		}
    
    		return true;
    	}
    	void AddItem(FXX XX)
    	{
    		FXX& Item = Items.Add_GetRef(MoveTemp(XX));
    		// 增加或修改元素后,需要对相应的元素手动 MarkDirty
    		MarkItemDirty(Item);
    	}
    	void RemoveItem(const FXX& XX)
    	{
    		for(int32 Idx = 0; Idx < Items.Num(); ++Idx)
    		{
    			if(Items[Idx] == XX)
    			{
    				Items.RemoveAt(Idx);
    				// 删除后,需要手动 MarkArrayDirty
    				MarkArrayDirty();
    				return
    			}
    		}
    	}
    };
    
    
    template<>
    struct TStructOpsTypeTraits<FXXXContainer> : public TStructOpsTypeTraitsBase2<FXXXContainer>
    {
    	enum
    	{
    		WithNetDeltaSerializer = true,
    	};
    };
    

编译选项

UE4关于编译配置的参考

时间

UE4 World的时间

序列化

UE4序列化
序列化与版本控制

反射

使用:
《InsideUE4》UObject(十三)类型系统-反射实战

原理:
UE 反射实现分析:基础概念
UE4反射机制
UE4 反射系统

// 根据反射调用函数
template<typename... TReturns, typename... TArgs>
void InternalCallUFunction(UObject* Object, UFunction* Function, TTuple<TReturns...>& OutParams, TArgs&&... Args)
{
	if (!Object || !Function)
	{
		return;
	}
	uint8* OutParamsBytes = (uint8*)&OutParams;
	TTuple<TArgs...> InParams(Forward<TArgs>(Args)...);
	uint8* InParamsByte = (uint8*)&InParams;

	void* FuncParamsStructAddr = (uint8*)FMemory_Alloca(Function->ParmsSize);
	FMemory::Memzero(FuncParamsStructAddr, Function->ParmsSize);
	for (TFieldIterator<FProperty> It(Function); It; ++It)
	{
		FProperty* Property = *It;
		void* PropAddr = Property->ContainerPtrToValuePtr<void*>(FuncParamsStructAddr);
		if ((Property->PropertyFlags & CPF_Parm) == CPF_Parm && (Property->PropertyFlags & CPF_ReturnParm) == 0)
		{
			int32 Size = Property->GetSize();
			int32 Offset = Property->GetOffset_ForInternal();
			FMemory::Memcpy(PropAddr, InParamsByte + Offset, Size);
		}
	}

	Object->ProcessEvent(Function, FuncParamsStructAddr);
	OutParamsBytes = (uint8*)&OutParams;
	for (TFieldIterator<FProperty> It(Function); It; ++It)
	{
		FProperty* Prop = *It;
		if (Prop->PropertyFlags & CPF_OutParm)
		{
			const void* PropBuffer = Prop->ContainerPtrToValuePtr<void*>(FuncParamsStructAddr);
			Prop->CopyCompleteValue(OutParamsBytes, PropBuffer);
			OutParamsBytes += Prop->GetSize();
		}
	}
}

template<typename... TReturn, typename... TArgs>
void CallUFunction(UObject* Object, FName FunctionName, TTuple<TReturn...>& OutParams, TArgs&&... Args)
{
	if (!Object)
	{
		return;
	}
	UFunction* Func = Object->FindFunction(FunctionName);
	if (!Func)
	{
		return;
	}
	InternalCallUFunction<TReturn...>(Object, Func, OutParams, Forward<TArgs>(Args)...);
}


TTuple<> EmptyReturn;
if (Property && Property->GetCPPType() == TEXT("bool"))
{
	InternalCallUFunction(Object, Function, EmptyReturn, Input > 0);
}
else
{
	InternalCallUFunction(Object, Function, EmptyReturn, Input);
}

委托

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGridEmpty, int32, X, int32, Y);

FOnGridEmpty OnGridEmpty;

OnGridEmpty.AddDynamic(this, &UCardManager::UpperCardExtend);

全面理解UE4委托

智能指针

https://blog.csdn.net/weixin_43844254/article/details/99752599
https://zhuanlan.zhihu.com/p/369974105

广播

[Unreal]广播事件(C++)

网络同步

《Exploring in UE4》关于网络同步的理解与思考[概念理解]
《Exploring in UE4》网络同步原理深入(上)[原理分析]
《Exploring in UE4》网络同步原理深入(下)
关于网络同步的理解与思考

属性复制

构造函数:bReplicates = true;

virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override;

void ARPGAICharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(ARPGAICharacter, Target);
}

UPROPERTY(Replicated)

仅在主控端同步

void AActor::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
  DOREPLIFETIME_CONDITION( AActor, ReplicatedMovement, COND_AutonomousOnly );
}

自定义Asset类型

https://blog.csdn.net/qq_29667889/article/details/109307203

https://zhuanlan.zhihu.com/p/41332747

射线检测

UE4 碰撞(Collision)之光线检测

Log

定义自己的Log分类:

// .h
DECLARE_LOG_CATEGORY_EXTERN(YourLog, Log, All);

// .cpp
DEFINE_LOG_CATEGORY(YourLog);
  • 输出Log

    // 带端前缀
    UKismetSystemLibrary::PrintString(GetWorld(), TEXT(""), true, false);
    
    #include "Engine/Engine.h"
    // 五秒。 
    // 参数中的-1"键"值类型参数能防止该消息被更新或刷新。
    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Hello World"));
    
  • 输出Log

    UE_LOG(LogTemp, Log, TEXT("Change Text By Cpp"));
    
    UE_LOG(LogTemp, Log, TEXT("UseSpell: %d"), cooling.Num());
    
    UE_LOG(LogTemp, Log, TEXT("%s"), *MyString);
    

Ue4的UE_LOG

Slate

https://unrealcommunity.wiki/creating-an-editor-module-x64nt5g3
https://docs.unrealengine.com/4.27/zh-CN/ProgrammingAndScripting/Slate/Overview/
https://zhuanlan.zhihu.com/p/418944210
https://zhuanlan.zhihu.com/p/450038103
https://zhuanlan.zhihu.com/p/377745543
https://zhuanlan.zhihu.com/p/45682313

性能分析

stats性能埋点 初步
stats性能埋点
如何应对CPU帧率瓶颈和卡顿
Unreal Insights

开始结束性能录制:

在这里插入图片描述
录制后自动打开Insight

在这里插入图片描述

IK-Rig

https://www.bilibili.com/video/BV1BS4y1H7qL?p=2&vd_source=4de8d23df531f514b8f60748534ebf62

异步操作

UE4原子操作与无锁编程
UE4/UE5的TaskGraph
UE4 多线程 创建与使用
UE4 C++基础 - 多线程
C++实现异步操作Delay
UE4关卡流式加载与Latent机制

包、模块、资源管理

UE4的资源管理
UE4加载模块分析笔记(一)
UE4加载模块分析笔记(二)
资源引用 FSoftObjectPath TSoftObjectPtr TSoftClassPtr

文件操作

查找文件 移动文件 删除文件
FPaths::ProjectContentDir() + XXX
创建UAsset资源
序列化保存关卡内所有Actor到UAsset

本地化

https://blog.csdn.net/u010385624/article/details/89705285

在使用UE4修改骨骼网格体的姿势时,我们可以按照以下步骤进行操作: 1. 打开UE4编辑器,并导入需要修改姿势的骨骼网格体。确保骨骼网格体已经有绑定好的骨骼节点和动画。 2. 选择骨骼网格体,打开“Persona”窗口。在“Skeleton”选项卡下,可以看到骨骼节点的层次结构。 3. 选中需要修改的骨骼节点,例如手臂、头部或腿部。在“Details”选项卡下,可以找到相关的属性和设置。 4. 在“Transform”部分,通过修改旋转、位移和缩放值来改变节点的位置和旋转。可以手动输入数值或拖动轴向进行微调。 5. 还可以使用“Transform”部分的“Rotation Retargeting”选项来调整节点的旋转,例如使用“Additive”模式进行增量旋转。 6. 如果只是暂时修改姿势而不改变骨骼网格体的原始位置,可以选择“Modify Bone”部分的“Retargeting Options”选项,勾选“Translation Retargeting”并选择“Animation Scale”。 7. 修改完毕后,可以点击“Apply”按钮以应用修改的姿势。如果需要多次修改姿势,可以先在“Pose”菜单下选择“Copy Pose”,然后再在需要修改的节点上选择“Paste Pose”来粘贴已复制的姿势。 8. 在修改骨骼网格体的姿势后,可以通过播放动画或在游戏中实时查看效果。如果没有达到预期的效果,可以返回“Persona”窗口再次对姿势进行微调。 总结:利用UE4的“Persona”窗口,我们可以很方便地修改骨骼网格体的姿势。通过调整节点的旋转、位移和缩放值,我们可以快速实现网格体的姿势修改,并通过应用和复制粘贴等操作来达到预期的效果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值