【问题标题】:return class instead of interface from collection holding interfaces从集合持有接口返回类而不是接口
【发布时间】:2019-05-10 19:22:30
【问题描述】:

我想创建一个小型实体组件系统示例并创建一些组件,例如

internal struct Position : IComponent
{
    public int X { get; set; }
    public int Y { get; set; }
}

internal struct MovementSpeed : IComponent
{
    public int Value { get; set; }
}

每个组件都实现了一个当前为空的接口IComponent。当系统循环遍历实体时,我想快速找到相关组件。

我想过创建一个字典,将组件类型作为键,将当前实体的组件作为值。

我从public Dictionary<Type, IComponent> Components { get; }开始

我可以使用myEntity.Components.Add(typeof(Movement), new Movement() as IComponent);添加组件

但是我怎样才能返回一个组件呢?我创建了一个运动系统示例

internal class Movement : ISystem
{
    public void Update()
    {
        foreach (Entity entity in EntityPool.activeEntities.Values) // Loop through all entities
        {
            Dictionary<Type, IComponent> components = entity.Components;

            if (components.TryGetValue(typeof(Position), out Position positionComponent))
            {
                if (components.TryGetValue(typeof(MovementSpeed), out MovementSpeed movementSpeedComponent))
                {
                    // TEST: move (1 * movementspeed) units
                    positionComponent.X += movementSpeedComponent.Value;
                    positionComponent.Y += movementSpeedComponent.Value;
                }
            }
        }
    }
}

if (components.TryGetValue(typeof(Position), out Position positionComponent)) 崩溃是因为字典的值没有返回期望类型本身的组件,而是返回了接口。

我怎样才能让它工作?

(是的,我知道我可以使用 ECS 框架,但出于学习目的我想自己做)

【问题讨论】:

  • 您需要使用反射来查找并获取指定成员的值。更好的方法是将成员放在您的界面中。我不确定我明白你在做什么,但这就是使用接口的目的。
  • 为什么嵌套了ifs?
  • @JonathanWood 你介意再解释一下吗?
  • @Fabjan 因为这个系统应该只更新附有位置和移动速度组件的实体
  • 从字典中以IComponent 检索它,然后将其转换为Position。因为你只是拉Positions,所以演员应该总是成功(尽管我会使用isas来确保和防止InvalidCastException

标签: c# entity-component-system


【解决方案1】:

如果插入 IComponent 类型的项,则只能将该项作为 IComponent 检索。 如果您向 Dictionary 询问特定类型,则可以直接转换为该类型。

        foreach (Entity entity in EntityPool) // Loop through all entities
        {
            Dictionary<Type, IComponent> components = entity.Components;
            if (components.TryGetValue(typeof(Position), out IComponent positionComponent))
            {
                Position position = (Position)positionComponent;
                if (components.TryGetValue(typeof(MovementSpeed), out IComponent movementSpeedComponent))
                {
                    MovementSpeed speed = (MovementSpeed)movementSpeedComponent;
                    // TEST: move (1 * movementspeed) units
                    position.X += speed.Value;
                    position.Y += speed.Value;
                }
            }
        }

使用 linq,您有一种非常有效的方式来对列表进行操作。也许这对您来说是更好的方法。这是一个例子:

    public void Update2()
    {
        List<IComponent> list = new List<IComponent>();
        list.OfType<Position>().ToList().ForEach(p =>
        {
            var speed = list.OfType<MovementSpeed?>().FirstOrDefault();
            if (speed.HasValue)
            {
                p.X = speed.Value.Value;
                p.Y = speed.Value.Value;
            }
        });
    }

【讨论】:

    【解决方案2】:

    简短的回答:你不能。如果字典的类型为Dictionary&lt;Type, IComponent&gt;,那么它将只返回IComponent

    但是你可以为此创建一个扩展方法:

    public static class DictionaryExtensions
    {
        public static TComponent GetValueOrNull<TComponent>(this Dictionary<Type, IComponent> dict) where TComponent : IComponent
        {
            dict.TryGetValue(typeof(TComponent), out IComponent component);
            return component as TComponent;
        } 
    }
    

    并使用它:

    internal class Movement : ISystem
    {
        public void Update()
        {
            foreach (Entity entity in EntityPool.activeEntities.Values) // Loop through all entities
            {
                var posComponent = entity.Components.GetValueOrNull<Position>();
    
                if (posComponent != null)
                {
                    // some code
                }
            }
        }
    }
    

    【讨论】:

    • 这不是一个糟糕的答案,但请注意 TryGet.... 方法有一个更通用的约定。这些往往返回一个布尔值(如果它得到它则为真,如果不存在则为假),并将实际值作为输出参数返回。 (参见 Dictionary 示例,但即使是 int.TryParse() 之类的各种内容也基本相同)。
    • 是的,我已将名称更改为 GetValueOrNull
    • 我知道很难达到这个结果,但他真的没有机会吗?这种方法怎么样? stackoverflow.com/questions/972636/… 特别是动态关键字。该解决方案的缺点是运行时性能缓慢,并且对对象的操作存在很大限制。
    猜你喜欢
    • 2011-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-30
    • 1970-01-01
    • 2022-01-12
    • 1970-01-01
    • 2016-11-17
    相关资源
    最近更新 更多