Автоматическое приведение универсального списка <Entity> - PullRequest
1 голос
/ 16 ноября 2010

Я создаю игру XNA со следующей упрощенной структурой:

class Entity
{
    Vector3 Speed;
    Matrix World;
}

class Mirror : Entity {}

class Lightbeam : Entity {}

class CollisionDetector
{
    /*
    ...
    */

    public override void Update(GameTime gameTime)
    {
        List<Entity> entities = entityManager.level.CurrentSection.Entities;

        for (int i = 0; i < entities.Count - 1; i++)
        {
            for (int j = i + 1; j < entities.Count; j++)
            {
                if(entities[i].boundingBox.Intersects(entities[j].boundingBox)) 
                {
                    collisionResponder.Collide(entities[i], entities[j]);
                }
            }
        }
        base.Update(gameTime);
    }
}

class CollisionResponder
{
    public void Collide(Entity e1, Entity e2)
    {
        Console.WriteLine("Normal entity collision response");
    }

    public void Collide(Mirror mirror, Lightbeam beam)
    {
        Collide(beam, mirror);
    }

    public void Collide(Lightbeam beam, Mirror mirror)
    {
        Console.WriteLine("Specific lightbeam with mirror collision response");
    }
}

Я пытаюсь добиться того, чтобы метод Collide (Lightbeam beam, Mirror mirror) вызывался, когда детектор столкновений обнаруживаетстолкновение между лучом и зеркалом (таким образом, сущности [i] - это зеркало, а сущности [j] - это луч света и наоборот).Однако поскольку в списке сущностей хранятся объекты типа Entity, вместо этого вызывается метод Collide (Entity e1, Entity e2).

Что я пытался преодолеть эту проблему:

  • Если обнаружена коллизия, проверьте, какие типы сущностей сталкиваются, и вызовите соответствующий метод.Это довольно уродливое решение, так как метод должен меняться каждый раз, когда добавляется новый тип столкновения.
  • Использовать обобщенные значения:
    Collide (Entity e1, Entity e2) где T: Lightbeam гдеU: Mirror

    Однако это решение не делает компилятор счастливым.

  • Я нашел ссылки на Pattern Builder и Factory Pattern, но, похоже, это не исправляетмоя проблема.

Мне кажется, что есть простое решение для этого, но я не смог найти ничего в интернете (используя комбинации ключевых слов, таких как: дженерики, подкласс, метод, перегрузка, роспись, автоматическое литье).

Ответы [ 3 ]

1 голос
/ 16 ноября 2010

Ваша главная проблема здесь в том, что вы не можете знать заранее, сравниваете ли вы зеркало с световым лучом, световой луч с зеркалом, зеркало с зеркалом или световой луч с световым лучом.Вы также предполагаете, что может быть добавлено больше типов сущностей, так что набор возможных сравнений начинает набухать, и, как вы правильно указали, все элементы в любом случае рассматриваются как базовая сущность.

Может показаться, чтоСамый простой ответ - разделить световые лучи и зеркала перед циклом сравнения, тогда у вас есть четко определенные отношения для сравнения.Вы могли бы сделать это, используя LINQ, но я понимаю, что с точки зрения предсказуемой производительности это может быть нежелательно.

Если вы не можете фактически хранить их отдельно в своих структурах игровых данных, вы можете построить два списка таким образом:

List<Entity> entities = entityManager.level.CurrentSection.Entities;
List<Mirror> mirrors = new List<Mirror>();
List<Lightbeam> lightbeams = new List<Lightbeam>();
for (int i = 0; i < entities.Count - 1; i++)
{
    if (entities[i] is Mirror)
        mirrors.Add((Mirror)entities[i]);
    if (entities[i] is Lightbeam)
        lightbeams.Add((Lightbeam)entities[i]);
}

После этого цикл становится следующим:

