0%

【UE4】资源、小知识、问题 汇总

记录一些 UE 资源、小知识点、遇到的问题

资源

UE蓝图基础_@大宁字的博客-CSDN博客

UE蓝图进阶_@大宁字的博客-CSDN博客

新手指南 | Epic Developer Community (epicgames.com)

业界大佬们:

大钊 - 知乎 (zhihu.com)

南京周润发 - 知乎 (zhihu.com)

Jiff - 知乎 (zhihu.com)

大侠刘茗Marin - 知乎 (zhihu.com)

Jerish - 知乎 (zhihu.com)

陶仁贤 - 知乎 (zhihu.com)

安柏霖 - 知乎 (zhihu.com)

顾煜 - 知乎 (zhihu.com)

小知识

输出

1
2
3
4
5
6
7
8
9
10
11
12
// 输出日志到文件中
UE_LOG(LogTemp, Warning, TEXT("Hello"));
UE_LOG(LogTemp, Error, TEXT("Hello"));
UE_LOG(LogTemp, Log, TEXT("Hello"));

// 输出到屏幕上
GEngine->AddOnScreenDebugMessage(-1, 20, FColor::Yellow, "Hello");

// 有时候要输出一下 FSting
FString fs = "asd";
UE_LOG(LogTemp, Error, TEXT("%s"), *fs);

获取

获取,设置 Actor 的位置和旋转

1
2
3
4
5
6
FVector NewLocation = GetActorLocation();
FRotator NewRotation = GetActorRotation();

SetActorLocationAndRotation(NewLocation, NewRotation);

// 这些函数都是 Actor 的成员函数,继承后直接用就行了

获取 GameState

1
2
3
AMyGameStateBase* GS = Cast<AMyGameStateBase>(GetWorld()->GetGameState());

GetWorld() 是 Actor 里面的函数;

获取 GameMode

1
2
3
4
5
6
7
8
9
// 方法一:
#include "Kismet/GameplayStatics.h"
const AMyGameModeBase* GM = Cast<AMyGameModeBase>(UGameplayStatics::GetGameMode(this));

// 方法二:
AMyGameModeBase* GM = Cast<AMyGameModeBase>(GetWorld()->GetAuthGameMode());

// 方法三:(GetDefaultGameMode() 是 AGameStateBase 里面的)
const AMyGameModeBase* GM = Cast<AMyGameModeBase>(GetDefaultGameMode());

通过反射,直接获取蓝图中配置的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FString AIPath = "Blueprint'";
AIPath.Append(M_SoldierInfoList[i].ToString());
AIPath.Append("'");
UClass* AIClass = LoadClass<AAICharacter_Base>(NULL, *AIPath);

if (AIClass)
{
AAICharacter_Base* AI_Base = Cast<AAICharacter_Base>(AIClass->GetDefaultObject());
if (AI_Base)
{
// 通过 GetDefaultObject() 获取到的 AI_Base 就是蓝图本身的这个Object;
// 然后在这里就可以直接获取到蓝图身上的东西了(我们可以往蓝图上面配置东西)
}
}

加载

从 c++ 中加载 UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 方法1:
FString HUDClassLoadPath = FString(TEXT("Blueprint'/Game/Blueprints/HUD.HUD_C'"));
auto MyUIClass = LoadClass<UUserWidget>(NULL, *HUDClassLoadPath);
if (MyUIClass != nullptr)
{
UUserWidget* MyUI = CreateWidget<UUserWidget>(GetWorld(), MyUIClass);
if (MyUI != nullptr) MyUI->AddToViewport();
}

// 方法2:
FString UiPath = TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/Blueprints/HUD.HUD_C'");//动态加载UI 类
UClass* MyUIClass = LoadClass<UUserWidget>(nullptr, *UiPath);
if (MyUIClass)
{
UUserWidget* MyUI = UWidgetBlueprintLibrary::Create(GWorld, MyUIClass, nullptr);//创建蓝图添加到窗口
MyUI->AddToViewport();
}

/*
注意:

1. 需要包含头文件:
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetBlueprintLibrary.h"

2. 两种方法都得在当前项目的 .Build.cs 文件中检查是否有包含 "UMG" 模块,没有的话需要添加上去,这样才能编译通过;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "UMG" });
(看到最后一个没有~)
*/

在 C++ 代码中设置 GameMode

