开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:2212

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 3566|回复: 53

[士郎] 一个无框架的ECS实现(Entity-Component-System)

[复制链接]  [移动端链接]
排名
3
昨日变化

6378

主题

6892

帖子

2万

积分

Rank: 16

UID
1231
好友
185
蛮牛币
2775
威望
30
注册时间
2013-7-29
在线时间
3267 小时
最后登录
2018-5-25

社区QQ达人活力之星原创精华达人突出贡献奖财富之证游戏蛮牛QQ群会员蛮牛妹VIP

发表于 2018-1-23 14:59:21 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x

1.png


咱们先从一切的起源说起——

只要是游戏,大多都会出现这样一个Enity-Manager系统。因为游戏本质就是大量实体行为(Enity)以及他们之间的交互(Manager)。

2.png


但很显然,一个游戏不可能只有两个类。随着逻辑的膨胀,出于各种原因都会进行逻辑的拆分。而比起继承,复合的灵活性更强,所以最后基本都会变成这样一个状态:

3.jpg

其实一般的游戏到这个状态就可以了,偶尔也会有一些继承关系穿插其中。但在实际的逻辑编写过程中,经常会出现一些恼人的两择问题

同一段逻辑,我到底是应该放在Component,还是Manager里呢?
因为这两个东西是相互依赖的,放哪儿其实都一样,而到底放那里才合适往往并不是那么容易判断的。因此过一阵子,即使是代码的编写者也不记得到底是放在Component还是Manager里了,得两边都找一次才可以。假如,Component和Manager并不是简单的两级关系,而是多级,就更好玩儿了。

通常,与多个Component相关的逻辑代码,放在Manager更合适。但是假如把只和一个Component相关的代码放在Manager,也只是看起来有点像静态方法,有点蠢,但并没有大碍。所以,经过权衡之后,开发者决定把Component的所有逻辑全部移动到对应的Manager上,以消除这种二择难题,这就产生了System:

4.jpg
写作Component,实为Data



这样移动逻辑之后,由于Data(Component)的依赖关系变得很简单,开发者又发现,其实Data胡乱拆分也没有关系,System也可以不受限制根据需要操作多个Data的数据,于是就变成了这样:
5.jpg
这就是ECS(Entity-Component-System)


实际上,这只是一个正常的架构优化,最主要的“特色”是将Component的逻辑全部移动到了System上,其他部分都是顺理成章的结果。


基本特征如下:
  • System是唯一承载逻辑的地方
  • Data(阿呸,是Component)不允许有逻辑,对外依赖就更不能有了
  • Entity首先是一个Data,但本质上是个多个Data的桥梁,用于标识它们属于同一物体。在不同的数据结构下,它甚至可以仅仅是一个int。
  • 在允许的情况下,System并不直接依赖Entity,因为并不需要。System直接依赖Data也有利于清晰判断依赖关系。
  • 至于System之间的相互依赖关系,和以前Component,Manager之间的相互依赖还是一样的。该怎么处理就怎么处理,这是ECS之外的问题。

一些意外的收获

  • 由于Data被拆散了,不容易遇到读入整个对象却只使用其中一个属性的情况(比如我们常见的读入一个Vector3却只使用一个x),有利于Cache(不过一般不会抠到这个份儿上)
  • 由于Data被拆散了,状态同步的功能可以直接放在Data上,同步逻辑会变得简单。
  • 由于Data和System之间依赖关系明确,交叉较少,对线程安全非常友好。在摩尔定律单核失效的现在,多线程会变得越来越重要

下面是个示例,玩家控制的两个球会吞吃屏幕中的点变大,球之间会相互推挤保证不重叠,包含一个吞食运动动画。

1.1.gif


首先是Component,也就是些纯数据类。数据类固定包含一个Entity的链接让它们能联系在一起。
[AppleScript] 纯文本查看 复制代码
public class BaseComponent : DItem[/align]
[align=left]{[/align]
[align=left]    public Entity entity;[/align]
[align=left]}[/align]

[align=left]public class PositionComponent : BaseComponent[/align]
[align=left]{[/align]
[align=left]    public Vector2 value;[/align]
[align=left]}[/align]

[align=left]public class SizeComponent : BaseComponent[/align]
[align=left]{[/align]
[align=left]    public float value;[/align]
[align=left]}[/align]

[align=left]public class SpeedComponent : BaseComponent[/align]
[align=left]{[/align]
[align=left]    public Vector2 value;[/align]
[align=left]    public float maxValue;[/align]
[align=left]}[/align]

