找回密码
 立即注册
首页 业界区 业界 ECS架构分析

ECS架构分析

决台 昨天 09:30
概述

ECS全称Entity-Component-System,即实体-组件-系统。是一种面向数据(Data-Oriented Programming)的编程架构模式。
这种架构思想是在GDC的一篇演讲《Overwatch Gameplay Architecture and Netcode》(翻成:守望先锋的游戏架构和网络代码)后受到了广泛的学习讨论。在代码设计上有一个原则“组合优于继承”,它的核心设计思想是基于这一思想的“组件式设计”。
ECS的基本类型

1.png


  • Entity(实体):在ECS架构中表示“一个单位”,可以被ECS内部标识,可以挂载若干组件。
  • Component(组件):挂载在Entity上的组件,负载实体某部分的属性,是纯数据结构不包含函数。
  • System(系统):纯函数不包含数据,只关心具有某些特定属性(组件)的Entity,对这些属性进行处理。
运行逻辑

某个业务系统筛选出拥有这个业务系统相关组件的实体,对这些实体的相关组件进行处理更新。
基本特点

Entity数据结构抽象:
PosiCompMoveCompAttrComp...PosVelocityHp...Map-Mp...--ATK...

  • 组件内聚本业务相关的属性,某个实体不同业务的属性通过组件聚合在一起。

    • 从数据结构角度上看,Entity类似一个2维的稀疏表,如上述Entity数据结构抽象
    • OOP的思路知道类型就知道了这个对象的属性,ECS的实体是知道了有哪些组件知道这个实体大概是什么,有点像鸭子理论:如果走路像鸭子、说话像鸭子、长得像鸭子、啄食也像鸭子,那它肯定就是一只鸭子。

  • 业务系统收集所有具有本业务要求组件的Entity,集中批量的处理这些Entity的相关组件
推论


  • ECS的组件式设计,是高内聚、低耦合的,对千变万化的业务需求十分友好
  • 批量处理数据在这些数据在连续内存的场合下对CPU缓存机制友好
  • 低数据耦合可以减少资源竞争对并行友好
  • ECS处理数据的方式是批量处理的,一个实体需要连续处理的场合十分不友好
个人见解

个人认为ECS架构的核心是为了解决对象中复杂的聚合问题,能有效的管理代码的复杂度,至于某些场合下的性能的提升,在大多数情况下只是锦上添花的作用(一些SLG游戏具有大量单位可能会有提升吧)。它没有传统OOP编程模式的复杂的继承关系造成的不必要的耦合,结构更加扁平化,相比之下更易于业务的阅读理解和拓展。但这种技术并非是完美无缺的,它十分不擅长单个实体需要连续处理业务(如序列化等)或实体之间相互关联等场合(如更新两个实体的距离),而且对于一些业务逻辑相对固定的模块或者一些底层模块来说,松耦合和管理复杂度可能不是首要问题,有可能在设计上硬拗ECS组件式设计反而带来困扰。对于游戏来说,ECS架构在GamePlay上的实用程度相对较高,在其他符合其特性的模块如(网络模块)也能提供一些不同以往的解题思路。
细节讨论

单例组件

Q:有些数据只需要一份或被全局访问等情况下,没必要挂载在Entity上和筛选
A:使用单例组件,和其他组件一样是纯数据,但是可以通过单例全局访问,即可以被任意系统任意访问。
工具方法

Q:有些处理方法,不适合进行批量处理(例如计算两个单位的距离,没必要弄个系统每个单位都相互计算距离)
A:用工具方法,它通常是无副作用的,不会改变任何状态,只返回计算结果
System之间的依赖关系

Q: 假设有渲染系统和碰撞系统,要像在这一帧正确的渲染目标的位置,就需要碰撞系统先更新位置信息,渲染系统在进行位置,需要正确处理系统间的前后依赖关系。
A:一个很自然的思路就是分层,根据不同层级的优先级进行处理。由此提出流水线(Piepline)的抽象,定义一颗树和相关节点,系统挂载在其节点上,运行时以某种顺序(先序遍历)展开,同一个节点的系统可以并行(没有依赖)。有需要的话流水线还可以定义系统/实体/组件的初始化等其他问题。
System对Entity的筛选

