【转载】UE4 常用到的 C++ 类、函数、宏定义、内存等知识点

1,127 阅读7分钟

版权声明:本文为CSDN博主「杰森大师」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/weixin_4229…

ConstructorHelpers 类

需包括头文件

#include "UObject/ConstructorHelpers.h"

应为静态加载只能在 构造函数 中添加

  1. FObjectFinder 用于加载材质和模型等
  2. FClassFinder 用于加载其他类,因格式问题需在复制地址后加入 _C 或者改成 /Game/Character/NewAnimBlueprint,否则会出 BUG 一开就崩溃
ABaseCharacter::ABaseCharacter()
{
    static ConstructorHelpers::FObjectFinder<USkeletalMesh> Skelel(TEXT("在蓝图中复制其地址"));
    static ConstructorHelpers::FClassFinder<UAnimInstance> BaseChaGameInstance(TEXT("/Game/Character/NewAnimBlueprint"));
    if (GameCharacterSkelel.Succeeded())
    {
        GetMesh()->SetSkeletalMesh(GameCharacterSkelel.Object);
        GetMesh()->SetAnimInstanceClass(BaseChaGameInstance.Class);
    }
}

ConstructorHelpers 内只有一个命名空间 ConstructorHelpersInternal 和一个Struct

GameplayStatics类

需包括头文件

#include "Kismet/GameplayStatics.h"

我们一般在蓝图中调用 GetPlayerController 或者 GetPlayerCharacter 时,直接写就好了,而在 cpp 中 要使用 GameplayStatics:: 来调用,而 GameplayStatics 是一个函数库,调用起来也很方便

UGameplayStatics::GetPlayerController(GetWorld(), 0)->bShowMouseCursor = true;
UGameplayStatics::OpenLevel(GetWorld(), TEXT("/Game/MAP/ExeciseMap"));
ACharacter *BaseCharacter = UGameplayStatics::GetPlayerCharacter(this, 0);

没事可以多 Alt+G 翻翻源码观看

TimeHandle 类

.h

/**技能定时器*/
FTimerHandle Skill2Timer;
/**总共CD时间*/
float Skill2TimerAllCD;
/**现在CD时间*/
float Skill2TimerCurrentCD; 

/**调用回调函数*/
void Fire();

/**声明回调函数*/
void Skill2TimerCallback();

.cpp

void AGamePlayerController::Fire()
{
    /**开启定时器*/
    Character->GetWorldTimerHandle().SetTimer(Skill2Timer, this, &AGamePlayerController::Skill2TimerCallback, 1.f, true);
    Skill2TimerCurrentCD = Skill2TimerAllCD;
}

void AGamePlayerController::Skill2TimerCallback()
{
    if (Skill2TimerCurrentCD == 0.0f) ///< 时间到
    {
        /**自己去 UMG 里面创建一个*/ 
        SkillUserWidget->MsgText2->SetVisibility(ESlateVisibility::Hidden);
        /// @note 清理定时器
        SoldierCharacter->GetWorldTimerManager().ClearTimer(Skill2Timer);
    }
    else
    {
        SkillUserWidget->MsgText2->SetVisibility(ESlateVisibility::Visible);
        SkillUserWidget->MsgText2->SetText(FText::FromString(FString::FromInt(Skill2TimerCurrentCD)));
        Skill2TimerCurrentCD--; ///< 倒计时
    }
}

FORCEINLINE(内联函数)

我们为什么要用内联函数?

内联函数是代码被插入到调用者代码中的函数,和 #define 宏定义一样,内联函数通过避免被调用的开销来提升执行效率,说起来它和宏很像,但是 宏定义是由预处理器对宏进行代替 ,而 内联函数通过编译器控制来实现 。但是内联函数不能进行循环条件判断选择

内联函数在 UE4 中的使用

// 创建一个 float 值
float CurrentHP;
publicFORCEINLINE void SetCurrentHP(float CurrentHP){this->CurrentHP=CurrentHP;}
FORCEINLINE float GetCurrentHP(){return CurrentHP;}

在其他类中如果想调用获取获取 CurrentHP 值时

// 先获取内联函数的头文件
#include "BaseGamePlayerState.h"

// 获取控制器
AGamePlayerController* GamePlayerController = Cast<AGamePlayerController>(TryGetPawnOwner()->GetController());

// 获取玩家状态
PlayerState = GamePlayerController->GamePlayerState;

// 设置玩家状态的内联函数 SetCurrentHP
PlayerState->SetCurrentHP(PlayerState->GetCurrentHP());

Blueprintable / BlueprintType / meta

  • Blueprintable 用蓝图去继承当前父类去实现蓝图

image.png

  • BlueprintType 在其他的蓝图里面把这个类当作一个变量来使用

image.png

  • metaDisplayName 代表的是它在蓝图中搜索或者在 default 编译界面中显示的名字,ClampMinClampMax 分别代表最小最大值,TodTip 代表提示,还有一个EditCondition 填入 bool 值,如果这个 bool 值为真才可以填写

image.png

image.png

全局类 UGameSingleton(游戏单例)

GEngine->GameSingleton, 一个 GEngine 下的 UObject 指针,UE4 专门提供出来作为全局变量给所有对象调用, UE4 建议放置不需要修改数据的对象

举个例子

首先,需创建一个 UTESTObject

image.png

接着,蓝图中创建继承这个类的对象

image.png

自己定义参数

image.png

后面到 Project Setting 项目设置里面去找到

image.png

当你想在其他的类中调用这些全局变量时,需要进行强转

UTESTObject*TESTObject = Cast<UTEXTObject>(GEngine->GameSingleton);