[align=left]public class ColorComponent : BaseComponent[/align]
[align=left]{[/align]
[align=left]    public Color value = Color.white;[/align]
[align=left]}[/align]

[align=left]public class TeamComponent : BaseComponent[/align]
[align=left]{[/align]
[align=left]    public int id;[/align]
[align=left]}[/align]

[align=left]//与Unity组件的桥接[/align]
[align=left]public class GameObjectComponent : BaseComponent[/align]
[align=left]{[/align]
[align=left]    public GameObject gameObject;[/align]
[align=left]    public Transform transform;[/align]
[align=left]    public SpriteRenderer spriteRenderer;[/align]
[align=left]}[/align]

[align=left]//临时特效型Component[/align]
[align=left]public class EatingComponent : BaseComponent[/align]
[align=left]{[/align]
[align=left]    public GameObjectComponent go;[/align]
[align=left]    public PositionComponent target;[/align]
[align=left]    public Vector2 startOffest;[/align]
[align=left]    public Vector2 endOffest;[/align]
[align=left]    public float dur = 0.2f;[/align]
[align=left]    public float endTime;[/align]

[align=left]    //仅操作数据的方法可以存在[/align]
[align=left]    public float GetLifePercent()[/align]
[align=left]    {[/align]
[align=left]        return 1f - (endTime - Time.time) / dur;[/align]
[align=left]    }[/align]

[align=left]    public void Start()[/align]
[align=left]    {[/align]
[align=left]        endTime = Time.time + dur;[/align]
[align=left]    }[/align]

[align=left]    public Vector2 GetCurPosition()[/align]
[align=left]    {[/align]
[align=left]        return target.value + Vector2.Lerp(startOffest, endOffest, GetLifePercent());[/align]
[align=left]    }[/align]
[align=left]}



Entity部分,这里并没有维护Component数组,而是以“写死”的方式把固定的Component创建出来并保存在字段里。因为背后并没有框架,并不需要提供框架需要的数据。而即使背后有框架,为了性能通常也会像这样把每个Component取出来“写死”放在一个固定的地方,其实也没啥太大的区别。
这样做的缺陷是无法动态增加Component,但是在项目逻辑代码内,需要动态Component的情况又有多少呢?真需要动态Component的时候(比如Buff),再加一个专门的数组管理也不迟。

[AppleScript] 纯文本查看 复制代码
public class Entity : DItem[/align]
[align=left]{[/align]
[align=left]    public GameObjectComponent gameObject;[/align]
[align=left]    public PositionComponent position;[/align]
[align=left]    public SizeComponent size;[/align]
[align=left]    public ColorComponent color;[/align]
[align=left]    public TeamComponent team;[/align]
[align=left]    public Entity()[/align]
[align=left]    {[/align]
[align=left]        gameObject = new GameObjectComponent() { entity = this };[/align]
[align=left]        position = new PositionComponent() { entity = this };[/align]
[align=left]        size = new SizeComponent() { entity = this };[/align]
[align=left]        color = new ColorComponent() { entity = this };[/align]
[align=left]        team = new TeamComponent() { entity = this };[/align]
[align=left]    }[/align]
[align=left]}[/align]

[align=left]public class MoveAbleEntity : Entity[/align]
[align=left]{[/align]
[align=left]    public SpeedComponent speed;[/align]
[align=left]    public MoveAbleEntity() : base()[/align]
[align=left]    {[/align]
[align=left]        speed = new SpeedComponent() { entity = this };[/align]
[align=left]    }[/align]
[align=left]}


System部分其实近似于静态类,仅仅保留一个Root对象的链接以避免出现单例。而整个系统中,也只有System才有权限访问Root对象。
目前所有的数据列表都保存在GameWorld这个Root对象中,通过Root对象也可以访问到其他的System。


可以看到,下面这些类只有EatSystem直接依赖了Entity,那是因为它涉及到了Entity本身的增删。其他的System都避免了对具体Entity的依赖,而只依赖零散的Component。


虽然看上去有点蠢,但这样在Entity拥有多个版本的时候,System并不需要关心自己操作的具体是哪一个,也就是Entity实际上拥有了“无限的多态特性”。

[AppleScript] 纯文本查看 复制代码
public class SystemBase
{
    public GameWorld world;
    public SystemBase(GameWorld world)
    {
        this.world = world;
    }
}

