3

对不起标题,我想不出更好的方式来描述这个问题。基本上,我正在尝试在游戏中实现碰撞系统。我希望能够注册一个“碰撞处理程序”来处理可以转换为特定类型的两个对象(以任意顺序给出)的任何碰撞。因此,如果Player : Ship : EntityLaser : Particle : Entity,以及 和 的处理程序注册 ,(Ship, Particle)(Laser, Entity)不是 的冲突(Laser, Player),则应通知两个处理程序,并以正确的顺序提供参数,并且 的冲突(Laser, Laser)应仅通知第二个处理程序。

一个代码片段说了一千个单词,所以这就是我现在正在做的事情(naieve 方法):

    public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
        where T1 : Entity
        where T2 : Entity
    {
        Type t1 = typeof(T1);
        Type t2 = typeof(T2);
        Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
        _onCollisionInternal += delegate(Entity obj1, Entity obj2)
        {
            if (t1.IsAssignableFrom(obj1.GetType()) && t2.IsAssignableFrom(obj2.GetType()))
                obs.OnNext(new Collision<T1, T2>((T1) obj1, (T2) obj2));
            else if (t1.IsAssignableFrom(obj2.GetType()) && t2.IsAssignableFrom(obj1.GetType()))
                obs.OnNext(new Collision<T1, T2>((T1) obj2, (T2) obj1));
        };
        return obs;
    }

但是,这种方法很慢(可测量;我在实施后损失了大约 2 FPS),所以我正在寻找一种方法来减少几个周期/分配。

我想到了(例如,花了一个小时实现然后因为自己是个白痴而将头撞到墙上)一种方法,该方法根据它们的哈希码将类型按顺序排列,然后将它们放入字典中,每个条目都是该类型对的处理程序的链接列表,带有布尔值指示处理程序是否希望反转参数的顺序。不幸的是,这不适用于派生类型,因为如果传入派生类型,它不会通知基类型的订阅者。谁能想到比检查每个类型对(两次)以查看它是否匹配更好的方法?

谢谢,罗伯特

4

3 回答 3

3

所有这些反思都不会很快。注意反射是如何在每次碰撞时发生的。那就是问题所在。

一些想法。

想法一:访客模式。

在 OO 语言中实现相当快速的双虚拟调度的标准方法是构建访问者模式的实现。

你能实现一个访问者模式,让访问者中的每个接受者都维护一个“在这种情况下要做的事情”列表吗?然后,您的 adder 方法包​​括确定编写新“要做的事情”的正确位置,然后将委托添加到该事物。当冲突发生时,您启动访问者进行双重调度,找到要做的事情的委托,并调用它。

您需要的不仅仅是双重调度吗?高效的多虚拟调度是可行的,但并不完全简单。

想法二:动态调度

C# 4 具有动态调度。它的工作方式是我们在第一次遇到调用站点时使用反射来分析调用站点。然后我们动态生成新的 IL 来执行调用,并缓存 IL。在第二次调用中,我们反思参数以查看它们是否与之前的类型完全相同。如果是,我们就重新使用现有的 IL 并调用它。如果不是,我们再次进行分析。如果参数通常只有几种类型,那么缓存很快就会开始只有命中而不是未命中,并且考虑到所有因素,性能实际上都非常好。肯定比每次都进行大量反射要快。我们每次做的唯一反思就是分析参数的运行时类型。

想法三:实现你自己的动态调度

DLR 所做的事情并没有什么神奇之处。它进行一次分析,吐出一些 IL 并缓存结果。我怀疑您的痛苦正在发生,因为您每次都在重新进行分析。

于 2010-03-03T00:25:56.773 回答
1

如果每个Entity都有一个标识其实体类型的属性,而您只是获取该属性的值,那不是更快吗?

然后,如果您对处理程序中实体的顺序有一个约定,那么 Laser 将始终位于播放器之前,等等。

您的实体类型可以是和枚举,枚举顺序可以是您的处理程序对象顺序。

public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
    where T1 : Entity
    where T2 : Entity
{
    EntityTypeEnum t1 = T1.EntityType;
    EntityTypeEnum t2 = T2.EntityType;

    Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
    _onCollisionInternal += delegate(Entity obj1, Entity obj2)
    {
       if (t1 < t2)
           obs.OnNext(new Collision<T1, T2>((T1) obj1, (T2) obj2));
       else
           obs.OnNext(new Collision<T1, T2>((T1) obj2, (T2) obj1));
    };
    return obs;
}
于 2010-03-03T00:18:59.727 回答
0

我假设你有两组碰撞:激光/播放器和激光/激光。如果您愿意使 IObservable< Collision< T1, T2 >> > 仅匹配这两种情况,那么您将能够将委托减少到仅进行一次检查,并将所有内容强类型化以进行比较。

_onCollisionInternal += delegate(T1 obj1, T2 obj2) {
  obs.OnNext(new Collision<T1, T2>( obj1, obj2));
};


public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
        where T1 : Entity
        where T2 : Entity
    {
        Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
        _onCollisionInternal += delegate(T1 obj1, T2 obj2) {
           obs.OnNext(new Collision<T1, T2>( obj1, obj2));
        };
        return obs;
    }

Observable<Collision<Laser, Player>> lp = onCollisionsOf<Laser, Player>();
Observable<Collision<Laser, Laser>> ll = onCollisionsOf<Laser, Laser>();
于 2010-03-03T00:27:35.400 回答