现在就可以进行调用了。

UE4 特有的指针和引用

UE4 C++ 指针声明

AActor *变量名;                // 硬指针,场景实际存在对象指针
TSubclassof<AActor> 变量名;    // 硬对象类指针,允许使用类型安全传递 UClass 模板
TSoftObjectPtr<AActor> 变量名; // 软对象指针,在需要时才进行加载
TSoftClassPtr<AActor> 变量名;  // 软对象类指针,同上
TWeakObjectPtr<AActor> 变量名; // 弱指针,可以访问一个对象又不对造成计数加 1 ,一般用于局部变量,无需保存
TSharedPtr<T> 变量名;          // 共享智能指针,不能用于 UObject 对象,因为 UObject 有自己的一套 GC 垃圾回收规则,TSharedPtr 用于自定义的结构体(不继承 UObject )

UE4 C++ 引用声明

TSharedRef<T> 变量名;         // 共享引用,和 TSharedPtr 一样都用于非 UObject ,无法引用空的对象

内存分区讲解(CPU 与 C++)

C++ 的内存分区

代码区(code area)

你们放置函数体的二进制代码

静态/全局数据区(data area)

由 static 修饰的变量和函数体 “” 创建的变量(其中这两者都是在程序运行前就有的,就在程序 exe 里面的二进制)

栈区(stack area)

存放函数的参数值,局部变量都是存放在栈区,栈区默认 2MB 大小

堆区(heap area)

创建 new() 或者 malloc() 新的内存地址的都是存放堆区,默认为 2GB 以下 (这两者是在程序运行后就有的,这两个你可以用反编译去看到它们的内存地址)

常量存储区(constant area)

存放的是常量,是不允许修改的,就是说用 const 修饰的值

举个例子

#include <iostream>

using namespace std;

inline int *a(int s, int x) { return &s; } // 内联函数不调用栈,只复制 return s+10 的代码

static int numbers = 10; // 静态数据区;
const int s = 10;        // 常量数据区
int numberss = 100;      // 全局数据区

int aa(int as) // 函数在代码区
{
    return as; // 形参和返回值为栈区分配的内存空间
}

int main()
{
    int *aaa = new (int);       // 函数的成员变量都在栈区 ,而 new 后的为堆区的,其中堆区的大小为 2GB 以下,就是 2*1024*1024*1024
    int *a = (int *)malloc(10); // malloc 后的为自由存储(堆)区的值
    int s;                      // 局部变量都在栈中,其中栈在 window 中默认是 2MB 。就是 2*1024*1024 的大小

    for (int i = 0; i < 100; i++)
    {
        cout << 100 + rand() % (1000 - 100) + rand() % 10 / 10 << endl;
    }
}

image.png

CPU 级别的内存分区

image.png

一个程序分为 3 个基本段:text 段,data 段,bss 段

text 段在内存中被映射为只读,但 data 段bss 段是可写的。

  • text 段:代码段,就是放程序代码的,编译时确定,只读;
  • data 段:存放在编译阶段(而非运行时)就能确定的数据,可读可写。也就是通常所说的静态存储区,赋了初值的全局变量和赋初值的静态变量存放在这个区域,常量也存在这个区域;
  • bss段:已经定义但没赋初值的全局变量和静态变量存放在这个区域。

两者之间区别

text(代码)段,data(数据)段,bss(堆栈段) 是 CPU 级别的概念,而前头提到的五大分区属于 C++ 语言级别的概念,两者是不同的概念。

存储类型关键字定义变量 与 函数作用域 与 生命周期

image.png

  • auto 变量:函数的局部变量,如果没有声明为 static , 函数中定义的局部变量全部为 auto 类型,auto 变量包括未加 static 声明的局部变量和函数的形参。在函数调用时系统会给它们分配存储空间,在函数调用结束后会自动释放这些空间。属于动态存储方式。
  • static 变量:用 static 声明的局部变量在调用结束后不会消失而保存原来的值。static 局部变量定义使用后值会存储下来。所以使用 static 局部变量定义只需要一次赋值。静态局部变量的作用域仅限于所定义的函数。但函数结束后变量的值会保留。直到整个程序运行结束。全局变量从定义开始作用于整个文件直至程序运行结束。
  • register 寄存器变量:寄存器变量可以提高 C 语言的执行效率,即将局部变量的值存入 CPU 的寄存器中。需要注意的是!!!
    1. 只有 动态存储的变量(自动局部变量和形参)才可以作为寄存器变量来存储,局部静态变量 不可以定义为寄存器变量。
    2. 计算机的寄存器数目是有限的,所以不能定义任意多个寄存器变量。
  • extern 外部变量:即全局变量的外部表现形式,是在函数外部定义的变量。全局变量的作用域为从定义开始到源文件结束。extern 对该变量作外部变量声明,扩展变量作用域。

UE4 制作游戏的流程

  1. 首先肯定需要一个游戏策划书,把游戏需要制作什么模块功能告诉你,然后与美术确定好相关功能后根据先将 UE4 UMG 模块相关功能写好,就基础的游戏 GamePlay 框架写好,将美术需要用到的参数都调用到蓝图窗口让他们进行调试。
  2. 具体化游戏互动逻辑的实现。
  3. 实现逻辑后完善游戏物理引擎,使得游戏更加真实。
  4. 多线程管理内存管理垃圾回收的机制的实现,优化游戏性能,让手机电脑更流畅的运行游戏。
  5. 美术上的图形渲染优化,同理 4。
  6. 添加联网模块,实现联网机制
  7. 调试游戏 BUG ,测试游戏的可玩性,继续对相关 BUG 进行优化。