StoneのBLOG

生活这种事情,从来都是自我陶醉

0%

UE4中需要了解的基础概念

为了记录自己在学习UE引擎过程中遇到的一些疑问点。亦或者是一些值得去记录的知识点。

UE4中常见的知识点

更新历史

  • ~2020/06 入社现在的公司之前
    • 在之前的公司陆陆续续更新了好多东西,有些杂乱倒也还好,今后准备更新更多UE4的内容
  • 2020/06/03 更新一些使用VisualStudio的UE4SetUp内容
  • 2020/08/21 更新UE4中的修饰符(Specifiers)部分

C++部分

C++与C#的不同之处

时隔多年见到C++的第一个违和感是头文件,为什么需要写头文件这个问题我找了一些别人写的代码发现了一下几个规则:

  • 在C++的头文件中SampleCode.h中一般都会预先定义一些东西

    • 需要包含的其他头文件

      1
      2
      3
      #include "Engine.h"
      #include "MyAppUtilities.h"
      ...
    • 定义需要使用的宏 预处理(C++的预处理器需要了解一下

      1
      2
      3
      //一般常量居多?
      #define PI 3.1415926
      ...
    • 定义类,类中包含该有的成员,

      1
      2
      3
      4
      5
      6
      7
      8
      9
      class SampleClass{
      //Attributes or functions
      public:

      protected:

      private:

      }

UE4中的C++不同的地方

1.UCLASS()宏

想要让类与UE4的类库联动的话,就需要这个宏。

UCLASS()大概的使用方法

Sample Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "Engine.h"  //如果需要使用UE的library的话
#include "MyAppUtilities.h"

UCLASS()
class UMyClass : public UObject{
GENERATED_BODY()

public:
//构造函数(公有的?)
UMyClass();

UPROPERTY(BlueprintReadWrite, Category = "MyApp")
int32 IntProp;

UFUNCTION(BlueprintCallable, Category = "MyApp")
float LengthOfSomething(const int32 index);

private:
TArray<int> IntArrayWork;
//标准c++中的数组声明
//std::vector<int> IntArrayWork;
}

对于UCLASS(),如果需要继承UE的类库,则都要加上这个。最好声明的类名也以U开头为好? 但是我觉得这种声明不是跟UE的类库搞混了吗, ~辨识度低我觉得应该没有这么智障的规则~。好的,有这种规则好像。类似的其他的以E,F,I,T,S等等的字母作为变量名的开始。

关于在头文件中添加注释的问题,在UE4的执行中倘若添加了日语(应该中文也一样)的注释,有可能会发生问题。所以在头文件中尽可能的使用英文注释。还有应该避免在各种宏的后面直接追加注释。

就目前的问题来说在UFUNCTION宏的后面直接加入日文注释(UTF-8)的话,会有编译无法通过的问题。所以为了避免上述问题应该尽量:

  • 头文件中使用英文注释
  • 不要直接在各种UE4的宏后面(例如UFUNCTION后)直接添加注释。
UCLASS() 参数的含义

CPPExampleActor.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma once

#include "GameFramework/Actor.h"
#include "CppExampleStruct.h"
#include "CppExampleEnum.h"
#include "CpExampleActor.generated.h"

UCLASS(BlueprintType)
class ACppExampleActor : public AActor{
GENERATED_BODY()

public:
UPROPERTY(BlueprintReadWrite, EdtAnywhere, Category="UE C++ Book")
FCppExampleStruct MyStructProp;

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="UE C++ Book")
ECppExampleEnum Type;

UFUNCTION(BlueprintCallable, Category="UE C++ Book")
float MyActorFunc(const float Input);
};

通过指定UNCLASS()的参数,可以指定类的类型。

  • BlueprintType表示这个类可以作为Blueprint的变量来使用。
    • UPROPERTY()的声明,在这个Actor的Detail面板上可以看到该Category下有声明的MyStructPropType属性。
    • 具体的BlueprintType的使用例子则仍需要调查。BlueprintType类型能做到的事情。
  • 多个参数

    1
    UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )

    这个的使用含义现在还不清楚各自代表着什么意思。

USTRUCT()结构体

CppExampleStruct.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma once

#include "CppExampleStruct.generated.h"

USTRUCT(BlueprintType)
struct FCppExampleStruct {
GENERATED_BODY()

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="UE C++ Book")
float Value;

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="UE C++ Book")
int32 Index;
};

