开发者

Automatic casting of generic List<Entity>

I am building an XNA game with the following simplified structure:

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 collisio开发者_运维知识库n 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");
    }
}

What I am trying to achieve is that the Collide(Lightbeam beam, Mirror mirror) method is invoked when the collision detector detects a collision between a beam and mirror (so entities[i] is a Mirror and entities[j] is a Lightbeam and vice versa). However, since the entities list stores objects of the type Entity, the Collide(Entity e1, Entity e2) method is invoked instead.

What i tried to overcome this problem:

  • If a collision is detected, check which types of entities are colliding and invoke the corresponding method. This is quite an ugly solution, since the method should be changed each time a new collision type is added.
  • Use generics:

    Collide(Entity e1, Entity e2) where T : Lightbeam where U : Mirror

    However, this solution doesn't make the compiler happy.

  • I found links to the Builder Pattern and Factory Pattern, but it doesn't seem like that fixes my problem.

It seems to me that there's a simple solution to this, but I couldn't find any on the internet (using combinations of keywords like the following: generics, subclass, method, overloading, list, automatic casting).


CollisionResponder should be an interface with a single method

void Collide(Entity e1, Entity e2)

Keep a list of these in CollisionDetector, and call each one in turn upon a collision. Within each implementation you can use the "is" operator to check the runtime types and see if you want to do any logic.

The compiler cannot know what the type of the arguments to the functions are at compile time- only a run time check can do what you need.


Your main problem here is that you can't know ahead of time whether you are comparing a mirror with a lightbeam, a lightbeam with a mirror, a mirror with a mirror or a lightbeam with a lightbeam. You also suggest that there might be more entity types to be added so that set of possible comparisons begins to swell and as you rightly indicate, all the elements are being addressed as the base Entity anyway.

It would seem that the simplest answer is to separate the lightbeams and mirrors ahead of the comparison loop, then you have a clearly defined relationship for the comparison. You could do this using LINQ but I understand that from a predictable performance aspect this might not be desirable.

If you can't actually hold them separately in your game data structures, you could build two lists thus:

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]);
}

You loop then becomes:

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]);
        }
    }
}

The code will still need updating for each new entity type that's added but your comparisons would be clear. I'd suspect that your split between entity types is likely to be lightbeams and obstacles so I'd still only expect to see two collections as I'd also expect that obstacles couldn't occupy the same bounding box and that would be restricted in the UI. With that in mind, you'd never need to check collision between obstacles beyond the UI, only collision between lightbeam and obstacle. It makes sense (to me anyway) to treat those as layers within the data structures and to maintain them separately.


You can use the keyword dynamic.

Here is an example:

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"; }
    }
}

OUTPUT:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