对官方文档的学习链接

利用UE创建一个C++基类

在编辑器中可以选择父类,根据这个父类我们可以创建一个基类用于后续的蓝图类制作。

以Actor父类为例创建基类,其头文件会包含一个构造函数,一个Tick函数的重载和一个BeginPlay函数的重载。

BeginPlay函数告诉Actor以可运行状态进入了游戏。这是启动类Gameplay逻辑的好位置。Tick 每帧调用一次,使用自上次调用传递以来经过的时间,可以在这里执行任何重复逻辑。

对于Tick函数不需要的话最好删除,这样可以节省性能,并将构造函数中的变量设为false

PrimaryActorTick.bCanEverTick = false;

将属性暴露给蓝图

我们在头文件中定义的变量就是这个类的属性,比如一个Actor的形状,速度,粒子特效。如果我们要在蓝图中操作改变这些属性就需要在定义前加上UPROPERTY(EditAnywhere)

括号内可以添加一些属性说明符,用于说明改变量在蓝图中的权限。属性说明符

对于蓝图来说主要有BlueprintReadWrite,BlueprintReadOnly等,还可以利用Category="Name",来对属性变量进行分类,相同Name的变量在蓝图中将出现在同一个标题下面。

设置变量值

对于属性说明符为EditAnywhere的变量,我们可以在C++代码或者蓝图中设置这些变量的值。

在C++代码中,主要有3种方法来初始化变量值。

  1. 头文件定义时赋值
UPROPERTY(EditAnywhere)
float Total = 100.0f;
  1. 构造函数赋值
AMyActor::MyActor
{
Total = 300.0f;
}
  1. 构造函数初始化列表赋值
AMyActor::MyActor : Total(200.0f)
{
Total = 300.0f;
}

三个方法可以同时使用,编译时会先构建头文件,再初始化列表,最后是构造函数。

所以前者会被后者的值覆盖。

初始化列表的顺序不是书写顺序而是在头文件中的声明顺序。

4. 利用PostInitProperties()初始化VisibleAnywhere属性的变量。

VisibleAnywhere属性的变量也可以通过上述3种方法初始化,只不过不能通过蓝图进行更改。需要使用PostInitProperties()虚方法。

该方法可以用于蓝图中改变变量值后,VisibleAnywhere属性的变量变换。该方法在构造函数之后,所有属性初始化之后才被调用。

在头文件中声明该虚函数

virtual void PostInitProperties() override;

在源文件中定义该虚函数

void AMyActor::PostInitProperties()
{
Super::PostInitProperties();
Totalper = Total/time;
}
  1. 实时更新VisibleAnywhere属性的变量

    如果我们在UE蓝图中更改了Total和time,想要Totalper也随之更改,就需要使用PostEditChangeProperty()方法,并且该方法需要编写在ifdef的内部,这样才能在构建游戏时只编译真正需要的代码,删除任何多余的、导致可执行文件大小增大的代码
#ifdef WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Totalper = Total/time; Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif

将函数暴露给蓝图

c++代码函数需要暴露给蓝图需要使用UFUNCTION(BlueprintCallable)

还有两种属性符号:BlueprintImplementableEvent和BlueprintNativeEvent。

  1. BlueprintImplementableEvent

    C++代码中只能声明,不能定义。定义需要在蓝图中重写。
  2. BlueprintNativeEvent

    C++代码中可以定义+声明,蓝图中可以选是否重写覆盖或者是否调用C++父类。在C++源文件中定义时需要将函数名改为

    <Func_Name>_Implementation()才能够被识别。
//.h文件
UFUNCTION(BlueprintNativeEvent, Category="Damage")
void CalledFromCpp(); //.cpp文件
void AMyActor::CalledFromCpp_Implementation()
{
// 这里可以添加些有趣的代码
}

Gameplay类

Gameplay类派生的4个主要类->UObject类,AActor类,UActorComponent类,UStruct类。

UObject类

Gameplay的基本,结合UClass,提供引擎服务

  • 反射属性和方法
  • 序列化属性
  • 垃圾回收
  • 按名称查找UObject
  • 属性的可配置值
  • 属性和方法的联网支持

    UObject派生的每一个类都会有自己的UClass,UObject和UClass位于Gameplay对象在其生命周期所有作用的根部位置。UClass主要描述的是UObject实例的样子、可序列化和联网的属性。