//移动
public class MoveSystem : SystemBase
{
    public MoveSystem(GameWorld world) : base(world) { }
    public void Add(SpeedComponent speed)
    {
        world.speeds.DelayAdd(speed);
    }

    public void Remove(SpeedComponent speed)
    {
        world.speeds.DelayRemove(speed);
    }

    public void Update(SpeedComponent speed, PositionComponent position, SizeComponent size)
    {
        position.value += speed.value * Time.deltaTime;
        if (position.value.x > world.screenRect.xMax - size.value)
        {
            position.value.x = world.screenRect.xMax - size.value;
            speed.value.x = 0f;
        }
        else if (position.value.x < world.screenRect.xMin + size.value)
        {
            position.value.x = world.screenRect.xMin + size.value;
            speed.value.x = 0f;
        }
        if (position.value.y > world.screenRect.yMax - size.value)
        {
            position.value.y = world.screenRect.yMax - size.value;
            speed.value.y = 0f;
        }
        else if (position.value.y < world.screenRect.yMin + size.value)
        {
            position.value.y = world.screenRect.yMin + size.value;
            speed.value.y = 0f;
        }
    }
}

//操控
public class InputSystem : SystemBase
{
    public InputSystem(GameWorld world) : base(world) { }
    public void Update(SpeedComponent speed, PositionComponent position)
    {
        Vector2 delta = (Vector2)world.mainCamera.ScreenToWorldPoint(Input.mousePosition) - position.value;
        speed.value = Vector2.ClampMagnitude(speed.value + delta.normalized * Time.deltaTime, speed.maxValue);
    }
}

//吞食逻辑
public class EatSystem : SystemBase
{
    public EatSystem(GameWorld world) : base(world) { }
    public void Update(PositionComponent sourcePosition, SizeComponent sourceSize, PositionComponent targetPosition, SizeComponent targetSize, Entity target)
    {
        float sizeSum = sourceSize.value + targetSize.value + 0.05f;
        if ((sourcePosition.value - target.position.value).sqrMagnitude < sizeSum * sizeSum)
        {
            sourceSize.value = Mathf.Sqrt(sourceSize.value * sourceSize.value + targetSize.value * targetSize.value);
            Kill(target, sourcePosition);
        }
    }
    public void Kill(Entity food, PositionComponent sourcePosition)
    {
        world.eatingSystem.CreateFrom(food.gameObject, food.position, sourcePosition);

        world.entitySystem.RemoveEntity(food);
        world.entitySystem.AddRandomEntity();
    }
}

//圆推挤
public class CirclePushSystem : SystemBase
{
    public CirclePushSystem(GameWorld world) : base(world) { }
    public void Update(PositionComponent pos1, SizeComponent size1, PositionComponent pos2, SizeComponent size2)
    {
        Vector2 center = Vector2.Lerp(pos1.value, pos2.value, size1.value / (size1.value + size2.value));
        Vector2 offest = pos1.value - center;
        float offestSqrMagnitude = offest.sqrMagnitude;
        float sqrRadius = size1.value * size1.value;
        if (offestSqrMagnitude < sqrRadius)
        {
            float offestMagnitude = Mathf.Sqrt(offestSqrMagnitude);
            if (offestMagnitude == 0)
                offestMagnitude = 0.01f;
            float pushMul = Mathf.Min(size1.value - offestMagnitude, (1 - offestMagnitude / size1.value) * Time.deltaTime * 10f);
            pos1.value += offest.normalized * pushMul;
        }
    }
}



这里要专门提下这个EatingSystem。它对应的是特殊的Component,和Entity无关,是在小球被吃掉时临时创建并管理它被吃掉的过程动画,操作的对象也仅仅是GameObjectComponent ,在它被创建之后,原本的Entity的生命周期其实已经结束了。


Entity唯一的作用是连接相关的Component,如果你仅仅关心它的一部分内容,就只需要引用那部分内容。GameObjectComponent就是那个小球的一张皮,我们不需要小球身上的其他逻辑,借用这张皮播一个死亡动画就可以了。


另外EatingSystem对应的EatingComponent,本身也没有对应的Entity,因为它并不需要和其他的Component连接起来。

时刻记住,在ECS里,System直接相关的是Component,而非Entity。没有什么比Entity的地位更低了。


