ECS Component简介

641 阅读3分钟

Component是ECS中最简单的部分,它表示的是Entity上的数据部分,类似于Unity中GameObject上的Component,一个Component可以有许多属性,也可以是空的等等,下面一一来看。

空的Component

[Game]
public class TestComponent : IComponent
{
}

任何自定义的Component(可以是class,struct,interface)都需要实现IComponent,创建好后,点击Tools/Jenny/Generate,生成Generate Code,代码如下:

public partial class GameEntity {

    static readonly TestComponent testComponent = new TestComponent();

    public bool isTest {
        get { return HasComponent(GameComponentsLookup.Test); }
        set {
            if (value != isTest) {
                var index = GameComponentsLookup.Test;
                if (value) {
                    var componentPool = GetComponentPool(index);
                    var component = componentPool.Count > 0
                            ? componentPool.Pop()
                            : testComponent;

                    AddComponent(index, component);
                } else {
                    RemoveComponent(index);
                }
            }
        }
    }
}
  • 所以我们可以通过entity.isTest查看该entity是否还有该Component,可以通过entity.isTest = true为entity添加该Component,通过entity.isTest = false删除组件。
  • 我们可以为空的Component添加前缀(FlagPrefix),如下所示:
[Game,FlagPrefix("My")]
public class TestComponent : IComponent
{
}

再次生成一次Generate Code,如下:

public partial class GameEntity {

    static readonly TestComponent testComponent = new TestComponent();

    public bool myTest {
        get { return HasComponent(GameComponentsLookup.Test); }
        set {
            if (value != myTest) {
                var index = GameComponentsLookup.Test;
                if (value) {
                    var componentPool = GetComponentPool(index);
                    var component = componentPool.Count > 0
                            ? componentPool.Pop()
                            : testComponent;

                    AddComponent(index, component);
                } else {
                    RemoveComponent(index);
                }
            }
        }
    }
}

只是isTest变为了myTest,my刚好是我们自定义的前缀,所以这样可以帮助我们更好的理解这个Component的含义,而使用方法却没有任何区别。

  • 若FlagPrefix修饰的Component不是空的,则FlagPrefix不会生效。
  • Component上的Game是一个Contexts标记,也就是说任何包含了该Component的entity 都可以在GameContext中找到,而我们可以通过如下方式,添加完成点击Generate,其中game对应于GameContext,input对应于InputContext。

只包含值类型的数据

[Game]
public class PositionComponent : IComponent
{
    public Vector3 pos;
}

生成代码如下:

public partial class GameEntity {

    public PositionComponent position { get { return (PositionComponent)GetComponent(GameComponentsLookup.Position); } }
    public bool hasPosition { get { return HasComponent(GameComponentsLookup.Position); } }

    public void AddPosition(UnityEngine.Vector3 newPos) {
        var index = GameComponentsLookup.Position;
        var component = (PositionComponent)CreateComponent(index, typeof(PositionComponent));
        component.pos = newPos;
        AddComponent(index, component);
    }

    public void ReplacePosition(UnityEngine.Vector3 newPos) {
        var index = GameComponentsLookup.Position;
        var component = (PositionComponent)CreateComponent(index, typeof(PositionComponent));
        component.pos = newPos;
        ReplaceComponent(index, component);
    }

    public void RemovePosition() {
        RemoveComponent(GameComponentsLookup.Position);
    }
}
  • 可以通过entity.position获取Component,
  • 通过entity.hasPosition查看是否包含该组件,
  • 通过entity.AddPosition(1,2)为entity添加Component,
  • 通过entity.RemovePosition()删除组件。
    最重要的是每个entity只可以含有一个同类型的Component,使用ReplaceComponent方法可以避免由于创建Component而导致的性能消耗

引用Component

[Game]
public class ViewComponent : IComponent
{
    public GameObject obj;
}

生成代码如下:

public partial class GameEntity {

    public ViewComponent view { get { return (ViewComponent)GetComponent(GameComponentsLookup.View); } }
    public bool hasView { get { return HasComponent(GameComponentsLookup.View); } }

    public void AddView(UnityEngine.GameObject newObj) {
        var index = GameComponentsLookup.View;
        var component = (ViewComponent)CreateComponent(index, typeof(ViewComponent));
        component.obj = newObj;
        AddComponent(index, component);
    }

