2010-11-16 2 views
1

Я строй XNA игры со следующей упрощенной структурой:Автоматического литья общего списка <Entity>

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 метод вызывается, когда детектор столкновения обнаруживает столкновение между лучом и зеркалом (поэтому объекты [i] являются зеркалом, а сущности [j] - светом и наоборот). Однако, поскольку список сущностей хранит объекты типа Entity, вместо этого вызывается метод Collide (Entity e1, Entity e2).

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

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

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

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

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

ответ

0

Вы можете использовать ключевое слово 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]. 
+0

Этот подход предполагает, что входные сущности упорядочены так же, как и параметры согласованного метода – houlgap

+0

@houlgap: Хорошая точка. Я действительно не принимал это во внимание. Для этого всегда можно было бы сочетать методы. 'Collide (Mirror m0, LightBeam lb0) и Collide (LightBeam lb0, Mirror m0)', где он просто указывает на другой. Подобно тому, как это делают точки OP. Основная проблема заключалась в том, что ОП искал подходящее ключевое слово для учета его ситуации, которая является 'dynamic';) –

+0

@myermian: Спасибо! Динамическое ключевое слово работает как шарм!:) @houlgap: Я уже подумал об этом и придумал ту же идею, что и myermian, добавленный к его примеру. Это не так важно для поддержания этого, поскольку столкновения могут существовать только между типами объектов. – Wytse

1

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

void Collide(Entity e1, Entity e2) 

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

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

+0

Это в основном то же самое решение, которое я заявил в моем вопросе. Он работает, но не поддерживается, когда вы добавляете, скажем, 25 других типов сущностей. С помощью решения myermian я должен добавить только два новых метода в класс CollisionResponder, если мне нужна новая логика столкновения для других типов Entity. Спасибо за попытку хотя :) – Wytse

+0

@Wytse Вы уверены в этом? Если у вас было 25 типов сущностей, вам не нужны 625 функций для логики столкновений? –

+0

Не все типы объектов будут иметь особые столкновения друг с другом. Так что многие функции не нужны. Опять же, не будет ли 625 функций более элегантными, чем огромный список if-утверждений? – Wytse

1

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

Казалось бы, самый простой ответ - отделить световые лучи и зеркала перед циклом сравнения, тогда у вас есть четко определенная связь для сравнения. Вы можете сделать это с помощью 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]); 
     } 
    } 
} 

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

+0

Это решение, о котором я не думал. На самом деле существует больше типов сущностей. Вот почему я предпочитаю решение для myermian. В противном случае мне не нужно было бы добавлять логику столкновений для любых новых коллизий в CollisionResponder, но мне также нужно было бы добавить новый (вложенный) цикл в CollisionDetector и отслеживать все типы Entity. – Wytse

+0

@Wytse: Это предполагает, что существуют типы сущностей, отличные от препятствий и световых лучей. Если мы ограничены таким образом, тогда вы только ищите столкновения с лучом -> препятствия, а код столкновения не должен меняться, независимо от того, сколько препятствий вы добавляете. Сплав световых лучей и зеркал вместе, и я делаю предположения о типе игры здесь, похоже, группирует разнородные объекты, которые, хотя может показаться здесь уместными, могут привести к архитектуре, которая вызовет у вас больше проблем. – Lazarus