[AppleScript] 纯文本查看 复制代码
//吞食动画[/align]
[align=left]public class EatingSystem : SystemBase[/align]
[align=left]{[/align]
[align=left]    public EatingSystem(GameWorld world) : base(world) { }[/align]
[align=left]    public void Update(EatingComponent e)[/align]
[align=left]    {[/align]
[align=left]        e.go.transform.position = e.GetCurPosition();[/align]
[align=left]        if (Time.time >= e.endTime)[/align]
[align=left]        {[/align]
[align=left]            world.eatings.DelayRemove(e);[/align]
[align=left]            world.gameObjectSystem.Remove(e.go);[/align]
[align=left]        }[/align]
[align=left]    }[/align]

[align=left]    public void CreateFrom(GameObjectComponent gameObject, PositionComponent source, PositionComponent target)[/align]
[align=left]    {[/align]
[align=left]        gameObject.entity.gameObject = null;//解除和原entity的关系[/align]

[align=left]        EatingComponent comp = new EatingComponent();[/align]
[align=left]        comp.go = gameObject;[/align]
[align=left]        comp.target = target;[/align]
[align=left]        comp.startOffest = source.value - target.value;[/align]
[align=left]        comp.endOffest = Vector2.Lerp(source.value, target.value, 0.5f) - target.value;[/align]
[align=left]        comp.Start();[/align]
[align=left]        world.eatings.DelayAdd(comp);[/align]
[align=left]    }[/align]
[align=left]}[/align]
[align=left]


这是这个系统如何和Unity的可视部分连接的。因为System不能保持状态,Unity的对象是存在专门的Component里的。除非为了接受事件,尽量不要往Untity的GameObject上添加MonoBehaviour脚本。除了性能上的考虑(Update涉及到反射),不需要加的东西干嘛非要加上去呢。

[AppleScript] 纯文本查看 复制代码
//和Unity显示部分的桥接[/align]
[align=left]public class GameObjectSystem : SystemBase[/align]
[align=left]{[/align]
[align=left]    public GameObjectSystem(GameWorld world) : base(world) { }[/align]
[align=left]    public void Add(GameObjectComponent e, PositionComponent position, SizeComponent size, ColorComponent color)[/align]
[align=left]    {[/align]
[align=left]        e.gameObject = new GameObject("Entity");[/align]
[align=left]        e.transform = e.gameObject.transform;[/align]
[align=left]        e.transform.localScale = Vector2.one * 0.001f;[/align]
[align=left]        e.spriteRenderer = e.gameObject.AddComponent<SpriteRenderer>();[/align]
[align=left]        e.spriteRenderer.sprite = UnityEditor.AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Knob.psd");[/align]
[align=left]        Update(e, position, size, color);[/align]
[align=left]    }[/align]

[align=left]    public void Remove(GameObjectComponent go)[/align]
[align=left]    {[/align]
[align=left]        GameObject.Destroy(go.gameObject);[/align]
[align=left]        go.transform = null;[/align]
[align=left]        go.gameObject = null;[/align]
[align=left]        go.spriteRenderer = null;[/align]
[align=left]    }[/align]

[align=left]    public void Update(GameObjectComponent go, PositionComponent position, SizeComponent size, ColorComponent color)[/align]
[align=left]    {[/align]
[align=left]        go.transform.position = position.value;[/align]
[align=left]        go.transform.localScale = Vector2.one * Mathf.MoveTowards(go.transform.localScale.x, size.value * 11f, Mathf.Max(0.01f, Mathf.Abs(go.transform.localScale.x - size.value)) * 10f * Time.deltaTime);[/align]
[align=left]        go.spriteRenderer.color = color.value;[/align]
[align=left]    }[/align]

[align=left]    public void SetToTop(GameObjectComponent go)[/align]
[align=left]    {[/align]
[align=left]        go.gameObject.AddComponent<SortingGroup>().sortingOrder = 1;[/align]
[align=left]    }[/align]
[align=left]}


最后是这个EntitySystem。它的逻辑都是最基本的增删Entity的过程。本来按道理System应该尽量少访问Entity,但Entity总得有个人管理才对啊。本来这段是放在GameWorld里的,犹豫了下还是单独列成了System,算是个特殊的System吧。而它的依赖关系也是最多的。

