UE4基础

First Post:

Last Update:

Word Count:
3.1k

Read Time:
13 min

UE4基础

文档:https://docs.unrealengine.com/4.27/en-US/

UE4项目目录结构

Binarires:存放编译生成结果的二进制文件

Config:存放当前项目的配置文件(中医)

Content:存放平常最常用到所有的资源和蓝图(重要)

Intermediate:中间文件(gitignore),存放一些临时生成的文件

Save:存储自动保存的文件,日志文件,引擎崩溃日志,硬件信息,烘焙信息数据等。

Source:C++源码文件(重要)

DerivedDataCache:渲染缓存,C盘不够的情况下,会将缓存文件存储在该目录下。

编译类型

编译类型分为两种:

Editor:编辑器型与UE4Editor配合编译便于制作开发

Game:游戏型可以直接从源代码运行游戏不关心编辑端

1
2
3
4
5
DebugGame:游戏调试(只适合游戏代码)
DebugGame Editor:游戏调试时通过UE4Editor进行调试
Development:游戏开发型调试(适合查看源代码执行)
Development Ediotr:游戏开发型调试在UE4Editor中进行(默认情况)
Shipping:发现模式没有了调试辅助

编译系统

UE4支持全平台开发,为了方便开发者使用同一套代码在全平台下发布,所以UE4使用了自定义编译系统。他也可以保证开发者的代码包括在UE4Editor下的参数设定,可以全平台适用。

这套自定义编译系统是在VS编辑器后台通过命令行默默执行的,他最重要的两个工具是UBT和UHT。

UBT(Unreal Build Tools)

* ue4自定义的一种编译工具
* 他使用的是C#语言
* 用来逐步编译Engine的代码模块和项目的代码模块
* 对项目的C++代码来说主要用于处理项目对引擎功能模块的依赖

UHT(Unreal Header Tool)

* ue4 对项目C++代码的解析工具
* 它可以将项目的C++代码翻译成UE4 Editor认识的特殊编码
* 它主要是为了给UE4Editor提供C++代码的可视化功能

C++基础

foreach

1
2
3
4
int numbers[] = {1, 2, 3, 4, 5};
for (auto c : numbers) {
cout << c << endl;
}

智能指针

auto_ptr

1
2
3
4
5
6
7
8
9
void TestAutoPtr() {
auto_ptr<Myclass> p(new Myclass);
if(nullptr != p.get) {
p.get()->member = "aaa"; // 当赋值给另一个智能指针时,原来的智能指针无效
p->Print();
// p->member = "aaa"; // 不建议使用
// p.release(); // 会返回 Myclass*,需要手动释放指向的内存。
}
}

shared_ptr

1
2
3
4
5
6
7
8
shared_ptr<int> p1(new int(10));
shared_ptr<int> p1;
p1 = make_shared<int>(10);


// shared_ptr<int> p2(p1); //拷贝构造
// shared_ptr<int> p3(move(p1));// 移动构造
// p2.use_count(); // 返回使用次数

wake_ptr

1
2
3
4
5
6
7
8
wake_ptr<int> pw1;
shared_ptr<int> ps1 = make_share<int>(1);
pw1 = ps1; // 不会增加ps1的引用计数
// 注意:因为弱引用指针不会增加计数器,为了程序安全,不能作为返回值。一般用作形参。
// 在使用的时候需要锁住对象,避免对象释放掉了再使用。
if(ps1.expired()) { // 表示安全
*(ps1.lock().get()) = 10; // lock返回共享指针,并对里面的值进行赋值。
}

unique_ptr

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
45
46
47
```



### Lamda

```c++
#include<iostream>
#include<functional>