Q:“原教旨主义”的ECS框架有ECS帧的概念,系统会在每一帧重新筛选需要处理的Entity。这种处理方式引起了很大的争论,大家认为是有一些优化空间。
A:社区中几乎没人赞同“原教旨主义”的做法,原因很简单:很多Entity在整个生命周期中都没组件的增删操作,还有相当部分有的有增删操作的Entity其操作频率也很低,每帧都遍历重新筛选代价相对太过昂贵,所以有人提出了缓存、分类、延迟增删操作等思路。一种思路是:Entity的增删/组件的增删的操作进行缓存,延迟到该系统运行时在进行评估筛选,以减少遍历和重复操作。
Entity是否在运行期动态更改组件分类&System是否每帧筛选Entity分类

Q:并不是每个Entity运行期都会改变动态变更组件,有些Entity在运行期压根就不变更组件,甚至它只被编译期就知道的指定System处理。也有些System不在运行期筛选Entity,要么编译期就知道处理哪些Entity,要么是处理一些单例组件。所以有人提出要不要对Entity和System对它们是否在运行期动态操作进行分类,以提升效率。
A:个人认为,Entity不变更组件,本身变动消息就很少只有增删,配合一些缓存、延迟筛选等方法其实没什么影响。不动态筛选Entity的System倒是可以分类型关闭Entity筛选。
是否加入响应式处理

Q:ECS是“自驱式”的更新,就像是U3D的Mono的Update方法更新。还有一种响应式的更新,即基于消息事件的通知。“原教旨主义”式的ECS框架是完全自驱的,没有消息机制。系统之间“消息传递”是通过组件的数据传递的,所以在处理“当进入地图时”这种场合,只能使用“HasEnterMap”或者“Enum.EnterMap”之类的标签,或者添加一个“EnterMapComponent"来处理。
A:个人倾向于加入一些消息的处理机制,可以更灵活些。基本思路是:给System添加一个收件箱,收到的消息放在收件箱的队列里。Entity相关变更(增删、变更组件)的一些消息单独使用一个队列管道,在系统刷新的时候首先处理Entity变更消息,进行评估筛选Entity,然后处理信箱里的其他消息,然后在处理System的更新逻辑。
内存效率优化

Q:批量处理数据在物理内存连续的场合有利于CPU缓存机制,关键是如何让数据的内存连续。首先想到的是使用数组,那么是组件使用数组还是Entity使用数组呢?
A:如果是组件使用数组,那么当系统处理的Entity包含多个组件的话,那么内存访问会在不同的数组中“跳来跳去”,优化效果十分有限。个人认为若是一定要优化内存访问,关键是保证组件一样的Entity存放在连续内存(Chuck)中,这样保证System访问Entity的内存连续,具体实现方案可以参考U3D的ECS设计Archetype和Chuck。另外,也有对象池的优化空间。上面提到,ECS并不是主要解决性能问题的,只是顺带的,不必太过于执着,当然有也是极好的~。
Unity ECS引入了Archetype和Chuck两个概念,Archetype即为Entity对应的所有组件的一个组合,然后多个Archetypes会打包成一个个Archetype chunk,按照顺序放在内存里,当一个chunck满了,会在接下来的内存上的位置创建一个新的chunk。因此,这样的设计在CPU寻址时就会更容易找到Entity相关的component
2.png