[AppleScript] 纯文本查看 复制代码
//增删物体和场景初始化[/align]
[align=left]public class EntitySystem : SystemBase[/align]
[align=left]{[/align]
[align=left]    public EntitySystem(GameWorld world) : base(world) { }[/align]

[align=left]    public void AddEntity(Entity e)[/align]
[align=left]    {[/align]
[align=left]        world.entitys.DelayAdd(e);[/align]
[align=left]        world.gameObjectSystem.Add(e.gameObject, e.position, e.size, e.color);[/align]
[align=left]    }[/align]

[align=left]    public void RemoveEntity(Entity e)[/align]
[align=left]    {[/align]
[align=left]        world.entitys.DelayRemove(e);[/align]
[align=left]        if (e.gameObject != null)[/align]
[align=left]            world.gameObjectSystem.Remove(e.gameObject);[/align]
[align=left]    }[/align]

[align=left]    public void AddRandomEntity()[/align]
[align=left]    {[/align]
[align=left]        Entity e = new Entity();[/align]
[align=left]        e.size.value = 0.025f;[/align]
[align=left]        e.team.id = 0;[/align]
[align=left]        e.position.value = new Vector2(Random.Range(world.screenRect.xMin + e.size.value, world.screenRect.xMax - e.size.value), Random.Range(world.screenRect.yMin + e.size.value, world.screenRect.yMax - e.size.value));[/align]
[align=left]        AddEntity(e);[/align]
[align=left]    }[/align]

[align=left]    public void AddMoveAbleEnity(MoveAbleEntity e)[/align]
[align=left]    {[/align]
[align=left]        this.AddEntity(e);[/align]
[align=left]        world.playerEntitys.Add(e);[/align]

[align=left]        world.moveSystem.Add(e.speed);[/align]
[align=left]        world.gameObjectSystem.SetToTop(e.gameObject);[/align]
[align=left]    }[/align]

[align=left]    public void InitScene()[/align]
[align=left]    {[/align]
[align=left]        for (int i = 0; i < 50; i++)[/align]
[align=left]        {[/align]
[align=left]            AddRandomEntity();[/align]
[align=left]        }[/align]

[align=left]        for (int i = 0; i < 2; i++)[/align]
[align=left]        {[/align]
[align=left]            MoveAbleEntity playerEntity = new MoveAbleEntity();[/align]
[align=left]            playerEntity.position.value = Vector2.zero;[/align]
[align=left]            playerEntity.size.value = 0.05f;[/align]
[align=left]            playerEntity.color.value = Color.yellow;[/align]
[align=left]            playerEntity.speed.maxValue = 1f;[/align]
[align=left]            playerEntity.team.id = 1;[/align]
[align=left]            playerEntity.position.value = new Vector2(Random.Range(-0.1f, 0.1f), Random.Range(-0.1f, 0.1f));[/align]
[align=left]            AddMoveAbleEnity(playerEntity);[/align]
[align=left]        }[/align]
[align=left]    }[/align]
[align=left]}



最后就是GameWorld部分了。首先它是一个MonoBehaviour,因为Unity程序的入口必须是MonoBehaviour。它的作用就是保存游戏里的全部对象,因为它们总得有一个地方保存。在它的Update方法里,决定不同数据的遍历逻辑,以及System的执行方式。ECS每个部分基本都很零散,总需要一个地方将它们连接在一起。
Gameworld应该是整个项目中交叉修改最多的一个文件,但也只有这个文件会这样。由于所有System同时也依赖了Gameworld,导致它的可替换性很弱,这也是这个无框架的系统最大的弱点了。


如果希望System可以多项目复用(或者更广范围的单元测试),需要对GameWorld做一些解耦处理,比如使用Event系统让System间通信,以及对数据提供通用化的存储方式,还有个办法是把System对GameWorld依赖的部分接口化……


嘛,需要的时候就做这样的修改就好了,毕竟要功能总有代价。但毕竟也有大量的团队并不需要这样做。耦合低,自然程序就会复杂,复杂就会导致成本,处理不好还会有性能损失和可靠性降低。关键在于,这种问题并不是ECS独有的,任何时候都存在这个权衡问题,没必要在这里讨论。如果要做到对GameWorld的解耦(同时保证可维护性和效率),代码量是肯定要增加的,也会让我这个示例看起来和别人写的没啥区别。

好在要处理的耦合度问题也只有System - GameWorld而已,起码问题被集中了。


(此外在Update内我有意试图多写了几种遍历方式,其实并不需要这样,仅作抛砖引玉用)