for (int i = 0; i < mirrors.Count - 1; i++)
{
    for (int j = 0; j < lightbeams.Count; j++)
    {
        if(mirrors[i].boundingBox.Intersects(lightbeams[j].boundingBox)) 
        {
            collisionResponder.Collide(mirrors[i], lightbeams[j]);
        }
    }
}

Код по-прежнему будет нуждаться в обновлении для каждого добавляемого нового типа объекта, но ваши сравнения будут понятны.Я подозреваю, что ваше разделение между типами сущностей, вероятно, будет световыми лучами и препятствиями, поэтому я все еще ожидаю увидеть только две коллекции, так как я также ожидаю, что препятствия не могут занимать одну и ту же ограничивающую рамку, и это будетпользовательский интерфейс.Имея это в виду, вам никогда не придется проверять столкновение между препятствиями за пределами пользовательского интерфейса, только столкновение между световым лучом и препятствием.В любом случае, для меня имеет смысл рассматривать их как слои в структурах данных и поддерживать их отдельно.

1 голос
/ 16 ноября 2010

CollisionResponder должен быть интерфейсом с одним методом.

void Collide(Entity e1, Entity e2)

. Хранить их список в CollisionDetector и вызывать каждый из них по очереди при столкновении.В каждой реализации вы можете использовать оператор «is» для проверки типов среды выполнения и посмотреть, хотите ли вы выполнить какую-либо логику.

Компилятор не может знать, какой тип аргументов у функций во время компиляции.только проверка во время выполнения может сделать то, что вам нужно.

0 голосов
/ 16 ноября 2010

Вы можете использовать ключевое слово dynamic.

Вот пример:

public class Program
{
    static void Main(string[] args)
    {
        List<IEntity> entities = new List<IEntity>();
        entities.Add(new Mirror(1));
        entities.Add(new Mirror(2));
        entities.Add(new LightBeam(1));
        entities.Add(new LightBeam(2));

        //I also fixed your for-loops, you don't need to do entities.Count - 1
        for (int i = 0; i < entities.Count; i++)
        {
            for (int j = i + 1; j < entities.Count; j++)
                Collide((dynamic)entities[i], (dynamic)entities[j]);
        }

        Console.ReadLine();
    }

    public static void Collide(Entity e0, Entity e1)
    {
        Console.WriteLine("Collision: IEntity {0}[{1}] and IEntity {2}[{3}].", e0.Name, e0.ID, e1.Name, e1.ID);
    }

    public static void Collide(LightBeam lb0, Mirror m0)
    {
        Collide(m0, lb0);
    }
    public static void Collide(Mirror m0, LightBeam lb0)
    {
        Console.WriteLine("Special Collision: Mirror {0}[{1}] and LightBeam {2}[{3}].", m0.Name, m0.ID, lb0.Name, lb0.ID);
    }
}

//Interfaces are our friends :)
public interface IEntity
{
    String Name { get; }
    Int32 ID { get; }
}

public abstract class Entity : IEntity
{
    protected Entity(Int32 id = 0)
    {
        ID = id;
    }

    public Int32 ID { get; private set; }
    public abstract String Name { get; }
}

public class Mirror : Entity
{
    public Mirror(Int32 id = 0)
        : base(id)
    {
    }

    public override String Name
    {
        get { return "Mirror"; }
    }
}

public class LightBeam : Entity
{
    public LightBeam(Int32 id = 0)
        : base(id)
    {
    }

    public override String Name
    {
        get { return "LightBeam"; }
    }
}

ВЫХОД:

Collision: IEntity Mirror[1] and IEntity Mirror[2].
Special Collission: Mirror Mirror[1] and LightBeam LightBeam[1].
Special Collission: Mirror Mirror[1] and LightBeam LightBeam[2].
Special Collission: Mirror Mirror[2] and LightBeam LightBeam[1].
Special Collission: Mirror Mirror[2] and LightBeam LightBeam[2].
Collision: IEntity LightBeam[1] and IEntity LightBeam[2].
...