AActor类

UObject派生而来,一个关卡中的所有对象都是从该类扩展而来的。可以显式销毁。AActor可以在联网时复制的基本类型。在网络复制期间,Actor还可以分发其拥有的,需要网络支持或同步的任何UActorComponent的信息。

Actor还作为ActorComponent层级容器。每个Actor实例对象都有一个RootComponent,其包含一个USceneComponent,而后者继而包含许多其他的Component。在可以将Actor放入关卡之前,它必须包含至少一个Scene Component,Actor可以从后者绘制其平移、旋转和缩放。

Actor包含在AActor生命周期中调用的一系列事件。以下列表是一组简化的事件,描绘了整个生命周期:

  • BeginPlay:Actor首次在Gameplay中存在时调用。
  • Tick:每帧调用一次,随着时间的进行持续完成工作。在编写自己的Tick函数时,必须确保调用Super::Tick
  • EndPlay:对象离开Gameplay空间时调用。

一个Actor的生命周期

Actor加载并存在,最后关卡被卸载后,Actor被销毁

Actor的产生:需要注册到多个运行时系统才能满足其所有需要。比如需要设置Actor的初始位置和旋转,那么物理模块需要知道这些信息,负责告诉Actor执行tick事件的管理器也需要知道。

所以UE专门定义了一个方法来生产Actor--SpawnActor。当成功产生Actor后,引擎会调用它的 BeginPlay方法,下一帧调用 Tick。

Actor生命周期结束时,您可以调用 Destroy 来将它销毁。在该过程中,将调用 EndPlay,让您能在Actor进入回收站之前执行自定义逻辑。

另一个控制Actor生命周期时长的方法是使用 Lifespan 成员。您可以在对象的构造函数中设置Actor的时间跨度,也可以在运行时使用其他代码进行设置。当这段时间到期后,会自动对该Actor调用 Destroy。

在构造函数中初始化Actor生命周期,蓝图中为set life span

InitialLifeSpan = 3.0f;

UActorComponent

一般需要依附在一个Actor类下的RootComponent,可以用来提供网格体、粒子效果、摄像机视角和物理互动。组件也可以与其他组件相连接,或者可以成为Actor的根组件。一个组件只能连接到一个父组件或Actor,但可以连接多个子Actor。

UStruct

不需要从其它类派生,只需要使用UStruct标记结构体即可,其不会被垃圾回收。为纯数据类型。如果创建动态结构体实例,必须自己管理生命周期。

虚幻的反射系统

UE使用自己的反射系统实现垃圾回收、序列化、网络复制、蓝图通信等动态功能。需要将正确的标记添加到类型才能开启这些功能,否则UE不会生成反射数据,一般为U开头的标记

UCLASS():为类生成反射数据。类必须派生自UObject
USTRUCT():用于告诉UE为结构体生成反射数据
GENERATED_BODY():表示UE将为这个类型生成所有必要的样板代码
UPROPERTY():支持将UCLASS的成员变量或USTRUCT用作UPROPERTY。UPROPERTY有很多用法。它可以允许复制变量、序列化变量和从蓝图访问变量。它们可以供垃圾回收程序使用,用来跟踪对UObject的引用次数。
UFUNCTION():支持将UCLASS的类方法或USTRUCT用作UFUNCTION。UFUNCTION可以允许从蓝图调用类方法,用作RPC等多种用途。

对于我们新建的一个类对象,头文件中会默认包含#include "Class_Name.generated.h",该语句必须使标头文件的最后一个语句。其作用是UE将生成所有的反射文件放入该文件中。

对象/Actor迭代器

  1. 对象迭代器
TObjectIterator<Class_Name>

一般只能用来迭代UObject或者其的子类的所有实例。

通过一个循环来获取

for (TObjectIterator<UObject> It; It; It++)

UE编辑器中:使用对象迭代器会返回游戏一个关卡中创建的所有UObject实例,并且还会返回编辑器使用的实例。

2. Actor迭代器

只能用于迭代AActor派生的对象。 并且只会返回游戏当前关卡实例所使用的对象。

TActorIterator<Class_Name>

对于Actor的迭代需要指定一个指向UWorld的指针,可以通过GetWorld()方法来获取