[AppleScript] 纯文本查看 复制代码
public class GameWorld : MonoBehaviour[/align]
[align=left]{[/align]
[align=left]    public DList<Entity> entitys;[/align]
[align=left]    public DList<SpeedComponent> speeds;[/align]
[align=left]    public DList<MoveAbleEntity> playerEntitys;[/align]

[align=left]    public DList<EatingComponent> eatings;[/align]

[align=left]    public EntitySystem entitySystem;[/align]
[align=left]    public MoveSystem moveSystem;[/align]
[align=left]    public GameObjectSystem gameObjectSystem;[/align]
[align=left]    public InputSystem inputSystem;[/align]
[align=left]    public CirclePushSystem circlePushSystem;[/align]
[align=left]    public EatSystem eatSystem;[/align]
[align=left]    public EatingSystem eatingSystem;[/align]

[align=left]    public Camera mainCamera;[/align]
[align=left]    public Rect screenRect;[/align]

[align=left]    void Start ()[/align]
[align=left]    {[/align]
[align=left]        if (Camera.main == null)[/align]
[align=left]        {[/align]
[align=left]            GameObject go = new GameObject("Camera");[/align]
[align=left]            mainCamera = go.AddComponent<Camera>();[/align]
[align=left]        }[/align]
[align=left]        else[/align]
[align=left]        {[/align]
[align=left]            mainCamera = Camera.main;[/align]
[align=left]        }[/align]
[align=left]        mainCamera.clearFlags = CameraClearFlags.Color;[/align]
[align=left]        mainCamera.backgroundColor = Color.black;[/align]
[align=left]        mainCamera.orthographic = true;[/align]
[align=left]        mainCamera.orthographicSize = 1f;[/align]
[align=left]        mainCamera.nearClipPlane = 0f;[/align]

[align=left]        screenRect = Rect.MinMaxRect(-mainCamera.aspect, -1f, mainCamera.aspect, 1f);[/align]

[align=left]        entitys = new DList<Entity>();[/align]
[align=left]        playerEntitys = new DList<MoveAbleEntity>();[/align]
[align=left]        speeds = new DList<SpeedComponent>();[/align]
[align=left]        eatings = new DList<EatingComponent>();[/align]

[align=left]        entitySystem = new EntitySystem(this);[/align]
[align=left]        moveSystem = new MoveSystem(this);[/align]
[align=left]        gameObjectSystem = new GameObjectSystem(this);[/align]

[align=left]        inputSystem = new InputSystem(this);[/align]

[align=left]        eatSystem = new EatSystem(this);[/align]
[align=left]        eatingSystem = new EatingSystem(this);[/align]
[align=left]        circlePushSystem = new CirclePushSystem(this);[/align]

[align=left]        entitySystem.InitScene();[/align]
[align=left]        ApplyDelayCommands();//执行延迟增删数组内容的操作[/align]
[align=left]    }[/align]


[align=left]    public void ApplyDelayCommands()[/align]
[align=left]    {[/align]
[align=left]        entitys.ApplyDelayCommands();[/align]
[align=left]        playerEntitys.ApplyDelayCommands();[/align]
[align=left]        speeds.ApplyDelayCommands();[/align]
[align=left]        eatings.ApplyDelayCommands();[/align]
[align=left]    }[/align]

[align=left]    void Update ()[/align]
[align=left]    {[/align]
[align=left]        //遍历所有Entity并执行所有相关System[/align]
[align=left]        foreach (Entity item in entitys)[/align]
[align=left]        {[/align]
[align=left]            if (item.destroyed)[/align]
[align=left]                continue;[/align]

[align=left]            gameObjectSystem.Update(item.gameObject, item.position, item.size,item.color);[/align]
[align=left]        }[/align]
[align=left]        //多对多关系[/align]
[align=left]        foreach (MoveAbleEntity player in playerEntitys)[/align]
[align=left]        {[/align]
[align=left]            if (player.destroyed)[/align]
[align=left]                continue;[/align]

[align=left]            inputSystem.Update(player.speed,player.position);[/align]
[align=left]            foreach (Entity item in entitys)[/align]
[align=left]            {[/align]
[align=left]                if (item == player || item.destroyed)[/align]
[align=left]                    continue;[/align]

[align=left]                if (item.team.id == 0) //是食物,执行吃逻辑[/align]
[align=left]                    eatSystem.Update(player.position, player.size, item.position, item.size, item);[/align]
[align=left]                else if (item.team.id == 1) //是玩家控制角色,执行圆推挤逻辑[/align]
[align=left]                    circlePushSystem.Update(player.position, player.size, item.position, item.size);[/align]
[align=left]            }[/align]
[align=left]        }[/align]
[align=left]        //单独遍历某些Component[/align]
[align=left]        foreach (SpeedComponent speed in speeds)[/align]
[align=left]        {[/align]
[align=left]            if (speed.destroyed)[/align]
[align=left]                continue;[/align]

[align=left]            Entity enity = speed.entity;[/align]
[align=left]            moveSystem.Update(speed, enity.position, enity.size);[/align]
[align=left]        }[/align]
[align=left]        //和Entity无关的Component[/align]
[align=left]        foreach (EatingComponent item in eatings)[/align]
[align=left]        {[/align]
[align=left]            if (item.destroyed)[/align]
[align=left]                continue;[/align]

[align=left]            eatingSystem.Update(item);[/align]
[align=left]        }[/align]

[align=left]        ApplyDelayCommands();[/align]
[align=left]    }[/align]
[align=left]}