原型Demo示例
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. namespace ECSDemo
  5. {
  6.     public class Singleton<T> where T : Singleton<T>, new()
  7.     {
  8.         private static T inst;
  9.         public static T Inst
  10.         {
  11.             get
  12.             {
  13.                 if (inst == null)
  14.                     inst = new T();
  15.                 return inst;
  16.             }
  17.         }
  18.     }
  19.     #region Component 组件
  20.     public class Component
  21.     {
  22.     }
  23.     public class SingleComp<T> : Singleton<T> where T : Singleton<T>, new()
  24.     {
  25.         //
  26.     }
  27.     #endregion
  28.     #region Entity 实体
  29.     public class EntityFactory
  30.     {
  31.         static long eid = 0;
  32.         public static Entity Create()
  33.         {
  34.             Entity e = new Entity(eid);
  35.             eid++;
  36.             EntityChangedMsg.Inst.Pub(e);
  37.             return e;
  38.         }
  39.         public static Entity CreatePlayer()
  40.         {
  41.             var e = Create();
  42.             e.AddComp(new PosiComp());
  43.             e.AddComp(new NameComp() { name = "Major" });
  44.             return e;
  45.         }
  46.         public static Entity CreateMonster(string name)
  47.         {
  48.             var e = Create();
  49.             e.AddComp(new PosiComp());
  50.             e.AddComp(new NameComp() { name = name });
  51.             return e;
  52.         }
  53.     }
  54.     public class Entity
  55.     {
  56.         long instID = 0;
  57.         public long InstID { get => instID; }
  58.         public Entity(long id) { instID = id; }
  59.         // 预计一个Entity组件不会很多,故使用链表...
  60.         List<Component> comps = new();
  61.         public void AddComp<T>(T t) where T : Component
  62.         {
  63.             comps.Add(t);
  64.             EntityChangedMsg.Inst.Pub(this);
  65.         }
  66.         public void RemoveComp<T>(T t) where T : Component
  67.         {
  68.             comps.Remove(t);
  69.             EntityChangedMsg.Inst.Pub(this);
  70.         }
  71.         public T GetComp<T>() where T : Component
  72.         {
  73.             foreach (var comp in comps)
  74.                 if (comp is T) return comp as T;
  75.             return default(T);
  76.         }
  77.         public bool ContrainComp(Type type)
  78.         {
  79.             foreach (var comp in comps)
  80.                 if (comp.GetType() == type) return true;
  81.             return false;
  82.         }
  83.     }
  84.     #endregion
  85.     #region System 系统
  86.     public class System
  87.     {
  88.         protected SystemMsgBox msgBox = new();
  89.         public virtual void Run()
  90.         {
  91.             msgBox.Each();
  92.             OnRun();
  93.         }
  94.         public virtual void OnRun()
  95.         {
  96.         }
  97.     }
  98.     public class SSystem : System
  99.     {
  100.         //
  101.     }
  102.     public class DSystem : System
  103.     {
  104.         protected Dictionary<long, Entity> entities = new();
  105.         protected List<Type> conds = new();
  106.         HashSet<Entity> evalSet = new();
  107.         public DSystem()
  108.         {
  109.             msgBox.Sub(EntityChangedMsg.Inst, (msg) => {
  110.                 var body = (EntityChangedMsg.MsgBody)msg;
  111.                 var e = body.Value;
  112.                 evalSet.Add(e);
  113.             });
  114.         }
  115.         public void Evalute(Entity e)
  116.         {
  117.             var id = e.InstID;
  118.             bool test = true;
  119.             foreach (var cond in conds)
  120.                 if (!e.ContrainComp(cond))
  121.                 {
  122.                     test = false;
  123.                     break;
  124.                 }
  125.             Entity cache;
  126.             entities.TryGetValue(id, out cache);
  127.             if (test)
  128.                 if (cache == null) entities.Add(id, e);
  129.                 else
  130.                 if (cache != null) entities.Remove(id);
  131.         }
  132.         public override void Run()
  133.         {
  134.             msgBox.EachEntityMsg();
  135.             foreach (var e in evalSet)
  136.                 Evalute(e);
  137.             evalSet.Clear();
  138.             msgBox.Each();
  139.             OnRun();
  140.         }
  141.     }
  142.     #endregion
  143.     #region Pipline 流水线
  144.     public class Pipeline<ENode, V>
  145.     {
  146.         public class Node<NENode, NV>
  147.         {
  148.             List<NV> items = new();
  149.             NENode node;
  150.             Node<NENode, NV> parent;
  151.             List<Node<NENode, NV>> childern = new();
  152.             public List<Node<NENode, NV>> Childern { get => childern; }
  153.             public List<NV> Items { get => items; }
  154.             public Node(NENode n)
  155.             {
  156.                 node = n;
  157.             }
  158.             public void AddChild(Node<NENode, NV> c)
  159.             {
  160.                 childern.Add(c);
  161.                 c.parent = this;
  162.             }
  163.             public void RemoveChild(Node<NENode, NV> c)
  164.             {
  165.                 childern.Remove(c);
  166.                 c.parent = null;
  167.             }
  168.             public void AddItem(NV v)
  169.             {
  170.                 items.Add(v);
  171.             }
  172.             public void RemoveItem(NV v)
  173.             {
  174.                 items.Remove(v);
  175.             }
  176.         }
  177.         Node<ENode, V> root;
  178.         Dictionary<ENode, Node<ENode, V>> dict = new();
  179.         public Pipeline(ENode node)
  180.         {
  181.             root = new Node<ENode, V>(node);
  182.             dict.Add(node, root);
  183.         }
  184.         public void AddNode(ENode n)
  185.         {
  186.             Node<ENode, V> p = root;
  187.             AddNode(n, p);
  188.         }
  189.         public void AddNode(ENode n, Node<ENode, V> p)
  190.         {
  191.             var node = new Node<ENode, V>(n);
  192.             p.AddChild(node);
  193.             dict.Add(n, node);
  194.         }
  195.         public void AddNode(ENode n, ENode p)
  196.         {
  197.             Node<ENode, V> node;
  198.             dict.TryGetValue(p, out node);
  199.             if (node != null)
  200.                 AddNode(n, node);
  201.         }
  202.         public void AddItem(ENode n, V item)
  203.         {
  204.             Node<ENode, V> node;
  205.             dict.TryGetValue(n, out node);
  206.             if (node != null)
  207.                 node.AddItem(item);
  208.         }
  209.         public void RemoveItem(ENode n, V item)
  210.         {
  211.             Node<ENode, V> node;
  212.             dict.TryGetValue(n, out node);
  213.             if (node != null)
  214.                 node.RemoveItem(item);
  215.         }
  216.         protected void Traveral(Action<V> action)
  217.         {
  218.             TraveralInner(root, action);
  219.         }
  220.         protected void TraveralInner(Node<ENode, V> node, Action<V> action)
  221.         {
  222.             var childern = node.Childern;
  223.             var items = node.Items;
  224.             foreach (var child in childern)
  225.                 TraveralInner(child, action);
  226.             foreach (var item in items)
  227.                 action(item);
  228.         }
  229.     }
  230.     public class SystemPipeline : Pipeline<ESystemNode, System>
  231.     {
  232.         public SystemPipeline(ESystemNode en) : base(en)
  233.         {
  234.             //
  235.         }
  236.         public void Update()
  237.         {
  238.             Traveral((sys) => sys.Run());
  239.         }
  240.     }
  241.     public enum ESystemNode : int
  242.     {
  243.         Root = 0,
  244.         Base = 1,
  245.         FrameWork = 2,
  246.         GamePlay = 3,
  247.     }
  248.    
  249.     #endregion
  250.     #region World 世界
  251.     public class World : Singleton<World>
  252.     {
  253.         SystemPipeline sysPipe;
  254.         public void Init()
  255.         {
  256.             sysPipe = SystemPipelineTemplate.Create();
  257.         }
  258.         public void Update()
  259.         {
  260.             sysPipe.Update();
  261.         }
  262.     }
  263.     #endregion
  264.     #region Event 事件
  265.     public class Event<T> : Singleton<Event<T>>
  266.     {
  267.         List> actions = new();
  268.         public void Sub(Action<T> action)
  269.         {
  270.             actions.Add(action);
  271.         }
  272.         public void UnSub(Action<T> action)
  273.         {
  274.             actions.Remove(action);
  275.         }
  276.         public void Pub(T t)
  277.         {
  278.             foreach (var action in actions)
  279.                 action(t);
  280.         }
  281.     }
  282.     public class EveEntityChanged : Event<Entity> { }
  283.     public interface IMsgBody
  284.     {
  285.         Type Type();
  286.     }
  287.     public interface IMsg
  288.     {
  289.         void Sub(MsgBox listener);
  290.         void UnSub(MsgBox listener);
  291.     }
  292.     public class Msg<T> : Singleton<Msg<T>>, IMsg
  293.     {
  294.         public class MsgBody : IMsgBody
  295.         {
  296.             public MsgBody(T v, Type ty) { Value = v; type = ty; }
  297.             Type type;
  298.             public T Value { private set; get; }
  299.             public Type Type()
  300.             {
  301.                 return type;
  302.             }
  303.         }
  304.         List<MsgBox> listeners = new();
  305.         public void Sub(MsgBox listener)
  306.         {
  307.             listeners.Add(listener);
  308.         }
  309.         public void UnSub(MsgBox listener)
  310.         {
  311.             listeners.Remove(listener);
  312.         }
  313.         public void Pub(T t)
  314.         {
  315.             var msgBody = new MsgBody(t, this.GetType());
  316.             foreach (var listener in listeners)
  317.                 listener.OnMsg(msgBody);
  318.         }
  319.     }
  320.     public class EntityChangedMsg : Msg<Entity> { }
  321.     public class MsgBox
  322.     {
  323.         protected Queue<IMsgBody> msgs = new();
  324.         protected Dictionary<Type, Action<IMsgBody>> handles = new();
  325.         public virtual void OnMsg(IMsgBody body)
  326.         {
  327.             msgs.Enqueue(body);
  328.         }
  329.         public void Sub(IMsg msg, Action<IMsgBody> cb)
  330.         {
  331.             msg.Sub(this);
  332.             handles.Add(msg.GetType(), cb);
  333.         }
  334.         public void UnSub(IMsg msg, Action<IMsgBody> cb)
  335.         {
  336.             msg.UnSub(this);
  337.             handles.Remove(msg.GetType());
  338.         }
  339.         public virtual void Each()
  340.         {
  341.             while (msgs.Count != 0)
  342.             {
  343.                 var msg = msgs.Dequeue();
  344.                 var type = msg.Type();
  345.                 Action<IMsgBody> handle;
  346.                 handles.TryGetValue(type, out handle);
  347.                 if (handle != null)
  348.                     handle(msg);
  349.             }
  350.         }
  351.     }
  352.     public class SystemMsgBox : MsgBox
  353.     {
  354.         Queue<IMsgBody> entityMsgs = new();
  355.         public override void OnMsg(IMsgBody body)
  356.         {
  357.             if (body.Type() == typeof(EntityChangedMsg))
  358.                 entityMsgs.Enqueue(body);
  359.             else
  360.                 msgs.Enqueue(body);
  361.         }
  362.         public void EachEntityMsg()
  363.         {
  364.             while (entityMsgs.Count != 0)
  365.             {
  366.                 var msg = entityMsgs.Dequeue();
  367.                 var type = msg.Type();
  368.                 Action<IMsgBody> handle;
  369.                 handles.TryGetValue(type, out handle);
  370.                 if (handle != null)
  371.                     handle(msg);
  372.             }
  373.         }
  374.         public override void Each()
  375.         {
  376.             while (msgs.Count != 0)
  377.             {
  378.                 var msg = msgs.Dequeue();
  379.                 var type = msg.Type();
  380.                 Action<IMsgBody> handle;
  381.                 handles.TryGetValue(type, out handle);
  382.                 if (handle != null)
  383.                     handle(msg);
  384.             }
  385.         }
  386.     }
  387.     #endregion
  388.     #region AppTest
  389.     public class AppComp : SingleComp
  390.     {
  391.         public bool hasInit;
  392.     }
  393.     public class MapComp : SingleComp<MapComp>
  394.     {
  395.         public bool hasInit;
  396.         public int monsterCnt = 2;
  397.     }
  398.     public class PosiComp : Component
  399.     {
  400.         public int x;
  401.         public int y;
  402.     }
  403.     public class NameComp : Component
  404.     {
  405.         public string name = "";
  406.     }
  407.     public class AppSystem : SSystem
  408.     {
  409.         public override void OnRun()
  410.         {
  411.             if (!AppComp.Inst.hasInit)
  412.             {
  413.                 AppComp.Inst.hasInit = true;
  414.                 Console.WriteLine("App 启动");
  415.             }
  416.         }
  417.     }
  418.     public class SystemPipelineTemplate
  419.     {
  420.         public static SystemPipeline Create()
  421.         {
  422.             SystemPipeline pipeline = new(ESystemNode.Root);
  423.             // 基本系统
  424.             pipeline.AddNode(ESystemNode.Base, ESystemNode.Root);
  425.             pipeline.AddItem(ESystemNode.Base, new AppSystem());
  426.             pipeline.AddNode(ESystemNode.GamePlay, ESystemNode.Root);
  427.             pipeline.AddItem(ESystemNode.GamePlay, new PlayerSystem());
  428.             pipeline.AddItem(ESystemNode.GamePlay, new MapSystem());
  429.             return pipeline;
  430.         }
  431.     }
  432.     public class MapSystem : DSystem
  433.     {
  434.         public MapSystem() : base()
  435.         {
  436.             conds.Add(typeof(PosiComp));
  437.             conds.Add(typeof(NameComp));
  438.         }
  439.         public override void OnRun()
  440.         {
  441.             if (!MapComp.Inst.hasInit)
  442.             {
  443.                 MapComp.Inst.hasInit = true;
  444.                 for (int i = 0; i < MapComp.Inst.monsterCnt; i++)
  445.                     EntityFactory.CreateMonster($"Monster{i + 1}");
  446.                 Console.WriteLine($"进入地图 生成{MapComp.Inst.monsterCnt}只小怪");
  447.             }
  448.             foreach (var (id, e) in entities)
  449.             {
  450.                 var name = e.GetComp<NameComp>().name;
  451.                 var x = e.GetComp<PosiComp>().x;
  452.                 var y = e.GetComp<PosiComp>().y;
  453.                 Console.WriteLine($"【{name}】 在地图的 x = {x}, y = {y}");
  454.             }
  455.         }
  456.     }
  457.     public class PlayerComp : SingleComp<PlayerComp>
  458.     {
  459.         public Entity Major;
  460.     }
  461.     public class PlayerSystem : SSystem
  462.     {
  463.         public override void OnRun()
  464.         {
  465.             base.OnRun();
  466.             if (PlayerComp.Inst.Major == null)
  467.                 PlayerComp.Inst.Major = EntityFactory.CreatePlayer();
  468.             if (Console.KeyAvailable)
  469.             {
  470.                 int dx = 0;
  471.                 int dy = 0;
  472.                 ConsoleKeyInfo key = Console.ReadKey(true);
  473.                 switch (key.Key)
  474.                 {
  475.                     case ConsoleKey.A:
  476.                         dx = -1;
  477.                         break;
  478.                     case ConsoleKey.D:
  479.                         dx = 1;
  480.                         break;
  481.                     case ConsoleKey.W:
  482.                         dy = 1;
  483.                         break;
  484.                     case ConsoleKey.S:
  485.                         dy = -1;
  486.                         break;
  487.                     default:
  488.                         break;
  489.                 }
  490.                 if (dx != 0 || dy != 0)
  491.                 {
  492.                     var comp = PlayerComp.Inst.Major.GetComp<PosiComp>();
  493.                     if (comp != null)
  494.                     {
  495.                         Console.WriteLine($"玩家移动 Delta X = {dx}, Delta Y = {dy}");
  496.                         comp.x += dx;
  497.                         comp.y += dy;
  498.                     }
  499.                 }
  500.             }
  501.         }
  502.     }
  503.     #endregion
  504.     class Program
  505.     {
  506.         static void Main(string[] args)
  507.         {
  508.             World.Inst.Init();
  509.             while (true)
  510.                 Loop();
  511.         }
  512.         public static void Loop()
  513.         {
  514.             World.Inst.Update();
  515.             Console.WriteLine("--------------------------------------------");
  516.             Thread.Sleep(1000);
  517.         }
  518.     }
  519. }
