UE4和C++ 开发-Unity 过度到 Unreal 4 对比简化版

2023-11-07

 3、 两个引擎的部分术语对比:

4.7、From GameObjects to Actors 从GameObjects到Actors

    GameObject在UE4中如何体现?UE4有一个 Gameplay Framework 系统来使用这些Actors工作。

5.1、UE4中,系统也将提供给我们两个函数,他们分别是 InitializeComponent() 函数和 TickComponent()函数,这两个函数和Unity中Start()函数和Update()函数类似

5.2、Scriptable Actor Blueprint Classes 可以编辑的Actor蓝图类

  5.2.1、你新建的 Actor蓝图类(此处不是组件蓝图类)可以使用蓝图来实现可视化编辑

  5.2.2、代码实现功能的C++类。下面给出了三个的对应关系。

  5.2.2、UE4 C++

#pragma once
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class AMyActor : public AActor
{    
GENERATED_BODY()  
  int Count;  

  // Sets default values for this actor's properties.  
  AMyActor()   
  {      
  // Allows Tick() to be called    
    PrimaryActorTick.bCanEverTick = true; 
     }   
 // Called when the game starts or when spawned.  
  void BeginPlay()   
 {     
   Super::BeginPlay();    
    Count = 0;  
  }   
 // Called every frame. 
   void Tick(float DeltaSeconds)   
 {       
 Super::Tick(DeltaSeconds);   
     Count = Count + 1;   
     GLog->Log(FString::FromInt(Count)); 
   }};  

5.6、Transform Components  Transform组件

Actors有一个叫做RootComponent的组件,Actor在世界中拥有位置、旋转、缩放属性,

6.3、Writing Event Functions (Start, Update, etc.) 写事件函数

6.3.2、C++ :

UCLASS()
class AMyActor : public AActor{  
  GENERATED_BODY()   
 // Called at start of game.    
void BeginPlay(); 
   // Called when destroyed.  
  void EndPlay(const EEndPlayReason::Type EndPlayReason); 
   // Called every frame to update this actor.  
  void Tick(float DeltaSeconds);};  

 

6.3.4、UE4中的组件包含一些不同的函数,下面列举了一些简单例子:

C++ :

UCLASS()
class UMyComponent : public UActorComponent{  
  GENERATED_BODY()   
 // Called after the owning Actor was created  
  void InitializeComponent();   
 // Called when the component or the owning Actor is being destroyed   
 void UninitializeComponent();  
  // Component version of Tick   
 void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction);};  

请记住,在UE4中,调用父类的函数的版本是非常重要的。

比如说,在unity里面这样写:base.Update();而在UE4中则需要这样写:Super::TickComponent();

void UMyComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{  
  // Custom tick stuff here  
  Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}  

UE4的C++代码中,字母前缀'A'表示是Actor的子类,'U'表示Object的子类。当然还有一些其他的前缀,比如‘F’,表示是一些空白的数据结构或者不是Object类。

6.3.6、Writing gameplay code in UE4  在UE4中编写游戏代码

6.3.6.1、Instantiating GameObject / Spawning Actor  实例化一个物体

NewObject()函数是用来创建UObject类型的物体,SpawnActor()函数是用来创建AActor类型的物体。

UE4中,这样写:

  1. UMyObject*NewObj=NewObject<UMyObject>();  

那对于Actor类型的物体如何实例化呢?Actors物体是通过使用一个UWorld类型的World物体上的SpawnActor()函数来实例化的。一些UObject提供了GetWorld()函数,比如所有的Actors类就可以给你提供这个函数。观察下面的函数,我们发现,我们需要传递进来的第一个参数是我们想要实例化的Actor的类,而不是Actor本身。在我们的实例中,这个类可以使AMyEnemy的任何子类。

但是如果我想复制一个物体,我该使用哪一种盛丽华的函数呢?NewObject()函数和SpawnActor()函数都个可以提供一个“template(模板)”物体来使用。虚幻引擎将会从这个物体来复制,而不是从头开始实例化对象,这样做将会把他的所有的UPROPERTYs(属性特性)和组件拷贝到。

 ·

AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, 
FVector SpawnLocation, 
FRotator SpawnRotation)
{   
 UWorld* World = ExistingActor->GetWorld();  
  FActorSpawnParameters SpawnParams; 
   SpawnParams.Template = ExistingActor; 
   World->SpawnActor<AMyActor>(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}  

在UE中,使用物体的构造函数来实现相同的功能。

UCLASS()
class AMyActor : public AActor{  
  GENERATED_BODY()  
  UPROPERTY()  
  int32 MyIntProp; 
   UPROPERTY()  
  USphereComponent* MyCollisionComp; 
   AMyActor()    {   
     MyIntProp = 42; 
       MyCollisionComp = CreateDefaultSubobject<USphereComponent>(FName(TEXT("CollisionComponent"));     
   MyCollisionComp->RelativeLocation = FVector::ZeroVector;  
      MyCollisionComp->SphereRadius = 20.0f; 
   }};  

在AMyActor的构造函数中,我们给这个类设置了默认的参数值,要记住使用CreateDefaultSubobject()函数。我们可以使用这个函数来创建组件并且为其制定默认的参数。我们使用这个函数创建的所有子物体实际上就是一个默认的模板,因此我们可以在一个子类中或者蓝图中修改他们。

Casting from one Type to Another 类型转换

就是根据已有的组件转换成另一种类型来有条件的处理一些事情。

Collider collider = gameObject.GetComponent<Collider>;
SphereCollider sphereCollider = collider as SphereCollider;
if (sphereCollider != null)
{   
     // ...
}  
UE4 C++:
UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());

USphereComponent* SphereCollider = Cast<USphereComponent>(Primitive);
if (SphereCollider != nullptr)
{ 
       // ...
}  

6.3.6.1.2、Destroying GameObject / Actor  销毁物体 

Disabling GameObjects / Actors  使物体处于不激活状态

6.3.6.1.3、Accessing the GameObject / Actor from Component 通过组件访问物体

Accessing a Component from the GameObject / Actor  通过物体访问组件 

 C++:

UMyComponent*MyComp=Cast<UMyComponent>(
MyActor->GetComponentByClass(UMyComponent::StaticClass())
);  

6.3.6.1.4、Finding GameObjects / Actors  查找物体
// Find GameObject by name
GameObject MyGO = GameObject.Find("MyNamedGameObject");
// Find Objects by type

MyComponent[] Components = Object.FindObjectsOfType(typeof(MyComponent)) as MyComponent[];

foreach (MyComponent Component in Components)
{   
     // ...
}
// Find GameObjects by tag

GameObject[] GameObjects = GameObject.FindGameObjectsWithTag("MyTag");
foreach (GameObject GO in GameObjects)
{  
      // ...
}
// Find Actor by name (also works on UObjects)

AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));
// Find Actors by type (needs a UWorld object)
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{     
   AMyActor* MyActor = *It;   
     // ...
}  
// Find UObjects by type
for (TObjectIterator<UMyObject> It; It; ++it)
{  
  UMyObject* MyObject = *It;   
 // ...
}
// Find Actors by tag (also works on ActorComponents, use TObjectIterator instead)
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
    AActor* Actor = *It;   
 if (Actor->ActorHasTag(FName(TEXT("Mytag"))))  
  {   
     // ...  
  }
}  
// Checks if an ActorComponent has this tag

if (MyComponent->ComponentHasTag(FName(TEXT("MyTag"))))
{  
  // ...
}  

6.3.6.1.8、Physics: RigidBody vs. Primitive Component  物理部分:RigidBody 对比 Primitive 组件

虚幻引擎把可能的物理和可视化组合在PrimitiveComponent组件中。任何一个组件都在世界中拥有集合属性,他们可以被渲染出来也可以和拥有PrimitiveComponent组件的物体相互作用。

6.3.6.1.9、Layers vs Channels  Unity中的Layers和UE4中的Channels

6.3.6.1.10、RayCast vs RayTrace  光线投射

 6.3.6.1.10.2、C++

APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{   
 // You can use this to customize various properties about the trace    FCollisionQueryParams Params;  
  // Ignore the player's pawn  
  Params.AddIgnoredActor(GetPawn());  
  // The hit result gets populated by the line trace 
   FHitResult Hit; 
   // Raycast out from the camera, only collide with pawns (they are on the ECC_Pawn collision channel)  
  FVector Start = PlayerCameraManager->GetCameraLocation();  
  FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);    bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params); 
   if (bHit)  
  {      
  // Hit.Actor contains a weak pointer to the Actor that the trace hit    
    return Cast<APawn>(Hit.Actor.Get());  
  }  
  return nullptr;
}  

 6.3.6.1.11、Triggers 触发器

 6.3.6.1.11.2、UE4 C++:

UCLASS()
class AMyActor : public AActor
{  
  GENERATED_BODY()
    // My trigger component  
  UPROPERTY()   
 UPrimitiveComponent* Trigger;  
  AMyActor()  
  {       
 Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerCollider")); 
       // Both colliders need to have this set to true for events to fire        Trigger.bGenerateOverlapEvents = true;   
     // Set the collision mode for the collider  
      // This mode will only enable the collider for raycasts, sweeps, and overlaps        Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly); 
   }  
  void BeginPlay()   
 {      
  // Register to find out when an overlap occurs        OnActorBeginOverlap.AddDynamic(this, &AMyActor::OnTriggerEnter);        OnActorEndOverlap.AddDynamic(this, &AMyActor::OnTriggerExit);   
     Super::BeginPlay();  
  }  
  void EndPlay(const EEndPlayReason::Type EndPlayReason)  
  {   
     OnActorBeginOverlap.RemoveDynamic(this, &AMyActor::OnTriggerEnter);        OnActorEndOverlap.RemoveDynamic(this, &AMyActor::OnTriggerExit);        Super:EndPlay(EndPlayReason);   
 }   
 UFUNCTION()  
  void OnTriggerEnter(AActor* Other); 
   UFUNCTION() 
   void OnTriggerExit(AActor* Other);
};  

6.3.6.1.12、Kinematic Rigidbodies 运动学

在UE4中,碰撞组件和刚体组件是一个东西。他们的父类是UPrimitiveComponent,他又很多子类来满足你的需求,比如USphereComponent,UCapsuleComponent等等。

6.3.6.1.12.2、UE4 C++:

UCLASS()
class AMyActor : public AActor
{ 
   GENERATED_BODY()    
UPROPERTY()    
UPrimitiveComponent* PhysicalComp;    
AMyActor()    
{        
PhysicalComp = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionAndPhysics"));        PhysicalComp->SetSimulatePhysics(false);        
PhysicalComp->SetPhysicsLinearVelocity(GetActorRotation().Vector() * 100.0f);    
}
};  

6.3.6.1.13、Input events  输入事件 

6.3.6.1.13.2、UE4 C++

UCLASS()
class AMyPlayerController : public APlayerController
{
    GENERATED_BODY()    
void SetupInputComponent()    
{        
Super::SetupInputComponent();        
InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::HandleFireInputEvent); 
       InputComponent->BindAxis("Horizontal", this, &AMyPlayerController::HandleHorizontalAxisInputEvent); 
       InputComponent->BindAxis("Vertical", this, &AMyPlayerController::HandleVerticalAxisInputEvent); 
   }    
void HandleFireInputEvent();  
  void HandleHorizontalAxisInputEvent(float Value);
    void HandleVerticalAxisInputEvent(float Value);
};  

How do I see Log Output from my game? 怎么看调试输出信息?

UE4编辑器中:Window -> Developer Tools

How do I throw exceptions? 我怎么才能抛出异常错误?

UE4中没有这样的机制,而是使用check()函数。

Where is the .NET Framework? .NetFramework在哪里?