最后:


  • 本文的写作理由是:偶尔看到有人说“ECS只适合大项目”,这是一个对这个观点的反驳。确实某些ECS的“写法”很适合大项目不适合小项目,但这是那个“写法”导致的,和ECS本身其实没啥关系。事实上,在同样的“写法”下,ECS相比非ECS还是有不少优点的,而且代价并不高。

  • ECS的代价,个人认为是“Component无逻辑产生的反直觉会让工程师极端不适应”,“基本废掉了继承的多态特性,导致继承无用”。有一种大众言论是“ECS是反OOP的”,如果把OOP仅仅理解成“封装,继承,多态”,这种说法确实没错,因为多态才是三者最重要的部分,而ECS确实把继承的多态特性毁掉了。但是把“OOP”理解成“面向对象编程”,那“ECS”则依然是面向对象编程的,因为System依然是对象。毁灭了继承的“多态”虽然可惜,但“多态”还是有很多其他方式可以实现的(比如说利用策略模式)。ECS本身并不会造成项目的可维护性降低。


  • ECS虽然解决了一些两择难题,但当一段逻辑放在这个System上可以,放另一个System也可以,还是会出现两择难题。放到Util类上是能解决,但用不用Util,依然是个两择难题。


  • ECS还有一个显而易见的问题。由于逻辑和状态在两个不同的类里,状态要能访问就只能全public,这多多少少还是有些隐患。


  • 虽然都要求System不能持有状态,但是假如一个状态数组和System强相关,或者仅允许这个System访问,是否应该允许将数组放在System内以限制可见度?(Component同理),对于ECS的逻辑限制到底需要遵守到什么程度还需要摸索。设计模式是用来解决问题的,而非用来遵守的。总想着遵守,最终反而会解决不了问题。这就是所谓的“过度设计”了。



知乎: flashyiyi

评分

参与人数 3鲜花 +15 收起 理由
bandok123 + 5 很给力!
个位数 + 5 很给力!
恋红尘、太虚伪 + 5 很给力!

查看全部评分

[发帖际遇]: 清风 发帖时在路边捡到 1 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

跟我念“站长妹纸萌萌哒!”我说站长,你说YO!爱你们么么哒~
回复

使用道具 举报

4四处流浪
334/500
排名
8423
昨日变化
78

0

主题

128

帖子

334

积分

Rank: 4

UID
250396
好友
0
蛮牛币
384
威望
0
注册时间
2017-10-23
在线时间
84 小时
最后登录
2018-5-25
发表于 2018-1-24 09:15:57 | 显示全部楼层
前排学习

回复

使用道具 举报

5熟悉之中
978/1000
排名
3371
昨日变化
16

1

主题

367

帖子

978

积分

Rank: 5Rank: 5

UID
122160
好友
1
蛮牛币
1602
威望
0
注册时间
2015-9-10
在线时间
228 小时
最后登录
2018-5-25
发表于 2018-1-24 09:53:59 | 显示全部楼层
感谢分享

回复

使用道具 举报

7日久生情
2692/5000
排名
2969
昨日变化
14

0

主题

1888

帖子

2692

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
219676
好友
0
蛮牛币
2488
威望
0
注册时间
2017-7-12
在线时间
370 小时
最后登录
2018-5-26

活力之星

发表于 2018-1-24 10:57:10 | 显示全部楼层
谢谢分享

回复

使用道具 举报

7日久生情
4039/5000
排名
135
昨日变化

18

主题

308

帖子

4039

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
67323
好友
1
蛮牛币
3051
威望
0
注册时间
2015-1-10
在线时间
1607 小时
最后登录
2018-5-20
发表于 2018-1-24 11:28:15 | 显示全部楼层
nice  咱正在研究这个东西

