版权声明:本文为CSDN博主「杰森大师」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
ConstructorHelpers 类
需包括头文件
#include "UObject/ConstructorHelpers.h"
应为静态加载只能在 构造函数 中添加
FObjectFinder
用于加载材质和模型等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;
public:
FORCEINLINE 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
用蓝图去继承当前父类去实现蓝图
BlueprintType
在其他的蓝图里面把这个类当作一个变量来使用
meta
中DisplayName
代表的是它在蓝图中搜索或者在default
编译界面中显示的名字,ClampMin
和ClampMax
分别代表最小最大值,TodTip
代表提示,还有一个EditCondition
填入bool
值,如果这个bool
值为真才可以填写
全局类 UGameSingleton(游戏单例)
GEngine->GameSingleton
, 一个 GEngine
下的 UObject
指针,UE4 专门提供出来作为全局变量给所有对象调用, UE4 建议放置不需要修改数据的对象
举个例子
首先,需创建一个 UTESTObject
类
接着,蓝图中创建继承这个类的对象
自己定义参数
后面到 Project Setting 项目设置里面去找到
当你想在其他的类中调用这些全局变量时,需要进行强转
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;
}
}
CPU 级别的内存分区
一个程序分为 3 个基本段:text 段,data 段,bss 段
text 段在内存中被映射为只读,但 data 段与 bss 段是可写的。
- text 段:代码段,就是放程序代码的,编译时确定,只读;
- data 段:存放在编译阶段(而非运行时)就能确定的数据,可读可写。也就是通常所说的静态存储区,赋了初值的全局变量和赋初值的静态变量存放在这个区域,常量也存在这个区域;
- bss段:已经定义但没赋初值的全局变量和静态变量存放在这个区域。
两者之间区别
text(代码)段,data(数据)段,bss(堆栈段) 是 CPU 级别的概念,而前头提到的五大分区属于 C++ 语言级别的概念,两者是不同的概念。
存储类型关键字定义变量 与 函数作用域 与 生命周期
- auto 变量:函数的局部变量,如果没有声明为
static
, 函数中定义的局部变量全部为auto
类型,auto
变量包括未加static
声明的局部变量和函数的形参。在函数调用时系统会给它们分配存储空间,在函数调用结束后会自动释放这些空间。属于动态存储方式。 - static 变量:用
static
声明的局部变量在调用结束后不会消失而保存原来的值。static
局部变量定义使用后值会存储下来。所以使用static
局部变量定义只需要一次赋值。静态局部变量的作用域仅限于所定义的函数。但函数结束后变量的值会保留。直到整个程序运行结束。全局变量从定义开始作用于整个文件直至程序运行结束。 - register 寄存器变量:寄存器变量可以提高 C 语言的执行效率,即将局部变量的值存入 CPU 的寄存器中。需要注意的是!!!
- 只有 动态存储的变量(自动局部变量和形参)才可以作为寄存器变量来存储,局部静态变量 不可以定义为寄存器变量。
- 计算机的寄存器数目是有限的,所以不能定义任意多个寄存器变量。
- extern 外部变量:即全局变量的外部表现形式,是在函数外部定义的变量。全局变量的作用域为从定义开始到源文件结束。
extern
对该变量作外部变量声明,扩展变量作用域。
UE4 制作游戏的流程
- 首先肯定需要一个游戏策划书,把游戏需要制作什么模块功能告诉你,然后与美术确定好相关功能后根据先将 UE4 UMG 模块相关功能写好,就基础的游戏 GamePlay 框架写好,将美术需要用到的参数都调用到蓝图窗口让他们进行调试。
- 具体化游戏互动逻辑的实现。
- 实现逻辑后完善游戏物理引擎,使得游戏更加真实。
- 多线程管理、内存管理、垃圾回收的机制的实现,优化游戏性能,让手机电脑更流畅的运行游戏。
- 美术上的图形渲染优化,同理 4。
- 添加联网模块,实现联网机制。
- 调试游戏 BUG ,测试游戏的可玩性,继续对相关 BUG 进行优化。