GameFramework解析:实体(Entity)
前言
如果你看过GF的UI模块,那么实体模块就会相当容易理解,因为实体模块的结构和UI模块有许多相似的地方。
先来看一下GF官方是如何定义的:
我们将游戏场景中,动态创建的一切物体定义为实体。此模块提供管理实体和实体组的功能,如显示隐藏实体、挂接实体(如挂接武器、坐骑,或者抓起另一个实体)等。实体使用结束后可以不立刻销毁,从而等待下一次重新使用。
实体模块结构
从结构图上就可以看出,实体模块的确和UI模块的结构差不多,但也可以看见一些区别:- EntityGroup对接的下层是IEntity,也就是Entity,而不是EntityInfo。而UI模块中,UIGroup里面接收管理的是UIFormInfo,而不是UIForm.
- 实体模块里面特有的功能是“附加”功能。实体可以附加其他实体,也可以被其他实体附加。
- 在EntityManager中,除了管理EntityGroup,还有m_EntityInfos来存储所有的实体。UI模块中,UIManager只会直接对接UIGroup,而不会直接管理UIFormInfo。
接下来,我们从上往下一步一步进行拆解。
EntityManager
EntityManager是外部访问框架实体模块的入口。提供给外部实体的显示、隐藏、附加、查找,获取等功能。这里从实体的生成和隐藏的流程和逻辑入手,说明EntityManager的工作原理和流程。
ShowEntity流程
HideEntity流程
类内信息
定义变量:
- m_EntityInfos:存储所有实体的列表。从结构上来讲,其实只需要一个m_EntityGroups就够了,但之所以整一个整体列表,目前还是为了方便查询和获取单个列表,经典的空间换时间。这也解释了为什么我们通过结构图上面看到的EntityGroup对接的是IEntity,而不是EntityInfo。
- m_EntityGroups:存储所有实体组的列表。
- m_EntitiesBeingLoaded: 正在加载的实体列表。这也是为了解决异步加载资源可能的冲突问题。
- m_EntitiesToReleaseOnLoad: 待释放的实体列表。同样为了解决异步加载资源可能的冲突问题。注意这里的判断列表只是相当于一个用于判断的“标识”,与真正的实体回收逻辑无关。
- m_RecycleQueue:回收实体队列。如果需要回收一个实体,会把此实体添加进当前的队列,待下一帧Update时,会把队列中的实体全部回收。
- m_LoadAssetCallbacks:GF里封装了加载资源回调委托类,通过初始化实例,创建实体的时候传递给ResourceManager,资源加载完成后就会执行对应的方法。
- m_ObjectPoolManager:对象池管理器。
- m_ResoureceManager:资源管理器。
- m_EntityHelper:实体辅助器。用于书写实体创建逻辑,开发者可以通过修改或者自写EntityHelper来实现自己的实体创建逻辑。这其实类似工厂模式,把创建和使用的逻辑进行了分离。
- m_Serial: 实体生命周期内的唯一标识。每次创建实体的时候,都会进行自增,它是每个实体在其生命周期中内的唯一标识符。即使是同一个实体实例,在被隐藏放入对象池后重新取出使用的时候,其m_Serial也会发生变化。且它的自增在资源加载之前进行,并不依赖于资源加载完成逻辑。自然其存在对应的也是m_EntityInfos,m_EntitiesBeingLoaded,m_EntitiesToReleaseOnLoad里面key值。
- m_IsShutDown: 当前模块是否关闭。
提供功能:
- 查询接口:提供GetEntityGroup,GetAllEntityGroups,HasEntity,GetEntity,GetEntities,GetAllLoadedEntities,IsLoadingEntity,GetAllLoadingEntityIds,IsValidEntity,GetParentEntity,GetChildEntityCount,GetChildEntities,GetEntityInfo来对实体和实体组进行查询和获取。
- 操作接口:AddEntityGroup,ShowEntity,HideEntity,HideAllLoadedEntities,AttachEntity,DetachEntity,DetachChildEntities,InternalShowEntity,InternalHideEntity来控制实体的显示,隐藏等操作。
- EntityGroup管理与生命周期的传递:通过Dictionary<string, EntityGroup>的字典结构来存储所有的实体组。EntityManager的Update方法中会遍历整个字典,调用每一个EntityGroup的Update方法,而每个EntityGroup的Update里面又会以同样的方式调用每个实体的Update方法。如此就做到的生命周期的传递。
- 资源管理:在定义变量的时候,我们确实可以看到获取了对象池模块的管理器,但是并没有直接在EntityManager里面去定义对象池,而是在创建实体的时候,传递给实体组,在每个实体组里面去创建对应的对象池,用于缓存当前组的实例。这也是和UI模块的结构不同的地方。
EntityGroup
通过上面,已经可以了解到EntityManager可以通过EntityGroup来管理实体,且可以通过轮询EntityGroup的Update方法,进而轮询Entity的Update方法,实现实体的生命周期的传递。以及每个EntityGroup里面有其对应的对象池,用于缓存当前组的实例。
EntityGroup作为EntityManager的内部类,其的主要作用就是管理其组内的实体,并提供相应的接口给EntityManager来对其组实体进行查询和操作。但是相对于UI模块里面,UIGroup里面包含的暂停,恢复,修改深度,刷新管理页面等功能,EntityGroup并没有这些功能,不需要这些规则,也自然不需要这样的功能,这才能保证实体模块在使用中的灵活性。
此外,说到存储实体的对象池分部到每一个EntityGroup里面,是因为每一种实体其实都是一个对象池。
那么分组按照什么来分呢?
这里的分组其实就是有一个种实体为其分一组。注意这里的说法,并不是说“一类实体分一组,一组内可以包含多种同类型的实体”,而是单纯的一类实体就是一个组。比如武器这个总的类型里面有3种敌人:敌人A,敌人B,敌人C,那么就分三个组————敌人A,敌人B,敌人C各自单独就是一组,因为这每一种敌人都可能被多次生成,所以一种就是一组,并不是说敌人一个组里面包含了这三种敌人。
类内信息
变量定义:
- m_Name:实体组的名称。
- m_Entities:实体组内的存放当前组所有实体的链表。
- m_InstancePool:实体组内的对象池。
- m_EntityGroupHelper: 实体组辅助器。
- m_CachedNodes:缓存的节点。起到标识以解决冲突的作用,这个缓存节点是用于解决执行当前实体Update和移除时候的冲突的。Update中通过此节点缓存,移除方法中通过此节点判断是否当前要移除的实体是否正在执行Update,如果是,那就给它跳过,避免出错。
1 |
|
提供功能:
- 查询接口:HasEntity,GetEntity,GetEntities,GetAllEntities可以查询和获取对应的实体。
- 操作接口:AddEntity,RemoveEntity可以对实体组进行操作。
- 对象池相关操作接口:RegisterEntityInstanceObject,SpawnEntityInstanceObject,UnspawnEntity,SetEntityInstanceLocked,SetEntityInstancePriority能够设置控制当前组内对象池。
EntityInfo
和EntityGroup一样,EntityInfo都是EntityManager的内部类,m_EntityInfos里面装的并不是IEntiy,而是EntityInfo。EntityInfo对Entity的相关信息进行了一层封装,里面除了包含对应的实体实例之外,还包含了当前实例的状态,EntityManager在设置和获取实体的状态时候,都是通过设置m_EntityInfos里面对应EnttiyInfo进行的。
里面主要就四个东西对应四个功能:
- m_Entity: 实体实例。
- m_Status: 当前实体的状态。对应其生命周期。
- m_ParentEntity: 父实体。用于控制当前实体附加的父实体。
- m_ChildEntities: 子实体列表。用于控制附加在此实体上的子实体。
Entity
GF层里面提供的是IEntity接口,UGF层里面的Entity类实现了此接口。通过上面的打开流程可以得知,在通过Entity脚本是通过EntityHelper创建并添加到其实体上的,以让此实体接收EntityManager和其对应的EntityGroup的管理。
Entity在这里两个作用:
- 让当前实体接收EntityManager和EntityGroup的管理。
- 传递生命周期给逻辑层EntityLogic。
生命周期
- OnInit & OnRecycle
- OnInit:在EntityManager执行ShowEntity的时候被调用。初始化Id和资源名称之后,获取或者创建EntityLogic到当前的实体上,并执行EnityLogic的OnInit方法。
- OnRecycle:在EntityManager执行HideEntity的时候被调用。执行EntityLogic的OnRecycle方法,并关闭其EntityLogic,让当前Id归零。
- OnShow & OnHide
- OnShow:OnInit执行后调用。执行EntityLogic的OnShow方法
- OnHide:OnRecycle执行之前调用。执行EntityLogic的OnHide方法。
- OnAttached & OnDetached | OnAttachTo & OnDetachFrom
- OnAttached:当实体附加其他实体时被调用。
- OnDetached:当实体被解除其附加实体的时候被调用。
- OnAttachTo:当实体被附加在其他实体上时被调用。
- OnDetachFrom:当实体被从其附加的实体上解除的时候调用。
- OnUpdate:通过EntityGroup传递。
EntityLogic
EntityLogic是实体书写业务逻辑的地方,它接受了Entity传递过来的生命周期,对于任何的基层逻辑我们都可以在EntityLogic里面书写,比如在默认逻辑里面如果一个实体被隐藏了,除了其GO被对象池回收外,此GO会因为EntityLogic的OnHide方法被设置不可见,我们只需要根据当前需求去书写当前实体的业务逻辑即可。
不过,一般的话,并不会直接在基层的EntityLogic里面写太多的逻辑,而是通过继承EntityLogic,然后重写并拓展其中的方法来实现自己的业务逻辑。另外,如果是自己重写了EntityLogic,那么就需要提前挂载对应的脚本到实体预制体上面,否则Entity只会添加一个默认的EntityLogic。