构造体的声明名字最好以F开始。其他的基本上使用方法与类相同。

UENUM()枚举类型

CppExampleEnum.h

1
2
3
4
5
6
7
8
#pragma once

UENUM(BlueprintType)
enum class ECppExampleEnum : uint8 {
None = 0,
Foo,
Bar
};

对于enum class ECppExampleEnum : uint8这种写法有些迷惑。

  • class是为了使枚举类型更安全。为什么安全,参考下面的链接。之后整理。
  • uint8是为了指定枚举器的基础类型。

参考链接:

2.GENERAED_BODY()

在阅读源码的时候会发现还有一个GENERATED_UCALSS_BODY()的宏,这两者之间又有区别。

为什么要使用这个宏呢?

Those macro pastes code generated by UnrealHeaderTool (UHT) contained in HeaderFileName.generated.h to class or struct deceleration, it is required for UObject to properly function. It’s primerly for reflection system, UHT maps your class and generate registration code, so you don’t need to register class, properties and and functions to that system, UHT prepares that for you + it includes helpful functions like StaticClass() which you probably already meet. But it can’t alter how C++ compiler work and you need to paste the code that it prepares via that macro to your class.

In C++ you can’t read structure of you code after compilation, everything is turned in to numbers and addresses, so application it self it need to implement so called reflection system in order to see it’s classes, properties and functions, to see it’s own reflection, like animal having ability to recognize it self in reflection, like name suggests and this is what UE4 and UHT provides. This way for example editor see all classes, properties and nodes based from C++. You can use it in game too to create auto discovery of actor classes and there properties, create smart UI so you don’t need to make new UI for each thing.

The link provided by ali explains the diffrence between the too, but you should use GENERATED_BODY() as other one is old method, it’s still being keeped alive as some of engine code still didn’t switch to new macro.

这段很长,是抄自What do, GENERATED_BODY() and GENERATED_UCLASS_BODY(), do?

当我们定义了GENERATED_BODY()宏之后,构造函数(constructor)就不是必须的了。但是如果需要的话还是可以照常进行声明和定义。

The other difference is that, in comparison to GENERATED_UCLASS_BODY(), GENERATED_BODY() doesn’t have the public specifier in it, so any class members that are declared after it are private by default (you can explicitly declare them as public). In your situation, please make sure that you have your constructor declared and defined correctly:

1
2
3
4
5
6
7
8
9
10
11
12
//.h file
//...
GENERATED_BODY()

public:
AFPSGameMode(const class FObjectInitializer& ObjectInitializer);

// .cpp file
AFPSGameMode::AFPSGameMode(const class FObjectInitializer& ObjectInitializer)
{
// constructor functionality
}

目前GENERATED_BODY()这个使用方式是UE4更推荐的。

3.UPROPERTY() UFUNCTION()

使用这个声明的属性跟方法UE的Blueprint可以使用。

UPROPERTY()宏

