CubeVisualAsset(TEXT("资源路径"));if.._setcolorandopacity">
赞
踩
个人笔记,记录一些之后可能会忘记来查的小点,因为时间跨度比较长,所以内容从浅到深都有,大的点会贴一些觉得比较好的文章,或者自己另外写一篇博客
注释: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
https://blog.csdn.net/jk_chen_acmer/article/details/115001623
快捷菜单: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)
编辑器内热更新代码:Ctrl+Alt+F11
快速批量修改资源属性:
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
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 )
开启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);
构造函数内创建组件
USceneComponent* P = CreateDefaultSubobject<USceneComponent>(TEXT("Name"));
RootComponent = P;
P->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
设置组件的相对位置
SetRelativeLocation(FVector)
动态创建组件,有两个接口AActor::AddComponent和AActor::AddComponentByClass,都可以通过将bDeferredFinish改为false,进行属性的设定。
删除创建的组件: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"));
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;
}
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]
环境内各个元素的作用:
Ctrl+L+鼠标移动:修改 DirectionalLight 的光照方向
PointLight、SpotLight可以修改 Source Radius 让阴影变得柔和,RectLight则是调整 Source Width 和 Source Height
开启光追:Project Setting
去除光照贴图:World Setting - Force No Precomputed Lighting:True,再Build Light Only
关闭光照:Affects World:False
环境配置工具:Windows - Env.Light Mixer
材质的Basic Color中的V值,是颜色自身的光线反射率,也就是Max(R,G,B)
材质快捷键
可以通过颜色*实数(光照强度)连接emissive,实现自发光,但推荐仅用于点缀
柔和光源造成的影子:Cast Ray Traced Shadows:Enabled,如果有噪点,将Samples Per Pixel改为4
SkyLight:
禁用自动曝光:
去除房间内蓝光:
增加光的体积感:调高DirectionalLight 的 Volumetric Scattering Intensity可以做到全局的,局部使用其它光源也可以
配置只有走到光方向的位置(窗口)才能看到体积光(不调也可以看需求):调高ExponentialHeightFog的Scattering Distribution
解决Lumen下有一些时隐时现 / 抖动的地方:
星空
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)
静态加载(构造函数内使用,所以路径只能写死)
其实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)
输出Log1
// 带端前缀
UKismetSystemLibrary::PrintString(GetWorld(), TEXT(""), true, false);
#include "Engine/Engine.h"
// 五秒。
// 参数中的-1"键"值类型参数能防止该消息被更新或刷新。
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Hello World"));
输出Log2
UE_LOG(LogTemp, Log, TEXT("Change Text By Cpp"));
UE_LOG(LogTemp, Log, TEXT("UseSpell: %d"), cooling.Num());
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)))
创建UserWidget类
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UUserWidget> PlayerAndStateUI;
UPlayerStateAndSkillWidget* widget;
widget = CreateWidget<UPlayerStateAndSkillWidget>(GetWorld(), PlayerAndStateUI);
widget->AddToViewport();
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);
设置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);
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);
UE_LOG FString
UE_LOG(LogTemp, Log, TEXT("%s"), *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));
查看引擎版本号: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
配置文件
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 操作:参考
打印堆栈信息
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);
前置声明
UE4 重定向模块名或者类名(加入到Config/DefaultEditor.ini),参考
[CoreRedirects]
+PackageRedirects=(OldName="/Script/PLUGINA", NewName="/Script/PROJECTA")
+ClassRedirects=(OldName="/Script/PROJECTA.ClassA",NewName="/Script/PROJECTA.ClassB")
UINTERFACE(Blueprintable)
class UReactToTriggerInterface : public UInterface
{
GENERATED_BODY()
};
class IReactToTriggerInterface
{
GENERATED_BODY()
public:
/** 对激活该对象的触发器体积做出响应。如果响应成功,返回“真(true)”。*/
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category="Trigger Reaction")
bool ReactToTrigger() const;
};
纯虚函数
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
/ | 权威 | 主控 | 模拟 |
---|---|---|---|
GameInstance | Yes | Yes | Yes |
GameMode | Yes | No | No |
GameState | Yes | Yes | Yes |
PlayerController | Yes | Yes | No |
PlayerState | Yes | Yes | Yes |
UE Network 基础
传递函数:
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);
};
(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);
命令行打包,命令参考
打包后LoadObject失败:
蓝图内修改属性后自动做出调整
#if WITH_EDITOR && USE_EQS_DEBUGGER
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif //WITH_EDITOR && USE_EQS_DEBUGGER
蓝图可访问该私有变量:meta = (AllowPrivateAccess = “true”)
开启 Nanite:在StaticMesh上右键 - Nanite - Enable
查看 Nanite 三角面:Lit - 改为Nanite Visualization - Triangles
后处理体积
运行时显示碰撞: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);
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:自己找找为什么符号没有被找到,可能是以下原因
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++中使用struct
USTRUCT的使用
UStruct 与 Traits
自定义结构体序列化
自定义结构体序列化
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, }; };
使用:
《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);
https://blog.csdn.net/weixin_43844254/article/details/99752599
https://zhuanlan.zhihu.com/p/369974105
《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 );
}
https://blog.csdn.net/qq_29667889/article/details/109307203
https://zhuanlan.zhihu.com/p/41332747
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
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。