首页 > 其他分享 >ET介绍—— 一切皆实体的设计

ET介绍—— 一切皆实体的设计

时间:2023-05-19 10:00:35浏览次数:48  
标签:AddComponent InstanceId 实体 Component 介绍 Entity 组件 ET

一切皆实体

目前十分流行ECS设计,主要是守望先锋的成功,引爆了这种技术。守望先锋采用了状态帧这种网络技术,客户端会进行预测,预测不准需要进行回滚,由于组件式的设计,回滚可以只回滚某些组件即可。ECS最重要的设计是逻辑跟数据的完全分离。即EC是纯数据,System实际上就是逻辑,由数据驱动逻辑。数据驱动逻辑是什么意思呢?很简单通过Update检测数据变化,通过事件机制来订阅数据变化,这就是所谓的数据驱动了。其它的特点例如缓存命中,在编写逻辑上来说并不太重要,现代游戏都用脚本,连脚本的性能都能容忍怎么会在乎缓存命中那点性能提升?ET在设计的时候吸收了这些想法,但是并不完全照搬,目前的设计是我经过长期的思考跟重构得来的,还是有些自己特色。

传统的ECS写逻辑作者看来存在不少缺陷,比如为了复用,数据必然要拆成非常小的颗粒,会导致组件非常非常多。但是游戏是多人合作开发的,每个人基本上只熟悉自己的模块,最后可能造成组件大量冗余。还有个问题,常见的ECS是扁平式的,Entity跟Component只有一层。组件一多,开发功能可能不知道该使用哪些Component。好比一家公司,最大的是老板,老板手下带几百个人,老板不可能认识所有的人,完成一项任务,老板没法挑出自己需要的人。合理的做法是老板手下应该有几个经理,每个经理手下应该有几个主管,每个主管管理几个工人,这样形成树状的管理结构才会容易管理。这类似ET的做法,Entity可以管理Component,Component管理Entity,甚至Component还可以挂载Component。例如:人由头,身体,手,脚组成,而头又由眼睛,耳朵,鼻子,嘴巴组成。

    Head head = human.AddComponent<Head>();
    head.AddComponent<Eye>();
    head.AddComponent<Mouse>();
    head.AddComponent<Nose>();
    head.AddComponent<Ear>();
    human.AddComponent<Body>();
    human.AddComponent<Hand>();
    human.AddComponent<Leg>();

 

ET中,所有数据都是Entity,包括Entity,Entity既可以当成组件使用,也可以当做其它Entity的孩子。通用的数据放在Entity身上作为成员,不太通用的数据可以作为组件挂在Entity身上。比如物品的设计,所有物品都有配置id,数量,等级的字段,这些字段没有必要做成组件,放在Entity身上使用会更加方便。

    class Item: Entity
    {
        // 道具的配置Id
        public int ConfigId { get; set; }
        // 道具的数量
        public int Count { get; set; }
        // 道具的等级
        public int Level { get; set; }
    }

 

ET的这种设计数据是一种树状的结构,非常有层次,能够非常轻松的理解整个游戏的架构。顶层Game.Scene,不同模块的数据都挂载在Game.Scene上面,每个模块自身下面又可以挂载很多数据。每开发一个新功能不用思考太多,类该怎么设计,数据放在什么地方,挂载这里会不会导致冗余等等。比如我玩家需要做一个道具系统,设计一个ItemsComponent挂在Player身上即可,需要技能开发一个SpellComponent挂在Player身上。全服需要做一个活动,搞个活动组件挂在Game.Scene上面。这种设计任务分派会很简单,十分的模块化。

组件的一些细节

1.组件的创建

组件的创建不要自己去new,应该统一使用ComponentFactory创建。ComponentFactory提供了三组方法用来创建组件Create,CreateWithParent,CreateWithId。Create是最简单的创建方式,它做了几个处理
a. 根据组件类型构造一个组件
b. 将组件加入事件系统,并且抛出一个AwakeSystem
c. 是否启用对象池
CreateWithParent在Create的基础上提供了一个Parent对象,设置到Component.Parent字段上。CreateWithId是用来创建ComponentWithId或者其子类的,在Create的基础上可以自己设置一个Id, Component在创建的时候可以选择是否使用对象池。三类工厂方法都带有一个fromPool的参数,默认是true。

2.组件的释放

Component都继承了一个IDisposable接口,需要注意,Component有非托管资源,删除一个Component必须调用该接口。该接口做了如下的操作
a. 抛出Destroy System
b. 如果组件是使用对象池创建的,那么在这里会放回对象池
c. 从全局事件系统(EventSystem)中删除该组件,并且将InstanceId设为0
如果组件挂载Entity身上,那么Entity调用Dispose的时候会自动调用身上所有Component的Dispose方法。

3.InstanceId的作用