UPROPERTY()的参数的含义

  • UPROPERTY()没有参数的情况,UE4的Blueprint和Level Editor都不能读取或者修改,但是却可以将这个变量纳入到UE4的GC对象中。比如:

    1
    2
    UPROPERTY()
    AActor* OwningActor;

    这种情况,如果OwningActor拥有了实例化对象,不手动释放也是没有问题的。或者说注意别手动释放了,会出问题的。

  • UPROPERTY(EditAnywhere, Category="UE C++ Book",EditAnywhere属性表示在Level Editor中也可以操作这个属性。
    • 那么Blueprint与Level Editor的差别在哪里,需要调查。
      • 猜测的结果,Blueprint就是那个连来连去的蓝图,可以在那个蓝图的编辑器中取到属性。
      • Level Editor指的是操作的画面,在Level Editor中可以取到的意思应该是在Detail panel中直接设定或者读取值那样。
  • UPROPERTY(BlueprintReadWrite, Category="UE C++ Book"),BlueprintReadWrite表示Blueprint可以读写。
  • 其他的属性,还有meta属性可以查找下面的链接:

像上面的使用方法,在函数的前面添加UNFUNCTION macro宏可以制作UE4的Blueprint编辑器的函数节点以供调用。在虚幻4的Blueprint编辑器中使用此函数。

4.TArray-UE4中的数组(重要)

先上本家,官方文档TArray:Arrays in Unreal Engine

再上中文翻译虚幻引擎中的数组–TArry:Arrays

5.C++中的静态函数static与UE C++(Unreal Engine C++)的静态函数

在C++中的静态函数与非静态函数的执行确实是有差别的。

复习一下C++中的静态函数:

UE C++中关于静态函数的使用应该大同小异,但是也不排除有差别的可能性。

将来可能会派上用场的文章

6.Blueprint函数node(节点)的输入与输出

在UE C++中函数的参数对应Blueprint的node的输入pin,函数的返回值则对应着node的输出pin

1
2
UFUNCTION(BlueprintCallable,Category="classcategory")
bool MyFunc(const int a,int b,const int& c,int& d)

上述的情况下a,b,c三个变量对应着node的三个输入pin,但是return value,d对应着node的输出pin。也就是说:没有const修饰的引用型参数会被分配到输出pin的阵营中。

这里便引申出几个问题

  • UE C++的函数参数为什么要使用const修饰,使用常量的必要性是什么
  • 万一想要使用const修饰的引用型参数作为node的输入pin怎么办
  • 想要增加node的输出pin的话,除此之外还有别的写法么
Blueprint支持的数据类型很有限
  • bool
  • uint8
  • int32
  • float

7.UE4的Head File

UE4中的头文件有许多中,为了能够分清使用方法把遇到的头文件整理一下

Actor.h

一般的写法是

1
#include "GameFramework/Actor.h"

一般是继承了UE4中的Actor类的话都需要包含这个头文件。

xxx.generated.h

比如说

1
#include "CppGate.generated.h"

这个头文件是有UE4的UnrealHeaderTool自动生成的文件。如果类继承了UObject类并且想要在Blueprint中使用这个类的话,就需要include这个头文件。而且需要把这个头文件放到所有的头文件的最后才行。

8.UE4中的特殊容器

1.FVector

一个表示3D空间的向量。可以用来表示空间的一个点或者方向。

参考链接:

9.类的初始值设定

基础类型变量的初始化可以在声明的同时进行,以外的数据类型(FVector等)初始化需要在构造函数中进行。静态变量的初始化则需要在类外进行。

1
2
3
int ClassExample::static_var;  //equle 0
//or lik this
int ClassExample::static_var = 4;

#pragma once vs include guard

打开UE工程看到一些.h头文件的源码的时候看到了#pragma once有些困惑,不知道是做什么用的。于是调查了一下。

总体来说这两个命令都是为了避免同一个头文件被include多次

方式1:

1
2
3
4
#ifndef __SOMEFILE_H_
#define __SOMEFILE_H_
...
#endif

方式2:

1
2
#pragma once
...

#ifndef方式

这种方式受C/C++语言标准支持,可以保证同一个文件不会被同时包含,也能保证内容完全相同的两个文件(或者代码片段)不会被同时包含。缺点是不同头文件中的宏的名字如果相同,会导致编译器找不到声明的问题。另外由于编译器每次需要打开头文件来判定是否有重复的定义,会导致编译的时间要长。

#progma once方式

这种方式是保证“物理存在上”同一个文件不会被包含多次。而不是内容相同的两个文件。而且无法对一个头文件中一段代码做pragma once声明,只能针对文件。当然也不会发生第一种方式的宏名碰撞引发的问题。

使用哪一种方式见仁见智,有好处有坏处。根据情况使用。

C++中的类与结构体

参考链接:

开启HSLS语法高亮

HLSL Tools for Visual Studio

应该是HLSL的语法编辑器插件,有自动补全的功能,附上下载链接。但是并没有解语法高亮的问题。

下载链接:

在VS2017中开启语法高亮(syntax high lighting)

虽然不是针对VS2017的解决方案,但是设定之后的确解决的这个问题:

设定顺序:

  • 在VS2017中 Go to Tools -> Options -> Text Editor -> Fie Extension 在这里选择编辑文件的扩展名与文法编辑器,然后添加保存。

这样设定应该里面有效果了。

参考链接:

UnrealEngine部分

UE4中使用的一些Tip

1. 快速制作封闭空间

快速挖空一个几何体的制作顺序:

  • Modes panel -> Geometry -> Box -> 设置尺寸
  • Details panel -> Brush settings -> Hollow 属性check

2.调整模型的模型坐标的原点

根据模型的大小来调整模型坐标的原点,顺序:

  • 双击UE4中导入的模型,打开材质编辑器(static mesh editor也叫Material Editor)
  • 点击Show Pivot显示模型坐标,同时在左上角看见,模型的大小
  • Detail panel中找到Transform,调整Import Tansiation的数值,移动坐标系
  • Tool bar -> Asset -> Reimport Model 之后就会发现模型坐标按照指示移动了

3.制作天空球(与雾)

感觉要制作出天空的感觉同时还有雾的模糊的时候使用,可以看见太阳就是不知道能否模拟太阳的移动

顺序:

  1. 选定平行光源,开启Light -> Atmosphere/Sun light
  2. 将Visual Effects -> Atmospheric Fog 拖拽至场景中
  3. Content Browser panel右下的View Options中开启Show Engine Content
  4. 在Engine Content中找到BP_Sky_Sphere并将其拖拽至场景中
  5. 在BP_Sky_Sphere的Details面板中的Directional Light Actor选定上面的平行光

4.UE4中的Volume应用

在UE中使用Volume执行不同的任务可以解决很多问题,比如说:

  • 给玩家施加伤害
  • 改变物理定律,在Volume中允许玩家悬浮等等
  • 作为碰撞表面,不允许玩家进入
  • 改变计算关卡光照和可见性方式

等等,出了直接使用Modes panel中的Volume工具之外,也可以直接将几何体笔刷(brush)转化为相应的Volume。Details -> Actor -> Convert Actor -> Volume(that you want)

参考资料:

5.Z-Fighting

Z-Fighting又Depth Fighting,深冲突。就是贴图会出现闪烁的情况,与实际模型产生交叉融合

z-fighting的出现是的不同面上的像素在z-buffer中的值相近,导致前台取像素的时候一会去这个面的,一会取那个面的。改变照相机的near、far属性会涉及到z-buffer中的值的精度。因为在各个平台上z-buffer位数不同,因此改变near和far能给z-buffer中的值的浮点数部分尽量留出空间,消除z-fighting。

参考:

6.将选定的Actor合并为组

使用Ctrl+G的快捷键可以快速把选定的Actor(场景的素材)合并为一组,下次选定的时候会选定为一组。便于移动或者复制。

使用Shift+G的快捷键会解除分组。当然这些操作都可以在选中Actor之后:

  • Right Click -> Group进行分组和分解

7.UE4中的Material和Material Function

在UE4中偶然看到了这两个材质的声明,发现名字不太一样,需要调查一下。

UE C++中的需要注意的问题

1.ConstructorHelpers类的使用

就我的理解这个类可以在其他类的构造函数中实例化对象。自己的情况中多为获取在工程中的资源,并不是level中的Actor资源而是单纯的Content文件夹中的某个资源。

1
2
3
4
5
6
7
8
9
// 使用实例
// SampleActorComponent.cpp中的构造函数
USampleActorComponent::USampleActorComponent()
{
PrimaryComponnetTick.bCanEverTick = true;

static ConstructorHelpers::FObjectFinder<UStaticMesh> SampleAsset(TEXT("StaticMesh'/Game/StartrContent/Shapes/Shape_Plane.Shape_Plane'"));
sample_mesh = SampleAsset.Object;
}

这样就能成功取到Content中的资源,当然不限于StaticMesh其他的类型UMaterial等等的类型都可以取到。需要注意的是

  • ConstructorHelpers类的使用必须是要在类的构造函数中进行(吃了不少苦头)
  • SampleAsset的资源链接可以直接在UE4的Content文件夹右键资源copy reference中直接取到

关于更多的使用应该在另一篇博文中有拓展。

2.在C++类中为类添加用户输入响应

在网上搜了一下如何给一个脚本添加键盘输入事件,也不是那么全,有价值的一个提问是这个

但是试了一下并不好用,就自己的理解来说,基本上想要键盘的输入的事件响应的基本上都应该去继承UE4的PawnActor类去了,而我自己就是想用键盘来调试而已。继承的类是ActorComponent,并不能实现他们的代码。后来找了一找还是有实现方法的。

首先要去UE4的Editor中的Project Settings中的input选项中将自己想要绑定的键位命名并登录。我起的名字就是PressedF等等。

然后就是在c++中实现绑定了:

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
// SampleActorComponent.cpp

void USampleActorComponent::BeginPlay()
{
// 因为要绑定一下键位的事件,所以需要在这里写
this->GetOwner()->EnableInput(this->GetWorld()->GetFirstPlayerController());
UInputComponent * myInputComp = this->GetOwner()->InputComponent;
if(myInputComp) // check(myInputComp)
{
SetupMyPlayerInput(myInputComponent);
}
}

void USampeActorComponent::SetupMyPlayerInput(UInputComponent * myInputComponent)
{
myInputComponent->BindAction("PressedF", IE_Pressed, this, &USampleActorComponent::PressedMethod);
}

void USampleActorComponent::PressedMethod()
{
// 这里是按下键盘键位之后的动作内容
}

// SampleActorCompoennt.h

class USampleActorComponent : public UActorComponent
{
GENERATED_BODY()

public:
UFUNCTINN(BlueprintCallable, Category = "MyUE4Class")
void PressedMethod(); // 这个在头文件的声明一定要使用UFUNCTION宏来修饰否则没有作用
void SetupMyPlayerInput(UInputComponent * myInputComponent);
}

完整的使用方法大概就是这样,应该有一篇文章介绍的很详细的但是关掉了页面不太好找了。

3.像Unity一样保存场景中的参照

在Unity中把scene中的参照直接拖进脚本的声明公共变量以达到快速参照的目的,在UE4中也有类似的用法。

1
2
3
4
5
6
7
8
9
class USampleClass : public UActorComponent
{
GENERATED_BODY()
public:
USampleClass();

UPROPERTY(EditAnywhere, Category = "Edit")
AActor * targrtActor;
}

为属性添加UPROPERTY()宏让变量暴露给编辑器,然后在该脚本的Detail面板上wei该变量找到场景中的Actor参照。虽然不能拖拽了。


Tips更新(2020/06/03)

上面是C++中编程的场景,在UE的蓝图中保存参照的时候非常简单,直接将Actor从场景中拖到蓝图里就自动出现对象的引用了。但是在使用的过程中还是会有一些不便,比如说还要调整窗口的大小进行拖拽。

我在浏览官网的时候偶然发现的一个功能,就是在进入蓝图之前先选中Actor,复数的Actor也是可以的(当然这里应该不仅限于Actor),然后打开蓝图之后就会出现Call Function On Selected Actors这样的选项。嗯,这应该会有些用处。


4.获取Actor上的Component

不知道是不是UE4中获取Actor上的组件就这么麻烦还是我没找到,组件的获取并不是那么单纯的事情。

1
2
3
4
5
// 获取名为targetActor身上的脚本组件(

TArray<USampleActorComponent*> Comps;
targetActor_p->GetComponents<USampleActorComponent>(Comps);
// 这样Comps[0]的内容应该就是想要的组件的参照了

5.在UEC++中实现代理

我在现实中都没怎么用到过得代理,我竟然一次用了这么多。在UE4中代理的制作感觉好简单好方便。之前有一个是在C++中声明代理,但是实现是在蓝图中的,叫Multi-Cast-Delegate好像。这次完全是在C++中声明在C++中实现代理。

再来复述一下代理的情况:一个类想要做一件事儿,但是这件事跟这个类的关系是只想知道这件事做了而已,具体做的内容完全不关心,这件事情的实现是另一个类的分工。这个时候只要在自己的类里面声明一个代理,当想要执行的时候通知那个类就行。就像是事件一样。

1.定义代理类型

在UE4中引擎为我们做了大部分,而我们只要用就可以了。首先定义代理的类型。

1
2
3
4
5
6
7
8
9
10
11
#include "CoreMinimal.h"
#include "SampleDelegateComponent.h" // 我们需要委托的类头文件声明
#include "SampleActoomponent.generated.h" // 这个头文件是自动生成的,没有的话要手动补上去就是自己的文件名加上.generated.h,而且这个声明必须要在所有的声明的最后。原因不清楚

DECLARE_DELEGATE(SampleDelegate)

UCLASS()
class USampleActorComponnet : UActorComponent
{
// Class Contents
}

这样我们便声明了一个类型为SampleDelegate的代理了。

2.声明代理
1
2
// 在USampleActorComponent中的声明这个该类型的代理变量
SampleDelegate sample_delegate;
3.绑定代理

用上一条的方法获取到level中的Actor的参照,然后将代理绑定到这个想要代为我们执行的实例上去。

1
2
3
4
5
6
7
8
9
// 取得需要绑定的实例参照
if(targetActor)
{
// 获取到实例身上的脚本组件
TArray<USampleDelegateComponent*> Comps;
targetActor->GetComponents<USampleDelegateComponent>(Comps);
// 绑定代理
sample_delegate.BindUObject(Comp[0], &USampleDelegateComponent::MethodWanted);
}

4.代理执行

剩下的就是在想要的时候执行代理就好了。

1
sample_delegate.Execute();

6.动态加载资源

关于动态加载资源又是能说一箩筐的话题,这次只记录自己用到的。

1
UTexture2D * sampleTex = LoadObject<UTexture2D>(NULL, TEXT("Texture2D'/Game/Path..'"), NULL, LOAD_None, NULL);

参数啥的也不太清楚,用的时候按照这个方向查吧。之后的关于如何从零开始制作烟雾特效的教程里应该会出现。

7.动态改变物体材质参数

关于UE4的材质,有好多的话要说,关于如何动态的改变一个物体材质的问题要是不是自己非要用C++写蓝图,估计也不会钻研的那么深。

首先是要动态的改变一个材质里面的参数需要我们创建一个动态的材质。也就是说我们需要得到场景中的物体的材质的实例参照,然后以这个参照为模板创建一个动态材质,再把这个修改了的动态材质赋给物体。

老规矩,上代码:

.h文件

1
2
3
4
// Class内,省略大部分框架代码
// 头文件中声明材质
UMaterial * target_material;
UMaterialInstanceDynamic * target_material_dynamic;

.cpp文件

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
// 构造函数中使用ConstructorHelpers获取到物体的材质
static ConstructorHelpers::FObjectFinder<UMaterial> targetMT(TEXT("PATH"));
target_material = targetMT.Object;

// 随后可以在BeginPlay函数中对动态材质进行初始化
target_material_dynamic = UMaterialInstanceDynamic::Create(target_material, this->GetWorld());

// 可以为材质中的变量赋值
target_material_dynamic->SetScalarParameterValue("MaterialParaName",10);


// 拓展复习~
// 获取一个UStaticMesh上的组件的材质
TArray<UStaticMeshComponent*>comps;
this->GetOwner()->GetComponnets<UStaticMeshComponent>(comps);
target_material = (UMaterial*)comps[0]->GetMaterial(0) // 指针类型强制转换这一步很重要


// 更新材质结束之后要赋给物体
mesh_comp = comps[0]
mesh_comp->SetMaterial(0, target_material_dynamic);

// 下面的是比较重要的一步
// 在BeginPlay中实例化动态材质之后需要
target_material_dynamic->AddToRoot();

// 完后再EndPlay中将动态材质移除
target_material_dynamic->RemoveFromRoot();

这个也是吃了苦头的,因为自己非要用C++写,不用蓝图,这东西谁都不会告诉我啊。AddToRoot这个操作让UE的GC不会把我们创建的动态材质给回收掉。而使用的AddToRoot的东西在结束或者不想用的时候RemoveFromRoot,以便回收。哎呀没加的时候UE4崩的不要不要的。

8.UE_LOG输出奇奇怪怪的数据类型

总是想输出些什么。使用UE_LOG可以在UE4的Editor输出运行的信息。但是C++嘛,你让它输出FString类型的东西,他不认识就不输出这个时候就像下面这样:

1
UE_LOG(LogTemp, Log, TEXT("output message %s"), *(FDateTime::Now().ToString()));

使用指针强制转换,我也不知道是个什么原理。

9.FTimerManager定时器

定时器是个好东西。但是要怎么用呢?

首先要获取到这个定时器,在世界中有这么一个定时器:

1
2
// 为什么要加 & ,引用的作用应该就是我不知道,不加就报错。我也不知道我是怎么灵机一动加上就好用的。
FTimeManager &timer = this->GetOwner()->GetWorldTimerManager();

然后声明一个Handle

1
2
// 在.h文件中声明一个Timer Handle供我们使用,一个Handle就是一个定时的名字应该
FTimerHandle timer_handle;

最后开始定时:

1
timer.SetTimer(timer_handle, this, &USampleActorComponent::TimerMethodWnted, 1.0, false);

参数的意思应该一目了然了。

UE4项目右键找不到GenerateProjectFile选项的问题

换新电脑或者重做系统之后会遇到的麻烦事儿,就是想要重新生成UE4的sln工程文件,右键却找不到重新生成选项的问题。解决方案是:

找到使用的引擎文件中的UnrealVersionSelector.exe文件,并双击。

一般这个文件都是在UE4的Binary文件夹下面。

参考资料:
Association .uproject / unreal are broken

UE4的uproject文件中EngineAssociation项目版本管理的问题

基本上在项目版本管理也就是使用Git之类,使用自定义引擎的时候避免不了遇到这个问题。

一时之计是去RegistryEditor那里 HKEY_CURRENT_USER\SOFTWARE\Epic Games\Unreal Engine\Builds 路径下面找到自己自定义的引擎的名字,修改为跟版本管理攻击一样的数列,这样差分就消失了。等下次有人又更新了就随之更新,反正uproject文件的更新频率不会高的。

但是这也是权宜之计,我也知道。

也许会有更好的方法。

貌似可以有直接修改源码的方式,只不过我没有试过。

UE4知识拓展

主要用来记录一些常见但是需要理解的优先级不高或者不太常用的知识点的拓展。

Wildcard 类型

Wildcard这个数据类型是UE4的Editor的可以适配所有类型的Pin的类型。

可以用来作为一些宏的Event的类型指定,就像为函数提供一个delegate一样,UE4的蓝图的宏也可以使用类似的特性。

这篇文章的知识点随着时间的推移开始变得有些杂乱了,该考虑一下重新整理文章内容了。

从UE4的源码中对工程进行设置

2020/06/03更新

参照官网上的步骤下载并配置源码版UnrealEngine,这其中有一个我漏掉的步骤,就是把UE4设置为启动工程

即在工程项目的SolutionExplorer中找到项目右键,会出现Set as StartUp Project,对,要执行这一步操作,然后在Build界面中选择Build UE4,因为这里不选直接使用默认的编译的话,会把全体都编译,有时间可以这么搞,没时间还是别这么搞,真的费时间。

这样按理说就可以使用编译好的引擎打开想要工作的项目了。

DefaultBuildSettings

UE4自4.24版本之后更改了默认的编译设定,BuildSettingsVersion.V2

Here’s an example V1(legacy) target.cs file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright 1998-2019 Epic Games, Inc. All Right Reserved.

using UnrealBuildTool;
using System.Collections.Generic;

public class AGobesTestEditorTarget : TargetRules
{
public AGnoesTsetEditorTarget( TargetInfo Target ) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V1;
ExtraModuleNames.AddRange( new String[] { "AgonesTest" } );
}
}