using namespace std;
int main() {

auto f = [](int a, int b) {
return a + b;
};
cout << f(1, 2) << endl;

int a = 4;
auto f2 = [=](int b) { // =代表值捕获当前作用域内的所有变量
return a + b;
};
cout << f2(2) << endl;

auto f3= [&](int b) { // &代表地址捕获当前作用域内的所有变量
return a + b;
};
cout << f3(2) << endl;


int c = 5;
auto f4= [=, &c](int b) { // 值捕获全部变量,c进行地址捕获
return a + b + c;
};
cout << f4(2) << endl;

// 采用function来获取返回值调用
function<int(int, int)> f5 = [&](int a, int b)-> int {
return a + b + c;
};
cout << f5(1, 2) << endl;

// 直接调用
cout << [=](int a, int b)-> int {
return a + b + c;
}(1, 2);
}

UE4 C++ 基础

命名规则

虚幻引擎封装了许多自定义的数据类型:类、结构、枚举;

自定义了所有的数据结构:动态数组、链表、集合、图、各种字符串操作;

对自己的封装形式做了如下区分:

A字母开头:表示当前类对象来自于AActor

U字母开头:表示它一定继承于UObject

F字母开头:大量使用在结构体当中,也有很多类使用他开头,如果类使用F开头,表示当前类不会继承UObject;

I字母开头:表示当前对象是接口类型;// 继承了大量的纯虚函数,没有实现,需要自己实现。

E字母开头:表示当前类型是枚举;

T字母开头:表示模板类,一般用于数据结构定义,多线程安全类;

如果字母全部大写,那么表示宏定义出来的。

打印日志

虚幻引擎日志分为两类:

终端输出

屏幕输出

需要注意的是, 虚幻引擎本身采用的是UTF-16字符编码集,Windows采用的是Unicode,两个字符集不通用,只有ASCII码表可以交互,对中文输出是乱码。

终端输出

虚幻引擎将日志分为三类:

Message(描述一般信息)

Warrning(描述警告,一般为黄色)

Error(描述发送致命错误,红颜色)

向终端输出函数:

UE_LOG(日志分类, 日志种类, 字符串格式化, 字符串参数, …)

1
2
3
UE_LOG(LogTemp, Warning, TEXT("开始"));
UE_LOG(LogTemp, Display, TEXT("display"));
UE_LOG(LogTemp, Warning, TEXT("The Actor's name is %s"), TEXT("Myactor"));

自定义log类型:

头文件中声明

1
DECLARE_LOG_CATEGORY_CLASS(MyLog, Log, All); // 自定义log类型

cpp文件中实例

1
2
3
DEFINE_LOG_CATEGORY_CLASS(AMyActor, MyLog);
// 使用
UE_LOG(MyLog, Display, TEXT("display"));

屏幕输出

1
void UEngine::AddOnScreenDebugMessage(int32 Key, float TimeToDisplay, FColor DisplayColor, const FString& DebugMessage, bool bNewerOnTop, const FVector2D& TextScale)

Key:缩进, -1自动计算

TimeToDisplay:显示多少秒

DisplayColor:打印颜色

DebugMessage:打印消息

bNewerOnTop:是不是从新的Top开始打印

TextScale:字体的缩小放大

1
2
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("InputComponent Enabled!"));
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("Test!"), true, );

运行顺序

编译器运行: 构造函数,PostInitProperties

运行期运行:函数,PostInitProperties

AMyActor

在编译完成后调用和运行时调用

先执行构造函数:引擎编译成功时就会调用对象的构造函数。这时为了与蓝图挂钩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AMyActor::AMyActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
UE_LOG(LogTemp, Warning, TEXT("构造"));
auto pSecen = CreateDefaultSubobject<USceneComponent>(FName(TEXT("Root")));
SetRootComponent(pSecen);
APlayerController *pc = UGameplayStatics::GetPlayerController(GetWorld(), 0);
EnableInput(0);
if(nullptr != InputComponent)
{
GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("InputComponent Enabled!"));

}
}

绝对不要再里面写一些空指针内存错误,会导致Editor打不开。

PostInitProperties

在编译完成后调用和运行时调用,对Actor所有属性进行初始化,编辑器类属性

重写

1
virtual void PostInitProperties() override;
1
2
3
4
void AMyActor::PostInitProperties()
{
Super::PostInitProperties(); // 需要进行调用父类函数
}