APlayerController* MyPC = GetMyPlayerControllerFromSomewhere();
UWorld* World = MyPC->GetWorld();
// 正如对象迭代器一样,您可以提供一个具体类来仅获得
// 属于该类或派生自该类的对象
for (TActorIterator<AEnemy> It(World); It; ++It)
{
// ...
}

内存管理和垃圾回收

同样是利用反射系统来实现垃圾回收,只有派生自UObject的类才能进行垃圾回收操作。

垃圾回收程序中,有一个根集的成员列表,在这个列表中的成员不会被垃圾回收程序进行垃圾回收,如果一个对象为该列表成员的引用,该对象也不会被回收。

反之,不存在此类路径,该对象无法访问,在下一次运行垃圾回收程序是将进行回收操作。UE一般按一定的时间间隔自动进行垃圾回收操作。

根集成员的引用:一般来说UPROPERTY或者UE容器类(TArray)修饰的UObject指针对象都可以当作引用,不会被执行垃圾回收。

Actor对象

Actor类的对象,在关卡关闭之前,都不会被垃圾回收,只有使用Destroy方法,立刻将对象从关卡中移除,他会立即从游戏中删除,但只有在下一次垃圾回收时才能被完全删除回收。

垃圾回收

一个UObject被垃圾回收时,通过UPROPERTY修饰的对象都会设置为空指针,所以可以通过判断是否为空,确保对象的正确调用

if(MyActor->SafeObject != nullptr)
{
//TODO
}

特别的是对于Actor类对象,如果使用Destroy销毁对象,因为只有在下一次垃圾回收的时候才会变为空指针,所以就需要使用IsPendingKill方法来判断该对象是否存在,如果为true则表明对象已经被销毁

if(IsPendingKill(MyActor) == false)
{
//TODO
}

UStructs和非对象引用

UStructs为UObejct的轻量版本,不能使用垃圾回收装置,如果必须使用UStructs实例,则只能使用智能指针。

C++对象(非派生自 UObject)也能够添加对对象的引用并防止垃圾回收。为此,对象必须派生自 FGCObject 并覆盖其 AddReferencedObjects 方法。

class FMyNormalClass : public FGCObject

void AddReferencedObjects(FReferenceCollector& Collector) override
{
Collector.AddReferencedObject(SafeObject);
}

使用 FReferenceCollector 来手动添加对需要且不希望垃圾回收的 UObject 的硬引用。当该对象被删除且其析构函数运行时,该对象将自动清除其所添加的所有引用。

FName

用于存储反复出现的字符串,来节省内存和CPU时间。=FName使用空间来存储索引,而不是对每个引用FName的对象存储一个值。调用通过检测索引的匹配来确定字符串是否相同。

容器

TArray:类似vector

TMap:类似map,键值对用于查找,添加,删除

Tset:类似set

容器迭代器:Name.CreateIterator()

void RemoveDeadEnemies(TSet<AEnemy*>& EnemySet)
{
// 从集开头处开始,迭代至集末尾
for (auto EnemyIterator = EnemySet.CreateIterator(); EnemyIterator; ++EnemyIterator)
{
// *运算符获取当前元素
AEnemy* Enemy = *EnemyIterator;
if (Enemy.Health == 0)
{
//"RemoveCurrent"受TSet和TMap支持
EnemyIterator.RemoveCurrent();
}
}
}

还可以利用for-each语法:来循环元素,对于TArray和TSet将返回元素值,TMap返回键值对

