《LearnUE——基础指南:上篇—1》——GamePlay架构之Actor和Component

news2024/9/22 15:38:06

目录

Component大法好,谁用谁知道!!

1.1.1 创世(UObject)

1.1.2 造物(Actor)

1.1.3 赋能(Component)



Component大法好,谁用谁知道!!

1.1.1 创世(UObject

话说创世之初,天下一片虚无,UE界的“盘古”大帝将身一伸,天即渐高,地便坠下。而天地更有相连者,左手执凿,右手持斧,或用斧劈,或以凿开。自是神力,久而天地乃分。二气升降,清者上为天,浊者下为地,自是混沌开矣首生盘古,垂死化身眼变日月,身化大地,后人为纪念其功勋,在UE中日月取名光照系统(Light),大地誉为UObject,即万物之始。

 

 UObject提供元数据、反射生成、GC垃圾回收、序列化、编辑器可见,Class Default Object等其他几乎大部分类的原始基类都是UObject

1.1.2 造物(Actor

 UE世界中有了山川大地之后,另一位大神“女娲”在世间游玩时顿感无聊,随抓来一抔土壤(UObject),创造出了一批活灵活现的万物(Actor),由UObject派生出的Actor,除了具备UObject的属性之外,拥有自己独特的一些特性,如:Replication(网络复制),Spawn(生生死死),Tick(有了心跳)。Actor无疑是UE中最重要的角色之一,组织庞大,最常见的有StaticMeshActor, CameraActor和 PlayerStartActor等。Actor之间还可以互相“嵌套”,拥有相对的“父子”关系

其主要的Actor家族如下所示:

思考:为何Actor不像GameObject一样自带Transform?
我们知道,如果一个对象需要在3D世界中表示,那么它必然要携带一个Transform matrix来表示其位置。关键在于,在UE看来,Actor并不只是3D中的“表示”,一些不在世界里展示的“不可见对象”也可以是Actor,如AInfoAHUD,APlayerCameraManager等,代表了这个世界的某种信息、状态、规则。你可以把这些看作都是一个个默默工作的灵体Actor。所以,Actor的概念在UE里其实不是某种具象化的3D世界里的对象,而是世界里的种种元素,用更泛化抽象的概念来看,小到一个个地上的石头,大到整个世界的运行规则,都是Actor.当然,你也可以说即使带着Transform,把坐标设置为原点,然后不可见不就行了?这样其实当然也是可以,不过可能因为UE跟贴近C++一些的缘故,所以设计哲学上就更偏向于C++的哲学“不为你不需要的东西付代价”。一个Transform再加上附带的逆矩阵之类的表示,内存占用上其实也是挺可观的。要知道UE可是会抠门到连bool变量都要写成uint bPending:1;位域来节省一个字节的内存的。
换一个角度讲,如果把带Transform也当成一个Actor的额外能力可以自由装卸的话,那其实也可以自圆其说。经过了UE的权衡和考虑,把Transform封装进了SceneComponent,当作RootComponent。但在权衡到使用的便利性的时候,大部分Actor其实是有Transform的,我们会经常获取设置它的坐标,如果总是得先获取一下SceneComponent,然后再调用相应接口的话,那也太繁琐了。所以UE也为了我们直接提供了一些便利性的Actor方法,如(Get/Set)ActorLocation等,其实内部都是转发到RootComponent。

/** Returns the location of the RootComponent of this Actor*/
FORCEINLINE FVector GetActorLocation() const
{
return TemplateGetActorLocation(RootComponent);
}

/** Returns the rotation of the RootComponent of this Actor */
FORCEINLINE FRotator GetActorRotation() const
{
return TemplateGetActorRotation(RootComponent);
}

template<class T>
static FORCEINLINE FVector TemplateGetActorLocation(const T* RootComponent)
{
return (RootComponent != nullptr) ? RootComponent->GetComponentLocation() : FVector::ZeroVector;
}

template<class T>
static FORCEINLINE FRotator TemplateGetActorRotation(const T* RootComponent)
{
return (RootComponent != nullptr) ? RootComponent->GetComponentRotation() : FRotator::ZeroRotator;
}

1.1.3 赋能(Component

UE的世界纷繁复杂,光有一种Actor可不够,自然就需要有各种不同技能的Actor各司其职。在早期的版本中,每个Actor拥有的技能都是与生俱有,只能父传子子传孙,一代代的传下去。随着游戏世界的越来越绚丽,需要的技能变得越来越多和频繁改变,这样一组合,唯出身论的Actor数量们就开始爆炸了,而且一个个也越来越胖,最后连UE这样的神也管理不了了。终于,到了后期版本中,Actor只提供一些通用的基本功能,而把众多的“技能”抽象成了一个个“Component”并提供组装的接口,让Actor随用随组装,把自己武装成一个个专业能手。看见UActorComponent的U前缀,是不是想起了什么?没错,UActorComponent也是基础于UObject的一个子类,这意味着其实Component也是有UObject的那些通用功能的。

思考:Actor和Component的关系是什么?

TSet<UActorComponent*> OwnedComponents 保存着这个Actor所拥有的所有Component,一般其中会有一个SceneComponent作为RootComponent。
TArray<UActorComponent*> InstanceComponents 保存着实例化的Components。实例化是个什么意思呢,就是你在蓝图里Details定义的Component,当这个Actor被实例化的时候,这些附属的Component也会被实例化。这其实很好理解,就像士兵手上拿着把武器,当我们拥有一队士兵的时候,自然就一一对应拥有了不同实例化的武器。但OwnedComponents里总是最全的。ReplicatedComponents,InstanceComponents可以看作一个预先的分类。一个Actor若想可以被放进Level里,就必须实例化USceneComponent* RootComponent。但如果你光看代码的话,OwnedComponents其实也是可以包容多个不同SceneComponent的,然后你可以动态获取不同的SceneComponent来当作RootComponent,只不过这种用法确实不太自然,而且也得非常小心维护不同状态,不推荐如此用。在我们的直觉印象里,一个封装过后的Actor应该是一个整体,它能被放进Level中,拥有变换,这一整个整体的概念更加符合自然意识,所以我想,这也是UE为何要在Actor里一一对应一个RootComponent的原因。

主要的Component如下所示:

 ActorComponent下面最重要的一个Component就非SceneComponent莫属了。SceneComponent提供了两大能力:一是Transform,二是SceneComponent的互相嵌套。

思考:为何ActorComponent不能互相嵌套?而在SceneComponent一级才提供嵌套?

首先,ActorComponent下面当然不是只有SceneComponent,一些UMovementComponent,AIComponent,或者是我们自己写的Component,都是会直接继承ActorComponent的。但很奇怪的是,ActorComponent却是不能嵌套的,在UE的观念里,好像只有带Transform的SceneComponent才有资格被嵌套,好像Component的互相嵌套必须和3D里的transform父子对应起来。老实说,如果让我来设计Entity-Component模式,我很可能会为了通用性而在ActorComponent这一级直接提供嵌套,这样所有的Component就与生俱来拥有了组合其他Component的能力,灵活性大大提高。但游戏引擎的设计必然也经过了各种权衡,虽然说架构上显得并不那么的统一干净,但其实也大大减少了被误用的机会。实体组件模式推崇的“组合优于继承”的概念确实很强大,但其实同时也带来了一些问题,如Component之间如何互相依赖,如何互相通信,嵌套过深导致的接口便利损失和性能损耗,真正一个让你随便嵌套的组件模式可能会在使用上更容易出问题。从功能上来说,UE更倾向于编写功能单一的Component(如UMovementComponent),而不是一个整合了其他Component的大管家Component(当然如果你偏要这么干,那UE也阻止不了你)。而从游戏逻辑的实现来说,UE也是不推荐把游戏逻辑写在Component里面,所以你其实也没什么机会去写一个很复杂的Component.

思考:Actor的SceneComponent的关?

很多其他游戏引擎,还有一种设计思路是“万物皆Node”。Node都带变换。比如说你要设计一辆汽车,一种方式是车身作为一个Node,4个轮子各为车身的子Node,然后移动父Node来前进。而在UE里,一种很可能的方式就变成,汽车是一个Actor,车身作为RootComponent,4个轮子都作为RootComponent的子SceneComponent。请读者们细细体会这二者的区别。两种方式都可以实现出优秀的游戏引擎,只是有些理念和侧重点不同。从设计哲学上来说,其实你把万物看成是Node,或者是Component,并没有什么本质上的不同。看作Node的时候,Node你就要设计的比较轻量廉价,这样才能比较没有负担的创建多个,同理Component也是如此。Actor可以带多个SceneComponent来渲染多个Mesh实体,同样每个Node带一份Mesh再组合也可以实现出同样效果。个人观点来说,关键的不同是在于你是怎么划分要操作的实体的粒度的。当看成是Node时,因为Node身上的一些通用功能(事件处理等),其实我们是期望着我们可以非常灵活的操作到任何一个细小的对象,我们希望整个世界的所有物体都有一些基本的功能(比如说被拾取),这有点完美主义者的思路。而注重现实的人就会觉得,整个游戏世界里,有相当大一部分对象其实是不那么动态的。比如车子,我关心的只是整体,而不是细小到每一个车轱辘。这种理念就会导成另外一种设计思路:把要操作的实体按照功能划分,而其他的就尽量只是最简单的表示。所以在UE里,其实是把5个薄薄的SceneComponent表示再用Actor功能的盒子装了起来,而在这个盒子内部你可以编写操作这5个对象的逻辑。换做是Node模式,想编写操作逻辑的话,一般就来说就会内化到父Node的内部,不免会有逻辑与表现掺杂之嫌,而如果Node要把逻辑再用组合分离开的话,其实也就转化成了某种ScriptComponent。

思考:Actor之间的父子关系是怎么确定的?

你应该已经注意到了Actor里面的TArray<AActor*> Children字段,所以你可能会期望看到Actor:AddChild之类的方法,很遗憾。在UE里,Actor之间的父子关系却是通过Component确定的。同一般的Parent:AddChild操作原语不同,UE里是通过Child:AttachToActor或Child:AttachToComponent来创建父子连接的。

void AActor::AttachToActor(AActor* ParentActor, const FAttachmentTransformRules& AttachmentRules, FName SocketName)
{
	if (RootComponent && ParentActor)
	{
		USceneComponent* ParentDefaultAttachComponent = ParentActor->GetDefaultAttachComponent();
		if (ParentDefaultAttachComponent)
		{
			RootComponent->AttachToComponent(ParentDefaultAttachComponent, AttachmentRules, SocketName);
		}
	}
}
void AActor::AttachToComponent(USceneComponent* Parent, const FAttachmentTransformRules& AttachmentRules, FName SocketName)
{
	if (RootComponent && Parent)
	{
		RootComponent->AttachToComponent(Parent, AttachmentRules, SocketName);
	}
}

 上篇:《LearnUE——基础指南:上篇—0》——UE架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/480238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

合肥职业技术学院分类考试招生职业技能考试 -- 计算机专业

考试大纲模块一 专业能力测试主要内容模块二 技术技能测试主要内容分值分布 分模块讲解模块一 专业能力测试计算机的发展、类型及其应用领域计算机技术的发展计算机应用领域 计算机中数据的表示、存储和处理计算机软、硬件系统的组成及主要技术指标计算机软、硬件系统的组成硬件…

YOLOv5-7.0训练中文标签的数据集

链接&#xff1a;https://pan.baidu.com/s/1KSROxTwyYnNoNxI5Tk13Dg 提取码&#xff1a;8888 以显示楷体为例&#xff08;上面的百度网盘里面有黑体、宋体、楷体ttf文件&#xff09; (1)将metric.py中&#xff1a; 将 sn.set(font_scale1.0 if nc < 50 else 0.8) # for …

iOS可视化动态绘制八种排序过程

一、可视化解决方案综述 1.交互UI综述 在本篇博客的第一部分我们先来整体的看一下我们Demo的功能。下方就是我们今天博客中的Demo的交互示意图。上方的输入框可以输入要排序元素的个数&#xff0c;下方输入的是300。程序会根据你输入的个数来随机生成数据&#xff0c;你输入30…

D. Edge Deletion(堆优化最短路)

Problem - D - Codeforces 给定一个由 n 个顶点和 m 条边组成的无向连通加权图。将从顶点 1 到顶点 i 的最短路径长度表示为 di。 你必须删除一些图中的边&#xff0c;使得最多只保留 k 条边。如果在删除边后&#xff0c;仍然存在从 1 到 i 的路径&#xff0c;其长度为 di&…

【数学建模】matlab的常用函数运用(1)

文章目录 1. matlab基本常识2. 常用输入输出函数2.1 输出函数2.2 拼接函数&#xff08;字符串的合并&#xff09;2.3 输入函数 3. 求和函数3.1 向量求和3.2 矩阵求和 4. 提取矩阵元素4.1 取第x行第y列的元素4.2 取指定行或列的所有元素4.3 取指定某些行的所有元素 1. matlab基本…

【OMNET++】V2X仿真

1.前言 车载无线通信技术V2X即Vehicle to Everything&#xff0c;是在车辆和任何会被该车辆所影响的实体之间分享信息的技术。V2X的主要动机是道路安全、交通效率和节能。 车辆影响实体的分类&#xff1a; Vehicle:对应其他车辆&#xff0c;对应通信V2V&#xff0c;对应设备是…

Linux学习[9]查找文件指令:which whereis locate find

文章目录 前言1. which2. whereis3. locate4. find总结&#xff1a; 前言 之前在弄交叉编译的时候需要找到gcc&#xff0c;gdb什么的在哪里&#xff1b;涉及到了查找文件指令。 这里对linux中的查找指令进行总结 1. which which指令一般用来寻找可执行文件的路径&#xff0c;…

C. Playing Piano(dfs)

Problem - C - Codeforces 小Paul想学弹钢琴。他已经有了一首想要开始演奏的旋律。为简单起见&#xff0c;他将这个旋律表示为键号序列a1,a2,…,an&#xff1a;数字越大&#xff0c;它就越靠近钢琴键盘的右侧。 Paul非常聪明&#xff0c;知道关键是正确地为他要演奏的音符分配…

git-windows安装

1.下载地址 https://www.git-scm.com/ 2.第一步&#xff0c;直接next 3.选择默认安装路径 4. 选择组件&#xff0c;默认 5.开始菜单是否创建&#xff0c;默认不创建 6.这里是设置 Git 默认编辑器&#xff0c;我们这里直接下一步 "Next" 7.调整新仓库中初始分支的名称…

观察 | 卫浴产业数字化转型下的中国智造样本

文 | 智能相对论 作者 | 佘凯文 数字技术的发展已成为全球科技变革向高端技术不断升级的方向。 年初&#xff0c;中共中央、国务院印发《数字中国建设整体布局规划》&#xff0c;这是党的二十大后党中央在我国数字化发展领域作出的最全面擘画&#xff0c;从顶层设计的高度对…

elasticsearch结构化查询(一)

在上一篇中我们介绍了DSL相关的知识&#xff0c;接下来我们将会学习elasticsearch的结构化查询&#xff0c;同时也实践一下上一篇的DSL的查询用法 什么是结构化搜索? 从《Elasticsearch权威指南》上摘取部分解释如下: 结构化搜索是指查询包含内部结构的数据。日期&#xff0…

CentOS 7.6更改yum源

使用字符串替换 我这里的操作参考了https://baijiahao.baidu.com/s?id1708418392526536542&wfrspider&forpc这篇文章&#xff0c;https://mirrors.tuna.tsinghua.edu.cn/help/centos/是清华大学官网教程。 /etc/yum.repos.d/CentOS-Base.repo文件如下&#xff1a; #…

Python的类与对象、构造方法、类与对象三大特性封装、继承和多态、类型注解

类与对象 1.Python的对象 使用对象组织数据 在程序中是可以做到和生活中那样&#xff0c;设计表格、生产表格、填写表格的组织形式的。 在程序中设计表格&#xff0c;我们称之为&#xff1a;设计类(class) class Student: name None #记录学生姓名 在程序中打印生产表格&…

【MySQL】函数和约束

如标题所说,本文重点只有两个:MySQL语句里面的函数和约束 目录 1. 函数1.1 字符串函数1.2 数值函数1.3 日期函数1.4 流程函数 2.约束2.1 外键的删除更新行为 1. 函数 因为在前一篇文章里面有讲到聚合函数,所以在这里就不重复介绍了,本文所介绍的函数有4类:字符串函数,数值函数…

瑞吉外卖+Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目

瑞吉外卖 Redis基础 Redis入门 redis.io nosql没有表的概念 下载与安装 注意关闭防火墙 systemctl stop firewalld 启动redis src/redis-server ./redis.conf 数据类型 常用命令 字符串 string 操作命令 哈希 hash 操作命令 列表list(类似 栈 )操作命令 集合set 操作命令 sdif…

【源码解析】流控框架Sentinel源码解析

Sentinel简介 Sentinel是阿里开源的一款面向分布式、多语言异构化服务架构的流量治理组件。 主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 核心概念 资源 资源…

【分布式】数据冗余

当我们拥有了许多的存储服务器&#xff0c;且通过将数据在网关进行一致性哈希或者哈希桶的分发之后&#xff0c;我们拥有了一个具有基本负载均衡的系统&#xff0c;但是&#xff0c;此时我们又有新的问题产生了&#xff1a;我们所有的数据只有一份&#xff0c;如果这一份数据丢…

OD工具之动态逆向分析技术实例分析

OD工具之动态逆向分析技术实例分析 vscode等编写cmp.cOD工具打开cmp.exe 卧槽垃圾高级软件工程真是烦人还是记录一下吧那么简单的几行没有手册搞半天都无力吐槽了 vscode等编写cmp.c 在vscode等编辑器中编写cmp.c文件&#xff1a; #include<stdio.h> int main() {int …

手机信息管理系统【控制台+MySQL】(Java课设)

系统类型 控制台类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87737284 更多系统资源库地…

CTFshow Web入门 命令执行

目录 web29 web30 web31 web32 web33 web34 web35 web36 web37 web38 web39 web40 web41 web42 web43 web44 web45 web46 web47 web48 web49 web50 web51 web52 web53 web54 web55 web56 web57 web58 web59 web60-65 web66 web67 web68 we…