1
2
3
4
5
6
7
8
9
10
// TestGameGameMode 构造函数中设置对应的 GameMode 的参数,当然要先引入下面对应类的头文件
ATestGameGameMode::ATestGameGameMode()
{
GameStateClass = ATestGameState::StaticClass();
PlayerControllerClass = ATestPlayerController::StaticClass();
PlayerStateClass = ATestPlayerState::StaticClass();
HUDClass = ATestHUD::StaticClass();
DefaultPawnClass = ATestPawn::StaticClass();
SpectatorClass = ATest_SpectatorPawn::StaticClass();
}

C++类 加载 蓝图类

1
2
3
4
5
6
7
8
9
10
11
FString CharacterClassLoadPath = FString(TEXT("Blueprint'/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter.ThirdPersonCharacter_C'"));	// 路径
DefaultPawnClass = LoadClass<AMyProject02Character>(NULL, *CharacterClassLoadPath); // 动态加载

// 这里需要注意这个 TEXT 里面的规则... 真的是很烦...
// Blueprint'路径';最后的蓝图类是 blueprintname.blueprintname_C

// 注意字符串的拼接
FString s = "Blueprint'";
s.Append(ClassPath.ToString()); // ClassPath 是 FSoftClassPath
s.Append("'");
UClass* BP_ProjectileClass = LoadClass<AProjectile>(NULL, *s);

创建销毁

创建组件,初始化组件,使用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*--------------- .h 文件-----------------*/ 	
UStaticMeshComponent *VisualMesh = nullptr;


/*--------------- .cpp 文件-----------------*/
// 一般在构造函数里面做
VisualMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisualMesh")); // 创建组件

// 把 VisualMesh 作为根组件
RootComponent = Cast<USceneComponent>(VisualMesh);

// 将组件附在另一个组件下面
Box->SetupAttachment(VisualMesh);

// 设置碰撞预设
VisualMesh->SetCollisionProfileName(TEXT("BlockAllDynamic"));

// 设置模型(注意,ConstructorHelpers::FObjectFinder 只能在构造函数中用,否则会崩溃)
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeVisualAsset(TEXT("/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube")); // 注意路径格式
if (CubeVisualAsset.Succeeded())
{
VisualMesh->SetStaticMesh(CubeVisualAsset.Object);
}

// 设置位置
VisualMesh->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));

创建、销毁Actor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
AMyBox *mybox = GetWorld()->SpawnActor<AMyBox>(SpawnLocation, SpawnRotation);	// 创建
mybox->destroy(); // 销毁

// 创建一个特效类对象,附在MyBox类对象上面(这里的this是MyBox的对象)
AMyEffect *MyEffect = GetWorld()->SpawnActor<AMyEffect>();
MyEffect->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform);

// 延迟创建 Actor,用于完整的创建,放置还未初始化就被销毁的情况
AProjectile* NewProjectile = Cast<AProjectile>(UGameplayStatics::BeginDeferredActorSpawnFromClass(this, BP_ProjectileClass, SpawnTransform, ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn));
if (NewProjectile)
{
// 在这里就是完成创建之前要做的事情
NewProjectile->SetOwner(this);
NewProjectile->Init(this);
}
UGameplayStatics::FinishSpawningActor(NewProjectile, SpawnTransform);

粒子效果的使用实例

1
2
3
4
5
6
7
8
9
10
11
class UParticleSystem* PickUpFX;
class UParticleSystem* StaticFX;

// Actor 消失,特效不消失
UGameplayStatics::SpawnEmitterAtLocation(this, PickUpFX, GetActorLocation());

// MeshComp 是一个组件,特效附着在组件上面,Actor销毁,特效也销毁
UGameplayStatics::SpawnEmitterAttached(StaticFX, MeshComp);

// 这一篇将了随着Actor销毁,特效销毁/不销毁 的两种做法。
https://blog.csdn.net/weixin_36728104/article/details/82893670

数学计算

计算两个位置的距离

1
FVector::Distance(FVector1, Fvector2);

随机数的使用

1
2
#include "Kismet/KismetMathLibrary.h"
float RandomX = UKismetMathLibrary::RandomFloatInRange(MIN_X, MAX_X);

时间的使用

1
2
3
4
5
6
7
8
9
10
11
12
// 获取当前的游戏时间
double CurrentTime = FDateTime::Now().GetTimeOfDay().GetTotalMilliseconds();

// 输出年月日
FDateTime tt = FDateTime::Now();
int year = tt.GetYear();
int month = tt.GetMonth();
int day = tt.GetDay();
int hour = tt.GetHour();
int minute = tt.GetMinute();
int second = tt.GetSecond();
UE_LOG(LogTemp, Error, TEXT("Time : %d, %d, %d, %d, %d, %d"), year, month, day, hour, minute, second);

定时器的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 声明一个定时器 Handle,用于特效对象的延迟销毁
FTimerHandle TimerHandle;
GetWorldTimerManager().SetTimer(TimerHandle, this, &AMyEffect::OnDestroyed, 2.0f, false);

// 检测定时器是否活跃
GetWorldTimerManager().IsTimerActive(M_TimerHandle) == true;

// 清楚定时器
GetWorldTimerManager().ClearTimer(M_TimerHandle);

// 这个可以看一下官方文档
https://docs.unrealengine.com/4.27/zh-CN/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/Timers/

// 但其实关于定时,我们可以通过记录时间的方式来实现,这样更加简单。

AI

运行行为树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	UPROPERTY()
class UBehaviorTreeComponent* M_BehaviorTree;

UPROPERTY()
class UBlackboardComponent* M_Blackboard;

UPROPERTY(EditAnywhere, Category = "BaseConfig")
class UBehaviorTree* BTree = nullptr;

UBehaviorTree* AAICharacter_Base::GetBTree()
{
return BTree;
}

void AAIController_Base::RunAIBehaviorTree(APawn* InPawn)
{
AAICharacter_Base* AI = Cast<AAICharacter_Base>(InPawn);
if (AI && AI->GetBTree())
{
M_Blackboard->InitializeBlackboard(*AI->GetBTree()->BlackboardAsset); // 初始化AI中的黑板,黑板是在行为树中的。
M_BehaviorTree->StartTree(*AI->GetBTree()); // 开始运行行为树
}
}

使用感知组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AIPerceptionComponent.h"

UPROPERTY()
class UAISenseConfig_Sight* SightConfig = nullptr;

UPROPERTY()
class UAIPerceptionComponent* M_AIPerception = nullptr;

// 构造函数
AAIController::AAIController()
{
M_AIPerception = CreateDefaultSubobject<UAIPerceptionComponent>("AIPerception");
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("AISightConfig"));
if (M_AIPerception)
{
M_AIPerception->ConfigureSense(*SightConfig);
M_AIPerception->SetDominantSense(SightConfig->GetSenseImplementation());
}
}

// 注意绑定感知函数不能在构造函数中,否则不生效,所以放在 BeginPlay 里面
void AAIController::BeginPlay()
{
Super::BeginPlay();

if (M_AIPerception)
{
M_AIPerception->OnTargetPerceptionUpdated.AddDynamic(this, &AAIController::OnTargetPerceptionUpdated);
}

UAIPerceptionSystem::RegisterPerceptionStimuliSource(this, UAISense_Sight::StaticClass(), this);
}


// 设置 AI 感知组件的参数
void AAIController_CloseCombat::InitAIPerception(APawn* InPawn)
{
// 写自己的设置参数逻辑
// ......

// 注意设置完需要重新配置一下,否则不生效!!!
M_AIPerception->ConfigureSense(*SightConfig);
}

枚举的使用

1
2
3
4
5
6
7
8
9
10
11
UENUM(BlueprintType)
enum class ECampType : uint8
{
NONE,
RED = 1 UMETA(DisplayName = "Red"),
BLUE = 2 UMETA(DisplayName = "Blue"),
};

// 枚举转换成字符串
UEnum* const CampTypeEnum = StaticEnum<ECampType>();
FString s = CampTypeEnum->GetDisplayNameTextByValue(static_cast<uint8>(ECampType::RED)).ToString();

遍历 TMap

1
TMap<FString, int>::TConstIterator iter = _Map.CreateConstIterator(); iter; ++iter)

问题

导入项目时,vs显示不支持?

1
2
3
答:vs版本不匹配,可以去网上找找 vs 和 ue 版本的匹配程度;我是:
ue 4.24 <=> vs 2017
ue 4.27 <=> vs 2022

换了之后 vs 编译时显示 sdk 有问题?

1
2
3
4
5
6
7
8
9
这个搞了好久...盲猜删2017的时候给删掉了...

报错信息:
/*
Could not find NetFxSDK install dir; this will prevent SwarmInterface from installing. Install a version of .NET Framework SDK at 4.6.0 or higher.
*/

参考这篇文章:
https://zhuanlan.zhihu.com/p/133456753

欢迎关注我的其它发布渠道