UE4中的C++编程简介的更多相关文章

  1. UE4 中的 C++ 编程介绍

    https://docs.unrealengine.com/latest/CHN/Programming/Introduction/index.html UE4 中的 C++ 编程介绍 Unreal ...

  2. 极简SpringBoot指南-Chapter05-SpringBoot中的AOP面向切面编程简介

    仓库地址 w4ngzhen/springboot-simple-guide: This is a project that guides SpringBoot users to get started ...

  3. UE4中C++编程(一)

    一: C++工程和Gameplay框架 GameInstance 它适合放置独立于关卡的信息,比如说显示UI. GameMode 表示游戏玩法, 包含游戏进行的规则和胜利条件等等信息,游戏模式是和关卡 ...

  4. UE4 中Struct Emum 类型的定义方式 笔记

    UE4 基础,但是不经常用总是忘记,做个笔记加深记忆: 图方便就随便贴一个项目中的STRUCT和 Enum 的.h 文件 Note:虽然USTRUCT可以定义函数,但是不能加UFUNCTION 标签喔 ...

  5. Python中的并发编程

    简介 我们将一个正在运行的程序称为进程.每个进程都有它自己的系统状态,包含内存状态.打开文件列表.追踪指令执行情况的程序指针以及一个保存局部变量的调用栈.通常情况下,一个进程依照一个单序列控制流顺序执 ...

  6. UNIX网络编程---简介

    UNIX网络编程---简介 一.           概述 a)       在编写与计算机通信的程序时,首先要确定的就是和计算机通信的协议,从高层次来确定通信由哪个程序发起以及响应在合适产生.大多数 ...

  7. 《编程简介(Java) &#183;10.3递归思想》

    <编程简介(Java) ·10.3递归思想> 10.3.1 递归的概念 以两种方式的人:男人和女人:算法是两种:递归迭代/通知: 递归方法用自己的较简单的情形定义自己. 在数学和计算机科学 ...

  8. win32编程简介

    win32编程简介 复习Win32整理下知识. 为什么学习win32? 我们要编写windos程序.都离不开API. 也就是我们所说的win32程序. 所以学好win32是你能不能再windows下编 ...

  9. 【Unix网络编程】chapter3套接字编程简介

    chapter3套接字编程简介3.1 概述 地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换.多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数, ...

  10. 【Unix网络编程】chapter3 套接字编程简介

    chapter3套接字编程简介3.1 概述 地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换.多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数, ...

随机推荐

  1. let与const

    let与const ES2015(ES6)新增加了两个重要的JavaScript关键字: let和const. 块级作用域 代码块内如果存在let或者const,代码块会对这些命令声明的变量从块的开始 ...

  2. FTP命令详解(含操作实例)

    以下是微软命令行FTP客户端命令大全,如果你想使用"未加工(RAW)"FTP命令而非下面翻译过的请参考:http://www.nsftools.com/tips/RawFTP.ht ...

  3. Spring源码之springMVC

    目录 web.xml 程序入口 servlet 初始化 运行阶段 销毁阶段 DispatcherServlet 初始化 DispatcherServlet 的逻辑处理 web.xml 它的作用是配置初 ...

  4. win32 - PeekNamedPipe的用法

    PeekNamedPipe: 将数据从命名管道或匿名管道复制到缓冲区中,而不将其从管道中删除.它还返回有关管道中数据的信息. 示例: #include <iostream> #includ ...

  5. 深入理解Go语言(01): interface源码分析

    分析接口的赋值,反射,断言的实现原理 版本:golang v1.12 interface底层使用2个struct表示的:eface和iface 一:接口类型分为2个 1. 空接口 //比如 var i ...

  6. 【Android逆向】Frida 无脑暴力破解看雪test2.apk

    1. 安装apk到手机 adb install -t test2.apk apk下载位置: https://www.kanxue.com/work-task_read-800625.htm 2. 题目 ...

  7. 常用SQL语句备查

    查询表中某一列是否有重复值 SELECT bizType, COUNT(bizType) FROM Res GROUP BY bizType HAVING COUNT(bizType) > 1 ...

  8. 2021-07-20 value!==value,JavaScript中NaN

    关于NaN NaN 属性代表一个"不是数字"的number类型的字面量值.这个特殊的值是因为运算不能执行而导致的,不能执行的原因要么是因为其中的运算对象之一非数字. NaN的出现场 ...

  9. webservice之jax-ws实现方式(服务端)

    1.什么是webservice? webservice是一种远程资源调用技术,它的实现方式主要分为两种, 第一种是jaxws方式,它是面向方法的,它的数据类型是xml是基于soap实现传输: 第二种是 ...

  10. 面向开发者的 ChatGPT 提示工程课程|吴恩达携手OpenAI 教你如何编写 prompt

    提示工程(Prompt Engineering)是一门相对较新的学科,旨在开发和优化提示,从而高效地将语言模型(LM)用于各种应用和研究主题,并帮助开发人员更好地理解大型语言模型(LLM)的能力和局限 ...