游戏碰撞处理

9

这里给出的代码仅作为示例,不是我代码库中的复制粘贴

我正在使用Boost和SDL编写一个基本的跨平台贪吃蛇游戏,我想知道实现碰撞处理(而不是碰撞检测)的最佳方法是什么。到目前为止,我一直在使用单调度思路,但代码非常混乱,像这样:

void Snake::CollisionHandler(const WorldObject& obj)
{
    // collided with self
    if(obj.GetObjectType() == snake)
        Die();
    ...
    ...
}

此外,我有一个“全局”碰撞处理程序,用于处理与每个对象无关的碰撞相关事项,例如:

void GameWorld::CollisionHandler(WorldObject& obj1, WorldObject& obj2)
{
    if(obj1.GetObjectType() == snake && obj2.GetObjectType() == snake)
        PlayDeathSound();
    ...
    ...
}

为了避免碰撞时声音被重复播放。

I've also considered double-dispatch, like so:


void Snake::CollisionHandler(WorldObject& obj) const
{
    // invoke obj's collision handler with a const Snake& parameter
    obj.CollisionHandler(*this);
}
// collided with self
void Snake::CollisionHandler(const Snake& obj)
{
    Die();
}

这也包括一个类似于上面的全局碰撞处理程序。
然后还有一种方法,使用全局碰撞处理程序(它是所有游戏对象的友元函数),如下所示:

void GameWorld::CollisionHandler(WorldObject& obj1, WorldObject& obj2)
{
    // snake collided with self
    if(obj1.GetObjectType() == snake && obj2.GetObjectType() == snake)
    {
        obj1.Die();
        obj2.Die();
        PlayDeathSound();
    }
    ...
    ...
}

有没有我错过的策略?哪一个最好?它们似乎都涉及到一些丑陋的代码,而单分派和双分派涉及到多态性,我个人尽量避免使用。


在你的最后一个示例中,注释写着“蛇与自身相撞”,然后你调用了 Die() 两次。我怀疑那会是一件坏事。另外,不需要“friend”任何东西,只需将所需的函数设置为公共函数即可。 - Mark Storer
PPS:为了避免重复代码,我倾向于只在“我与Foo碰撞”函数中放置该对象的效果。例如:当飞船与子弹相撞时,它会受到一些伤害。当子弹与飞船相撞时,它就消失了。 - Mark Storer
2个回答

5

碰撞处理是一个C++/Java式的对象导向(使用单一派发)不够灵活的领域。碰撞的结果取决于两个相撞对象的类型,因此需要多重派发来处理它。(维基百科multiple dispatch文章上的激励示例正是碰撞处理!)

我认为一个全局碰撞处理器,然后派发到各个对象方法是解决C++中这种不足的最佳方法。


这基本上就是我现在拥有的。实际上,在Effective C++中,有一些取巧的方法可以在C++中实现双重分派。其中一个方法显示在我的原始帖子中(第三个代码块)。 - bfops
是的,但我认为在这种情况下这是一种较弱的解决方法。如果您在类A中编写了与类B碰撞的代码,并在类B中编写了与类A碰撞的不同代码,则可能会无意中使它们不一致。(也许在您的游戏中只有蛇可以与物体碰撞,因此在实践中不会发生这种情况,但在更一般的情况下,这将是值得考虑的风险。) - Gareth Rees
我不明白你的意思;每个东西都会处理与其他所有东西的碰撞。A只处理与B碰撞时自己的那一端,而B只处理与A碰撞时自己的那一端。我不确定我理解你所说的“不一致”。 - bfops
如果你犯了一个错误(例如,你决定改变A/B碰撞的某个方面,但只在A类中进行了更改而没有在B类中进行更改),那么这将是不一致的。显然,如果你从不犯错误,那么代码组织方式并不重要,但对于像我这样容易出错的凡人来说,将相关代码放在一起会有所帮助。 - Gareth Rees

1

在我看来,让游戏类完全处理碰撞和其他全局事件的最后一种方法是最明智和可能是最“正确”的方式。
例如,以象棋程序为例。很明显,游戏类应该处理其中一个玩家获胜或失败的情况,而不是玩家类。
这也更加实用。想象一下,如果您想要添加更多类(例如另一种类型的蛇),您可以让游戏类处理它,而无需重复代码或覆盖CollisionHandler,从而减少了大量的工作量。

然而,如果您的唯一目的是创建一个简单的贪吃蛇游戏,那么您选择如何实现它可能并不那么重要。


如果我添加另一种蛇类,使用双重分派方法会更简单吧?不需要代码重复,而且它可以很好地分离成模块。 - bfops
1
哎呀,对于一行通用的CollisionHandler(WorldObject&)函数存在轻微的代码重复。 - bfops
1
如果你要继承WorldObject,你将需要复制代码。另外,作为一个经验法则,当你不必使用函数重载时最好不要使用它。 - stnr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接