点评

那还不赶紧给我打赏点蛮牛币!  发表于 2018-1-24 13:50

回复 支持 反对

使用道具 举报

5熟悉之中
683/1000
排名
3666
昨日变化
16

0

主题

108

帖子

683

积分

Rank: 5Rank: 5

UID
127798
好友
0
蛮牛币
563
威望
0
注册时间
2015-11-3
在线时间
205 小时
最后登录
2018-5-25
发表于 2018-1-24 11:35:04 | 显示全部楼层
刚好最近在研究框架,看到了ecs,但找不到比较好的例子,感谢分享。

回复 支持 反对

使用道具 举报

5熟悉之中
898/1000
排名
3187
昨日变化
3

0

主题

235

帖子

898

积分

Rank: 5Rank: 5

UID
119648
好友
3
蛮牛币
1389
威望
0
注册时间
2015-8-25
在线时间
258 小时
最后登录
2018-4-27
QQ
发表于 2018-1-24 11:37:30 | 显示全部楼层
感谢分享

回复

使用道具 举报

3偶尔光临
253/300
排名
8590
昨日变化
8

1

主题

62

帖子

253

积分

Rank: 3Rank: 3Rank: 3

UID
223503
好友
0
蛮牛币
277
威望
0
注册时间
2017-5-23
在线时间
70 小时
最后登录
2018-5-26
发表于 2018-1-24 14:21:55 | 显示全部楼层
谢谢奉献

回复

使用道具 举报

5熟悉之中
762/1000
排名
4058
昨日变化
29

0

主题

233

帖子

762

积分

Rank: 5Rank: 5

UID
228584
好友
0
蛮牛币
936
威望
0
注册时间
2017-6-25
在线时间
211 小时
最后登录
2018-5-25
发表于 2018-1-24 17:02:38 | 显示全部楼层
看看看看

回复

使用道具 举报

排名
15438
昨日变化
8

1

主题

13

帖子

87

积分

Rank: 2Rank: 2

UID
235215
好友
0
蛮牛币
111
威望
0
注册时间
2017-8-1
在线时间
34 小时
最后登录
2018-5-3
发表于 2018-1-24 17:40:53 | 显示全部楼层
膜拜大神,,,,,,,,,,,,,,,ing

回复

使用道具 举报

5熟悉之中
763/1000
排名
5744
昨日变化
1

0

主题

403

帖子

763

积分

Rank: 5Rank: 5

UID
146677
好友
9
蛮牛币
2571
威望
0
注册时间
2016-4-25
在线时间
152 小时
最后登录
2018-5-19
QQ
发表于 2018-1-24 18:14:02 | 显示全部楼层
很不错的东西,加油

回复 支持 反对

使用道具 举报

7日久生情
2408/5000
排名
420
昨日变化

0

主题

197

帖子

2408

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
85260
好友
0
蛮牛币
2216
威望
0
注册时间
2015-3-26
在线时间
751 小时
最后登录
2018-5-26
发表于 2018-1-25 08:29:05 | 显示全部楼层
very good   

回复 支持 反对

使用道具 举报

7日久生情
1586/5000
排名
1058
昨日变化
2

0

主题

194

帖子

1586

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
148134
好友
0
蛮牛币
2586
威望
0
注册时间
2016-5-10
在线时间
456 小时
最后登录
2018-5-11

迈向小康

QQ
发表于 2018-1-25 08:46:26 | 显示全部楼层
感谢分享

回复

使用道具 举报

4四处流浪
318/500
排名
7212
昨日变化

0

主题

107

帖子

318

积分

Rank: 4

UID
248343
好友
0
蛮牛币
597
威望
0
注册时间
2017-10-12
在线时间
59 小时
最后登录
2018-5-10
发表于 2018-1-25 09:43:03 | 显示全部楼层
v55v5v5v5v5v5v5
[发帖际遇]: 一个袋子砸在了 Hult 头上,Hult 赚了 1 蛮牛币. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

7日久生情
1747/5000
排名
2787
昨日变化
10

1

主题

883

帖子

1747

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
216583
好友
2
蛮牛币
2939
威望
0
注册时间
2017-4-7
在线时间
405 小时
最后登录
2018-5-25
发表于 2018-1-25 09:52:20 | 显示全部楼层
谢谢分享

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

关闭

站长推荐 上一条 /1 下一条

快速回复 返回顶部 返回列表