PostInitializeComponents

在运行期调用,早于BeginPlay

1
2
3
4
5
void AMyActor::PostInitializeComponents()
{
Super::PostInitializeComponents();
}

BeginPlay

游戏启动时调用

1
2
3
4
5
// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
Super::BeginPlay();
}

Tick

每一帧都调用,该函数的执行会取决于PrimaryActorTick.bCanEverTick,一般在构造函数中会设置该值为true;

1
2
// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
1
2
3
4
5
// Called every frame
void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}

EndPlay

在结束运行的时候调用,释放对象的时候,在该函数中进行释放。

1
virtual  void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
1
2
3
4
5
6
7
void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// 一定要在调用Super::EndPlay(EndPlayReason);之前,先释放对象


Super::EndPlay(EndPlayReason);
}

BeginDestroy

在结束运行的时候调用,在EndPlay之后调用。

1
virtual  void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
1
2
3
4
void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
}

设置类变量属性

1
2
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FLValues")
FString myText = TEXT("Hello World!");

字符串

虚幻采用UTF-16的编码格式,他使用的字符类型为TCHAR,向外打印输出一定采用TCHAR类型。

将Ascii码转tchar

1
TCHAR * chars = ANSI_TO_TCHAR("1234");

将TCHAR转char

1
ANSICHAR* ansic TCHAR_TO_ANSI(chars);

转为UTF-8

1
TCHAR_TO_UTF8()

采用TEXT可直接将字符串转换为TCHAR类型。

1
TEXT("hello")

UE4定义的字符串类解释

FName:用于描述资源名称或路径,对于组件或Actor命名的时候用到它,只读属性。

FText:UE4本地化字符类型,用于描述不同操作系统下的统一字符类型,只读属性。

FString:标准TCHAR动态字符串。

FString

初始化

1
FString a(TEXT("你好"));

追加

1
2
3
a += TEXT("  ");      // 追加
a.Append(TEXT("!")); // 推荐,追加
a.InsertAt(2, TEXT("%")); // 插入操作

判断字符串是否存在

1
2
3
4
5
6
// UE_NODISCARD FORCEINLINE bool Contains(const TCHAR* SubStr, ESearchCase::Type SearchCase = ESearchCase::IgnoreCase, 
// ESearchDir::Type SearchDir = ESearchDir::FromStart ) const
if(a.Contains(TEXT("%")))// 查找子字符串是否存在,默认忽略大小写
{// 存在
}

查找字符串

1
2
3
4
5
6
7
8
9
10
11
// 查找字符串
//UE_NODISCARD FORCEINLINE int32 Find( const FString& SubStr, ESearchCase::Type SearchCase = ESearchCase::IgnoreCase,
// ESearchDir::Type SearchDir = ESearchDir::FromStart, int32 StartPosition=INDEX_NONE ) const
int idx = a.Find(TEXT("%"), ESearchCase::CaseSensitive, ESearchDir::FromStart, 0);
if(idx >= 0)
{
// 存在,子字符串起始位置idx
}else
{
// 不存在
}

删除字符串

1
2
// FORCEINLINE void RemoveAt(int32 Index, int32 Count = 1, bool bAllowShrinking = true)
a.RemoveAt(0, 1, true);

判断是否为空

1
a.IsEmpty();

获取长度

1
a.Len();

将int转化为FString

1
2
FString s = FString::FormatAsNumber(1);

将FString转化为int

1
int n2 = FCString::Atoi(TEXT("123"));

将float类型转换为FString

1
FString::SanitizeFloat(2.0f);

将FString类型转换为float

1
float n = FCString::Atof(TEXT("1.2"));

格式化字符串

1
2
FString FString::Format(const TCHAR* InFormatString, const FStringFormatNamedArguments& InNamedArguments);
FString FString::Format(const TCHAR* InFormatString, const FStringFormatOrderedArguments& InOrderedArguments);

例子