    public void ReplaceView(UnityEngine.GameObject newObj) {
        var index = GameComponentsLookup.View;
        var component = (ViewComponent)CreateComponent(index, typeof(ViewComponent));
        component.obj = newObj;
        ReplaceComponent(index, component);
    }

    public void RemoveView() {
        RemoveComponent(GameComponentsLookup.View);
    }
}

与上面的PositionComponent生成代码类似,只不过其属性变为了引用类型的对象,而且其引用的对象可能很复杂。

一些常用的Attribute(修改Component或者其属性)

Unique

表示只有一个entity可以拥有此Component,若超过一个便会报错。例如:

[Game,Unique]
public class TestComponent : IComponent
{
    public Vector3 pos;
}

其生成代码如下:

public partial class GameContext {

    public GameEntity testEntity { get { return GetGroup(GameMatcher.Test).GetSingleEntity(); } }
    public TestComponent test { get { return testEntity.test; } }
    public bool hasTest { get { return testEntity != null; } }

    public GameEntity SetTest(UnityEngine.Vector3 newPos) {
        if (hasTest) {
            throw new Entitas.EntitasException("Could not set Test!\n" + this + " already has an entity with TestComponent!",
                "You should check if the context already has a testEntity before setting it or use context.ReplaceTest().");
        }
        var entity = CreateEntity();
        entity.AddTest(newPos);
        return entity;
    }

    public void ReplaceTest(UnityEngine.Vector3 newPos) {
        var entity = testEntity;
        if (entity == null) {
            entity = SetTest(newPos);
        } else {
            entity.ReplaceTest(newPos);
        }
    }

    public void RemoveTest() {
        testEntity.Destroy();
    }
}

public partial class GameEntity {

    public TestComponent test { get { return (TestComponent)GetComponent(GameComponentsLookup.Test); } }
    public bool hasTest { get { return HasComponent(GameComponentsLookup.Test); } }

    public void AddTest(UnityEngine.Vector3 newPos) {
        var index = GameComponentsLookup.Test;
        var component = (TestComponent)CreateComponent(index, typeof(TestComponent));
        component.pos = newPos;
        AddComponent(index, component);
    }

    public void ReplaceTest(UnityEngine.Vector3 newPos) {
        var index = GameComponentsLookup.Test;
        var component = (TestComponent)CreateComponent(index, typeof(TestComponent));
        component.pos = newPos;
        ReplaceComponent(index, component);
    }

    public void RemoveTest() {
        RemoveComponent(GameComponentsLookup.Test);
    }
}

通过生成代码可以看到,与其他Component不同的是,我们可以直接通过GameContext访问拥有这个Component的entity和操作这个Component.例如:

GameContext gameContext = Contexts.sharedInstance.game;
if (!gameContext.hasTest)
{
    gameContext.SetTest(Vector3.one); //添加
}
gameContext.ReplaceTest(Vector3.down);//替换
gameContext.RemoveTest();//删除

等价于

GameContext gameContext = Contexts.sharedInstance.game;
var entity = gameContext.CreateEntity();
entity.AddTest(Vector3.one);//添加
entity.ReplaceTest(Vector3.down);//替换
entity.RemoveTest();//删除

PrimaryEntityIndex

修饰Component的属性,所修饰的属性的值不能有重复的,可以用于修饰id例如:

[Game]
public class TestComponent : IComponent
{
    [PrimaryEntityIndex]
    public Vector3 pos; //表示拥有这个组件的pos不可以是重复的
}

可以通过GetEntityWithTest快速获得有某个pos的entity。

GameContext gameContext = Contexts.sharedInstance.game;
gameContext.GetEntityWithTest(Vector3.back);

EntityIndex

修饰Component的属性,类似于unity里的tag,例如:

[Game]
public class TestComponent : IComponent
{
    [EntityIndex]
    public Vector3 pos;
}

可以利用GetEntitiesWithTest获得拥有该Component,并且Component的值为传入的值的所有entity

GameContext gameContext = Contexts.sharedInstance.game;
gameContext.GetEntitiesWithTest(Vector3.back);

还有一些不常用的可以查看这里