任何Component都带有一个InstanceId字段,这个字段会在组件构造,或者组件从对象池取出的时候重新设置,这个InstanceId标识这个组件的身份。为什么需要这么一个字段呢?有以下几个原因

  1. 对象池的存在,组件未必会释放,而是回到对象池中。在异步调用中,很可能这个组件已经被释放了,然后又被重新利用了起来,这样我们需要一种方式能区分之前的组件对象是否已经被释放,例如下面这段代码:
        public static async ETVoid UpdateAsync(this ActorLocationSender self)
        {
            try
            {
                long instanceId = self.InstanceId;
                while (true)
                {
                    if (self.InstanceId != instanceId)
                    {
                        return;
                    }
                    ActorTask actorTask = await self.GetAsync();
                    
                    if (self.InstanceId != instanceId)
                    {
                        return;
                    }
                    if (actorTask.ActorRequest == null)
                    {
                        return;
                    }

                    await self.RunTask(actorTask);
                }
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }

 

while (true)中是段异步方法,await self.GetAsync()之后很可能ActorLocationSender对象已经被释放了,甚至有可能这个对象又被其它逻辑从对象池中再次利用了起来。我们这时候可以通过InstanceId的变化来判断这个对象是否已经被释放掉。
2. InstanceId是全局唯一的,并且带有位置信息,可以通过InstanceId来找到对象的位置,将消息发给对象。这个设计将会Actor消息中利用到。这里暂时就不讲了。

ET开源地址地址:egametang/ET: Unity3D Client And C# Server Framework (github.com)   qq群:474643097

标签:AddComponent,InstanceId,实体,Component,介绍,Entity,组件,ET
From: https://www.cnblogs.com/flamesky/p/17414073.html

相关文章

  • 一个.Net开发的功能强大、易于使用的流媒体服务器和管理系统
    推荐一个视频管理系统,非常适合个人或者公司打造视频网站。项目简介这是基于.NetCore开发的,跨平台的开源项目;支持多种音视频格式,如MP3、MP4、AVI、WMV、FLV等;支持本地管理与远程管理,让管理员可以轻松的管理视频资源。而且该项目还提供多平台的客户端,支持Web、桌面、Liunx、安卓......
  • 001-Leaflet-地图初始化
    一、代码1<!DOCTYPEhtml>2<htmllang="en">34<head>5<metacharset="UTF-8">6<metahttp-equiv="X-UA-Compatible"content="IE=edge">7<metaname="viewport......
  • DetGPT:看图聊天跨模态推理定位及落地复杂场景
    本文转自机器之心,作者港科大LMFlow团队&港大NLP实验室。一直以来,人类梦想着机器人能够辅助人类处理生活和工作的事情。“请帮我调低空调的温度”,甚至“请帮我写一个商城网站”都在近年来的家居助手和OpenAI发布的Copilot上得以实现。GPT-4的出现,进一步为我们展示了多模......
  • #yyds干货盘点# LeetCode程序员面试金典: 二叉树的层序遍历 II
    1.简述:给你二叉树的根节点root,返回其节点值自底向上的层序遍历。(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 示例1:输入:root=[3,9,20,null,null,15,7]输出:[[15,7],[9,20],[3]]示例2:输入:root=[1]输出:[[1]]示例3:输入:root=[]输出:[]2.代码实现:classSolution......
  • es笔记一之es安装与介绍
    本文首发于公众号:Hunter后端原文链接:es笔记一之es安装与介绍首先介绍一下es,全名为Elasticsearch,它定义上不是一种数据库,是一种搜索引擎。我们可以把海量数据都放到es里然后提供搜索操作,但是MySQL也同样可以提供搜索,为什么要用es呢?一个是因为它搜索快,使用倒排索引的......
  • macOS系统2023最佳清理软件CleanMyMac X 4.13功能介绍及如何激活解锁许可证
    CleanMyMacX4.13在软件功能列表中为MAC用户提供了常见的清理(系统垃圾、邮件附件、废纸篓)功能,还有保护(移除恶意软件、隐私)、速度(优化、维护)、应用程序(卸载器、更新程序、扩展)、文件(空间透镜、大型和旧文件、碎纸机)等功能。操作界面极其易用,例如仅需要点击几下就可以完成MAC系统的......
  • Netty集成HTTP的GET和POST通讯
    核心就是ChannelInitializer的实现使用http消息解码器packagecom.coremain.handler;importio.netty.channel.ChannelInitializer;importio.netty.channel.socket.SocketChannel;importio.netty.handler.codec.http.HttpObjectAggregator;importio.netty.handler.codec......
  • Solution Set - CDQ分治
    A[洛谷P2163].给定平面上若干个点,多次询问给定矩形内的点数。B[洛谷P3810].给定若干个三元组,对所有\(k\),求这样三元组的个数:恰有\(k\)个三元组,满足其每个分量都不超过它的相应分量。C[洛谷P3157].给定一个序列,从中依次删去某些元素,求每次删除前逆序对数目。D[CF762E/CF1045G].......
  • Ext中的get、getDom、getCmp、getBody、getDoc的区别
    Ext中包含了几个以get开头的方法,这些方法可以用来得到文档中DOM、得到当前文档中的组件、得到Ext元素等,在使用中要注意区别使用。1、get方法get方法用来得到一个Ext元素,也就是类型为Ext.Element的对象,Ext.Element类是Ext对DOM的封装,代表DOM的元素,可以为......
  • Springboot集成Netty实现TCP通讯
    Netty测试客户端packagecom.coremain;importcom.coremain.handler.ServerListenerHandler;importio.netty.bootstrap.Bootstrap;importio.netty.buffer.Unpooled;importio.netty.channel.Channel;importio.netty.channel.ChannelFuture;importio.netty.channel.Cha......