复制代码

  • Demo包含了ECS的基本定义和分层、筛选、消息等机制,简单的原型多看下应该可以看明白。
  • 当XXX的消息使用组件的数据HasInit实现,当然也可以使用消息,思路是:给System加虚函数Awake、Start、End、Destory等虚函数,SystemPipeline初始化时两次遍历分别Awake、Start,同样,清理时两次遍历调用End、Destory函数。可以在Start时监听一些消息,在End时清理。
  • Pipeline流水线有一种更加自动化的绑定节点的方法:使用C#的特性(Attribute)标记System,在程序启动通过反射自动组装。大概类似这样:
  1. [AttributeUsage(AttributeTargets.Class)]
  2. public class SystemPipelineAttr : Attribute
  3. {
  4.     public ESystemNode Type;
  5.     public SystemPipelineAttr(Type type = null)
  6.     {
  7.         this.Type = type;
  8.     }
  9. }
  10. [SystemPipelineAttr(ESystemNode.GamePlay)]
  11. public class MapSystem {} // ...
  12. // ...
  13. public static Dictionary<string, Type> GetAssemblyTypes(params Assembly[] args)
  14. {
  15.         Dictionary<string, Type> types = new Dictionary<string, Type>();
  16.         foreach (Assembly ass in args)
  17.         {
  18.             foreach (Type type in ass.GetTypes())
  19.             {
  20.                 types[type.FullName] = type;
  21.             }
  22.         }
  23.         return types;
  24. }
  25. // ...
  26. foreach (Type type in types[typeof (SystemPipelineAttr)])
  27. {
  28.         object[] attrs = type.GetCustomAttributes(typeof(SystemPipelineAttr), false);
  29.         foreach (object attr in attrs)
  30.         {
  31.                 SystemPipelineAttr attribute = attr as SystemPipelineAttr;
  32.                 // ...
  33.         }
  34. }
复制代码
备注


  • ECS的架构目前使用的非常的多,很多有名的框架设计都或多或少的受到了其影响,有:

    • U3D的ECS架构:不是指原来的GameObj那套,有专门的插件,有内存优化
    • UE4的组件设计:采用了特殊的组件实现父子关系
    • ET框架:消息 + ECS,采用ECS解耦,更注重消息驱动的响应式设计,Entity和Comp的思路也独特:Entity同时是组件,并有父子关系
    • 云风大佬的引擎:好像未开源,只有一些blog在讨论ECS,貌似连引擎层面和Lua侧都涉及ECS的设计思想


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册