1
2
3
4
TArray<FStringFormatArg> args;
args.Add(TEXT("你好"));
args.Add(0);
FString out = FString::Format(TEXT("FString fomat str={0}, num={1} "), args);

FName

在构造时进行赋值

1
FName name(TEXT("RootComponent"));

FString对FName进行赋值

1
2
name = *FString(TEXT("FString"));
name = *FString(TEXT("/Game/MobileStarterContent/Maps/StarterMap.StarterMap"));

将FName转换为FString

1
FString out2 = name.ToString();

FText

在做UI编程的时候,FText比较常用。

1
2
3
FText text = FText::AsNumber(1.0f);
text = FText::FromString(FString(TEXT("dddd")));
FString fs = text.ToString();

UE4 C++数据结构

TArray

初始化

1
2
TArray<int32> arr;
arr.Init(2, 10); // 初始化值,和初始化个数

添加元素

1
2
3
4
5
6
7
8
9
arr.Add(123); // 可能拥有内存开辟和复制操作,
arr.Emplace(35); // 采用引用右值技术,创建一个新实例,当元素数量较大时,效率较高。
arr.Push(100); // 对Add于Emplace进行重载,根据大小情况进行选择
arr.Num(); // 获取元素长度
arr.Remove(123); // 删除123元素
arr.RemoveAt(0, 3, true); // 从0开始,删3个,删除完之后,是否归还空间。
arr.Empty(); //删除所有内容
//条件删除
arr.RemoveAll([](const int32 val) { return val > 2; }); // 删除大于2的元素

采用foreach 循环

1
2
3
4
for (auto c : arr)
{
GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, FString::FormatAsNumber(c), true, FVector2D(2.0f, 2.0f));
}

采用迭代器循环,利用迭代器效率最高。

1
2
3
4
5
6
7
8
9
10
auto pFunc = [](TArray<int32> arr)->void
{
if(arr.Num() > 0)
{
for(TArray<int32>::TConstIterator it = arr.CreateConstIterator(); it; ++it)
{
UE_LOG(MyLog, Display, TEXT("Arr = %d"), *it);
}
}
};

组件

创建组件

1
2
USceneComponent *root = CreateDefaultSubobject<USceneComponent>(FName(TEXT("Root")));
SetRootComponent(root);

创建对象

1
NewObject<UObject>();

UMG

UMG:(Unreal Motion Graphics UI Designer)****虚幻示意图形界面设计器

是一个可视化的UI创作工具,可以用来创建UI元素,如游戏中的HUD、菜单或您希望呈现给用户的其他界面相关图形。UMG的核心是控件,这些控件是一系列预先制作的函数,可用于构建界面(如按钮、复选框、滑块、进度条等)。这些控件在专门的控件蓝图中编辑,该蓝图使用两个选项卡进行构造:设计器(Designer)选项卡允许界面和基本函数的可视化布局,而图表(Graph)选项卡提供所使用控件背后的功能。

ref:https://docs.unrealengine.com/4.27/zh-CN/InteractiveExperiences/UMG/

HUD:(Head Up Display)

安卓打包

ref: https://zhuanlan.zhihu.com/p/385138133

Edit->Project Settings->Packaging->Project->Build Configureation设置为Shipping (发行模式)。

常用API

获取第一个PlayerController

1
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();

获取鼠标屏幕上的位置

1
2
FVector2D MousePos = FVector2D(0, 0);
PlayerController->GetMousePosition(MousePos.X, MousePos.Y);

获取鼠标在世界里的位置

1
2
FVector MouseLocation, MouseDirection;
PC->DeprojectMousePositionToWorld(MouseLocation, MouseDirection);

在一定范围内返回差值

1
float XDirection = FMath::Clamp( ObjLocation.X - MousePos.X, -1.0f, 1.0f);

实现对象在X轴上的移动

1
2
FVector Direction = FVector(XDirection, 0, 0);
AddMovementInput(Direction);
打赏点小钱
支付宝 | Alipay
微信 | WeChat