.Net Framework UE4
String FStringFText
List TArray
Dictionary TMap
HashSet TSet

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UE4和C++ 开发-Unity 过度到 Unreal 4 对比简化版 的相关文章

  • EF Core Group By 翻译支持条件总和

    听说 EF Core 2 1 将支持翻译小组 我感到非常兴奋 我下载了预览版并开始测试它 但发现我在很多地方仍然没有得到翻译分组 在下面的代码片段中 对 TotalFlagCases 的查询将阻止翻译分组工作 无论如何 我可以重写这个以便我
  • 以文化中立的方式将字符串拆分为单词

    我提出了下面的方法 旨在将可变长度的文本拆分为单词数组 以进行进一步的全文索引处理 删除停止词 然后进行词干分析 结果似乎不错 但我想听听关于这种实现对于不同语言的文本的可靠性的意见 您会建议使用正则表达式来代替吗 请注意 我选择不使用 S
  • 为什么两个不同的 Base64 字符串的转换会返回相等的字节数组?

    我想知道为什么从 base64 字符串转换会为不同的字符串返回相同的字节数组 const string s1 dg const string s2 dq byte a1 Convert FromBase64String s1 byte a2
  • 按成员序列化

    我已经实现了template
  • ASP.NET MVC:这个业务逻辑应该放在哪里?

    我正在开发我的第一个真正的 MVC 应用程序 并尝试遵循一般的 OOP 最佳实践 我正在将控制器中的一些简单业务逻辑重构到我的域模型中 我最近一直在阅读一些内容 很明显我应该将逻辑放在域模型实体类中的某个位置 以避免出现 贫血域模型 反模式
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 使用实体框架模型输入安全密钥

    这是我今天的完美想法 Entity Framework 中的强类型 ID 动机 比较 ModelTypeA ID 和 ModelTypeB ID 总是 至少几乎 错误 为什么编译时不处理它 如果您使用每个请求示例 DbContext 那么很
  • BitTorrent 追踪器宣布问题

    我花了一点业余时间编写 BitTorrent 客户端 主要是出于好奇 但部分是出于提高我的 C 技能的愿望 我一直在使用理论维基 http wiki theory org BitTorrentSpecification作为我的向导 我已经建
  • OleDbDataAdapter 未填充所有行

    嘿 我正在使用 DataAdapter 读取 Excel 文件并用该数据填充数据表 这是我的查询和连接字符串 private string Query SELECT FROM Sheet1 private string ConnectStr
  • 将 VSIX 功能添加到 C# 类库

    我有一个现有的单文件生成器 位于 C 类库中 如何将 VSIX 项目级功能添加到此项目 最终目标是编译我的类库项目并获得 VSIX 我实际上是在回答我自己的问题 这与Visual Studio 2017 中的单文件生成器更改 https s
  • 带动态元素的 WPF 启动屏幕。如何?

    我是 WPF 新手 我需要一些帮助 我有一个加载缓慢的 WPF 应用程序 因此我显示启动屏幕作为权宜之计 但是 我希望能够在每次运行时更改屏幕 并在文本区域中显示不同的引言 这是一个生产力应用程序 所以我将使用非愚蠢但激励性的引言 当然 如
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • 如何序列化/反序列化自定义数据集

    我有一个 winforms 应用程序 它使用强类型的自定义数据集来保存数据进行处理 它由数据库中的数据填充 我有一个用户控件 它接受任何自定义数据集并在数据网格中显示内容 这用于测试和调试 为了使控件可重用 我将自定义数据集视为普通的 Sy
  • 如何使用 C# / .Net 将文件列表从 AWS S3 下载到我的设备?

    我希望下载存储在 S3 中的多个图像 但目前如果我只能下载一个就足够了 我有对象路径的信息 当我运行以下代码时 出现此错误 遇到错误 消息 读取对象时 访问被拒绝 我首先做一个亚马逊S3客户端基于我的密钥和访问配置的对象连接到服务器 然后创
  • 向现有 TCP 和 UDP 代码添加 SSL 支持?

    这是我的问题 现在我有一个 Linux 服务器应用程序 使用 C gcc 编写 它与 Windows C 客户端应用程序 Visual Studio 9 Qt 4 5 进行通信 是什么very在不完全破坏现有协议的情况下向双方添加 SSL
  • 如何将带有 IP 地址的连接字符串放入 web.config 文件中?

    我们当前在 web config 文件中使用以下连接字符串 add name DBConnectionString connectionString Data Source ourServer Initial Catalog ourDB P
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • C# 模拟VolumeMute按下

    我得到以下代码来模拟音量静音按键 DllImport coredll dll SetLastError true static extern void keybd event byte bVk byte bScan int dwFlags
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写
  • 使用.NET技术录制屏幕视频[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有一种方法可以使用 NET 技术来录制屏幕 无论是桌面还是窗口 我的目标是免费的 我喜欢小型 低

随机推荐

  • 该升级了,阿里云Code升级Codeup

    如果你还在使用阿里云Code 不防看看如何从阿里云Code升级至Codeup 云效代码管理Codeup是阿里云出品的一款企业级代码管理平台 提供代码托管 代码评审 代码扫描 质量检测等功能 全方位保护企业代码资产 帮助企业实现安全 稳定 高
  • GeoServer style(sld)中文乱码解决方法

    在说明这个问题之前 有三点需要明确 一是创建New style时 网页中文本框内的内容才是最终会应用到GeoServer的sld内容 这与本地sld文件没有关系 二是xml的encoding定义的编码不一定和文件编码 文件的字符编码 一致
  • 【C++】C++知识面经;C++易错点汇总;

    文章目录 在main执行之前和之后执行的代码可能是什么 程序在执行int main int argc char argv 时的内存结构 你了解吗 C 从代码到可执行程序 预处理 编译 汇编 链接 C 特点 为什么C 没有垃圾回收机制 这点跟
  • 算法图解 总结

    定义 算法指的是解题方案的准确而完整的描述 是一系列解决问题的清晰指令 算法代表着用系统的方法描述解决问题的策略机制 也就是说 能够对一定规范的输入 在有限时间内获得所要求的输出 如果一个算法有缺陷 或不适合于某个问题 执行这个算法将不会解
  • 怎么使用chatGTP

    ChatGTP是一个开源的即时通讯工具 可以用来在网络上进行实时文本聊天 要使用ChatGTP 您需要完成以下步骤 安装ChatGTP 在网上搜索 ChatGTP 然后在官方网站上下载并安装最新版本的软件 创建帐户 打开ChatGTP 点击
  • 怎么用python编简单游戏大全_适合新手练手的三个python简单小游戏

    学Python之前我们先来几个简单的小游戏练练手 这三个小游戏一个比一个复杂 建议新手慢慢来 1 猜拳 import random 导入随机模块 num 1 yin num 0 shu num 0 while num lt 3 if shu
  • RGMII(介质独立接口) PCB布线设计

    RGMII 介质独立接口 PCB布线设计 本文主要讲解的是RGMII PCB的设计 包括特性和运用的总结 希望大家看了以后能轻松的应对各种RGMII方案的PCB设计 网络设备一定离不开MAC和PHY 有MAC和PHY的地方就有相应的接口 无
  • Dubbo源码分析----过滤器之ActiveLimitFilter

    ActiveLimitFilter也是用来做并发控制的 区别在于ExecuteLimitFilter作用于服务端 而ActiveLimitFilter作用于客户端 看下官网的例子
  • php获取远程文件curl函数的使用

    1 curl函数介绍 curl close 关闭一个curl会话 curl copy handle 复制一个cURL句柄和它的所有选项 curl errno 返回最后一次的错误号 curl error 返回一个保护当前会话最近一次错误的字符
  • 允许asp.net web程序的跨域访问

    什么叫跨域 简单理解就是不同服务器 不同域名之间的访问 如何设置asp net web程序的跨域 在web config中添加如下代码
  • C++中的虚函数和纯虚函数详细讲解

    1 虚函数 1 1 概念 1 定义一个函数为虚函数 不代表函数为不被实现的函数 2 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数 1 2 简介 假设我们有下面的类层次 class A public virtual void fo
  • 用ping让对方电脑堵塞瘫痪

    用ping让对方电脑堵塞瘫痪2008 04 27 11 32 定义echo数据包大小 在默认的情况下windows的ping发送的数据包大小为32byt 我们也可以自己定义它的大小 但有一个大小的限制 就是最大只能发送65500byt 也许
  • Codeforces Round #736 (Div. 2)_A. Gregor and Cryptography

    A Gregor and Cryptography 题目传送门 题目传送门 题面截图 题目大意 给你个质数P 输出 a b a b a b满足 2
  • Talib技术因子详解(四)

    talib安装方式 pip install Ta lib Tushare数据获取请参考 金融量化分析基础环境搭建 数据获取代码请参考 Talib技术因子详解 一 26 MACD 异同移动平均线 调用方式如下 macd macdsignal
  • Swingbench基准测试-4、基准测试报告(results2pdf)

    生成基准测试报告 基准测试完成后 自动将生成测试结果 results xml 该文件需要可使用 results2pdf 将xml结果生成pdf格式 转换过程如下 results2pdf bat c results xml o results
  • C# 迭代器实现枚举器

    在上一篇日志中说的手动实现枚举器来枚举自定义的类型 http blog csdn net weixingstudio article details 6817319 可以看到过程非常复杂 而且容易出错 为了减轻程序员的负担 C 还提供了迭代
  • tp5开发购物商城

    Tp5 1开发购物商城 本人已自学2年 PHP计算数据库中商品总和 非联表查询 1 首先利用client type 注册类型 client id 注册ID 查询出对应的产品订单 2 在common公共文件中 利用对产品表进行查询出相应的价格
  • Qt d指针简单实现及解析

    object h ifndef OBJECT H define OBJECT H define INVOKE METHOD PRIVATE Class Class Private d func return reinterpret cast
  • 虚拟机无法打开“D:\虚拟机\CentOS.vmx”

    虚拟机无法打开 D 虚拟机 CentOS vmx 对于这个问题 解决方法是移管理员身份运行这个VM软件 但是每次都用管理员权限很麻烦 一劳永逸的方法是 1 右击软件打开属性 2 选兼容性那个 3 勾选里面的以管理员身份运行 4 点击确定就可
  • UE4和C++ 开发-Unity 过度到 Unreal 4 对比简化版

    3 两个引擎的部分术语对比 4 7 From GameObjects to Actors 从GameObjects到Actors GameObject在UE4中如何体现 UE4有一个 Gameplay Framework 系统来使用这些Ac