Here’s an example V2 (new) target.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright 1998-2019 Epic Games, Inc. All Right Reserved.

using UnrealBuildTool;
using System.Collections.Generic;

public class AGobesTestEditorTarget : TargetRules
{
public AGnoesTsetEditorTarget( TargetInfo Target ) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V2;
ExtraModuleNames.AddRange( new String[] { "AgonesTest" } );
}
}

还有就是需要注意的是,当由之前的V1版本切换到V2版本之后会出现include文件路径不对的问题,换了一种编译方式,编译路径也不一样了。
然后就是自己的发现就是V2编译的路径变成了相对路径

道听途说

1.关于C++中的资源释放

一般在c++中应该是资源的获取与申请(new等等的操作)与资源的释放都是需要程序员手动操作的。但是如果使用了UE管理的资源,比如继承了UE中的类库等等的操作,那么释放这些资源的操作,在UE中会自动释放而不需要程序员的额外操作,反而要是操作了还会出问题。

当然这个问题只是道听途说,未能够验证。

当然需要仔细调查了啊

2.关于UE中UObject的派生对象的垃圾回收问题

如果一个UObject的派生类对象中有一个UObject的派生类对象的指针变量,当这个指针变量经过NewObject<>()的操作实例化了之后,这个实例化对象就成为了GC对象(garbage collection),但是仅仅如此还不足以达到自动回收的目的,像下面这样在声明之前加上UPROPERTY()宏,就不需要显式的delete垃圾回收了。

1
2
UPROPERTY()
UObject* MyObjectReference;

总结来说,在C++中使用了new等等的方式来申请了一部分空间的话在使用结束的时候需要显式的释放掉,否则的话就会造成内存泄漏。然而上述的使用方式会使得这样声明的实例对象能够得到的资源自动回收。

参考来源:

关于C++好像发现了一个不得了的链接

貌似可以在这个链接中找到C++的标准文档,英文文档。

一些感觉蛮重要的文章

这里放置一些我想看但是暂时没有时间看的文章。

感觉是比较新的知识点,总是有看到过的影子,关于MeshDistanceFields: