【问题标题】:Trying to use templates to double dispatch physics collision尝试使用模板双重调度物理碰撞
【发布时间】:2011-04-19 05:32:23
【问题描述】:

我想让编译器为物理碰撞系统建立函数连接。我有测试碰撞功能:

template <typename T, typename U>
inline void Collision(T& t, U& u)
{
    if(u.CheckCollision(t.GetCollider()))
    {
        u.HitBy(t);
        t.Hit(u);
    }
}

然后我使用一个碰撞器对象来保存碰撞对象。

template <typename T>
class Collidable
{
    T m_Collider;
    VictimEffect m_HitBy;
    MenaceEffect m_Hit;
 public:
    void SetCollider(const T& t) { m_Collider = t; }
    void SetVictim(VictimEffect effect) { m_HitBy = effect; }
    void SetMenace(MenaceEffect effect) { m_Hit = effect; }

    T& GetCollider()
    {
        return m_Collider;
    }
    template <typename V>
    void HitBy(V& menace)
    {
        m_HitBy.HitBy(menace.GetCollider());
    }
    template <typename V>
    void Hit(V& victim)
    {
        m_Hit.Hit(victim.GetCollider());
    }
    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

我为效果函数添加了一个间接级别

class VictimEffect
{
public:
    template<typename C>
    void HitBy(C&)
    {;}
};

class MenaceEffect
{
public:
    template<typename C>
    void Hit(C&)
    {;}
};

我最终想要的是一种在具有正确函数签名但不需要不会命中的类型的任何对象中调用函数的方法。

class Wall;
class Ball : public MenaceEffect
{
public:
    void Hit(Wall& wall);
    void Hit(Ball& ball);
};

class Wall : public VictimEffect
{
public:
    void HitBy(Ball& ball);
};

请注意,我不想让墙撞墙的功能。我只想为应该发生的交互编写或提供函数。我知道这不起作用,因为 VictimEffect 没有“看到”派生函数。

我想分派而不必在任何使用它们的地方指定模板参数。我想在可碰撞之外构建我的可碰撞效果,并将其一般地赋予可碰撞对象。我不想为每个可以碰撞的事物集合使用不同的可碰撞对象。基本上我想要

vector<collidable> movingThings;
vector<collidable> staticThings;
// get iterators ...
Collide(Moving, Static);

我在这里需要更多的间接层,但我就是看不到它。我知道这是可以做到的,因为所有信息在编译时都可用。不需要动态多态解决方案。

非常感谢任何帮助!

更新 我想做的是建立自己的现代(如安德烈的书)游戏库。到目前为止,我已经解决了大部分关系,但这一个让我难过。这不是时间关键问题,而是结果关键问题。我从事游戏工作很多年了,我不喜欢我从事过的任何游戏引擎。这意味着易于使用且易于扩展。现在控制台编译器已经变得不那么糟糕了,它可以在任何平台上运行。

这是我想要写的一个简单的例子。

int main()
{
    // System must come first
    System system(640, 480, false);
    Renderer renderer;
    Mouse mouse;
    Keyboard keys;

    // Make the ball
    Bounce ball;
    Sprite ballSprite("02.bmp");
    CollisionBox ballPhysics;
    BallCommand ballBehavior;

    ball.SpriteMover = boost::bind(&Sprite::Observer<Pos2D>, &ballSprite, _1);
    ball.PhysicsMover = boost::bind(&CollisionBox::Move, &ballPhysics, _1);

    // Make the bounds
    Boundary bounds;

    // Set up Physics
    Collidable<Boundary> boundC;
    boundC.SetCollider(bounds);
    boundC.SetVictim(ballBehavior);

    Collidable<CollisionBox> ballC;
    ballC.SetCollider(ballPhysics);

    PretendPhysics physics;
    physics.SetBall(ballC);
    physics.SetBounds(boundC);

    while(!mouse.LeftClick() && !keys.AnyKey())
    {
        ball.MoveRel(ballBehavior.Delta());
        physics.Update();

        renderer.Clear();
        renderer.Render(ballSprite);
        renderer.SwapBuffers();
    }
}

所有的物理都是在制品,不是我希望它结束​​的地方,但它正在到达那里。 System object 初始化 Windows 和平台特定的渲染器。在这种情况下,它是 openGL。尽管它被编译为控制台应用程序,但它会创建所有 Windows 组件。我不想让人们处理 main() 与 winmain()。除了物理,没有继承。在我将场景导入系统后,三个渲染调用将被替换为一个。我计划使用我不久前写的东西,结合 Gary Powell 和 Martin Weiser 的 View 模板库。

我知道我只是看的不对。访客可以帮助改变我的方法。

这就是我想要做的。

更新 2 好的。到目前为止,我从 cmets 和答案中得到了一些启发,这就是我去的地方,但我还没有完成。

template <typename T, typename V = VictimEffect, typename M = MenaceEffect>
class Collidable
{
    T m_Collider;
    V m_HitBy;
    M m_Hit;

public:
    Collidable(T collide, V victim, M menace) : m_Collider(collide), m_HitBy(victim),         m_Hit(menace) {;}
    Collidable(T collide) : m_Collider(collide) {;}
    Collidable(T collide, V victim) : m_Collider(collide), m_HitBy(victim) {;}

    T& GetCollider()
    {
        return m_Collider;
    }

    template <typename V>
    void HitBy(V& menace)
    {
        m_HitBy.HitBy(menace.GetCollider());
    }

    template <typename V>
    void Hit(V& victim)
    {
        m_Hit.Hit(victim.GetCollider());
    }

    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

然后使用它我这样做

    Collidable<Boundary, BallCommand> boundC(bounds, ballBehavior);
    Collidable<CollisionBox> ballC(ballPhysics);

无所不在是对的。通过传入基础,我丢失了类型信息。现在基地在那里只是为了捕捉我不关心的东西。

对于我的容器,我认为我可以使用类型列表来收集添加到容器中的每种类型,并使用它来声明特定类型的容器,然后处理信息。一个问题是我不能取出普通的迭代器,传递函数对象会更容易。

也许 C++ 0X 可以帮助处理这个新的未知类型变量。 (忘记叫什么了)。

【问题讨论】:

  • 我不相信这些信息在编译时可用。通过将基类实例(或者,更可能是引用派生实例的基类型的引用(一般意义上的指针计数))放入向量中,它已从编译时知识中“删除”。我认为您以某种方式无法使用虚函数。
  • 我知道,一旦我进行了继承,信息就会丢失,而且我知道将所有内容都放入一个集合中还有进一步的挑战。也许在那一点上它不能完成,但至于触发一个具有共同签名且仅在参数上有所不同的事件。我认为这是可以做到的。我可以用 Ball 和 Wall 做到这一点,但如果有意义的话,我不能用 (Ball Wall) 做一个对象,而用 Wall 做另一个对象。我也可以通过专门研究所有可能的结果来做到这一点,但这很乏味。
  • @Tavison - 一旦你开始使用虚函数,类访问者模式似乎会得到你想要的。
  • Tavison 你能更好地解释一下你想做什么吗?我不明白你的问题。给我们 Collidable 和 Victim/MenaceEffect 之间关系的完整代码?
  • 这可能有助于正确的方向。我已经成功地将观察者模式模板化,以使用命令模式移动物理和渲染对象。我不急于寻求解决方案。这更像是 Andrei 的书试图为游戏引擎设计寻找纯粹的通用解决方案。我在业余时间构建了一个现代游戏引擎,因为我讨厌迄今为止我使用过的所有引擎。感谢访客提示。我在 Modern C++ Design 中看到 Andrei 有一个访问者模板。很好的帮助。

标签: c++ templates game-physics double-dispatch


【解决方案1】:

也许是这样的? (只有hit部分,hitBy是对称的)

class Wall;

class MenaceBase {
    public:
    template<typename T>
    void hit(T& t) {
        t.hitImpl(*this);
    }
};

class VictimBase {
    public:
};

template<typename M>
class Menace {
    public:
    virtual void hit(M&) = 0;
};

template<typename V>
class Victim {
    public:
    virtual void hitBy(V&) = 0;
};

class Ball : public MenaceBase, public Menace<Wall>, public Menace<Ball> {
    public:
    void hit(Wall& wall) {}
    void hit(Ball& ball) {}
};

class Wall : public VictimBase, public Victim<Ball> {
    public:
    void hitBy(Ball& ball) {}
    void hitImpl(Menace<Wall>& menace) {
        menace.hit(*this);
    }
};

【讨论】:

  • 是的,那肯定会到达那里。我仍然想在没有虚拟的情况下这样做。我知道这听起来很奇怪,但 12 或 13 年前我决定不使用继承,除非绝对必要。它并不总是更好的代码或好的代码,但它帮助我找出了一些有趣的技术。我认为这个答案对于工作代码是正确的,但不是我正在寻找的思想练习。这绝对有帮助。非常感谢。
  • 好的,所以挑战是没有使用虚拟?你能接受dynamic_cast吗?
  • 我花了一分钟,但我明白你的意思了。我很想看看你有什么,但我不想把它作为最终的解决方案。我认为我们是从两个不同的角度来寻求解决方案的,最终的答案在中间的某个地方。
【解决方案2】:

好的。这就是我的结尾。

class VictimEffect
{
public:
    template<typename C>
    void HitBy(C&)
    {;}
};

class MenaceEffect
{
public:
    template<typename C>
    void Hit(C&)
    {;}
};


template <typename T, typename M = MenaceEffect, typename V = VictimEffect>
class Collidable
{
    T m_Collider;
    M m_WeHit;
    V m_WeWereHit;

public:
    Collidable(T collide, M menaceEffect, V victimEffect) : m_Collider(collide), m_WeHit(menaceEffect), m_WeWereHit(victimEffect) {;}
    Collidable(T collide) : m_Collider(collide) {;}
    Collidable(T collide, M menaceEffect) : m_Collider(collide), m_WeHit(menaceEffect) {;}

    T& GetCollider()
    {
        return m_Collider;
    }

    template <typename V>
    void HitBy(V& menace)
    {
        m_WeWereHit.HitBy(menace.GetCollider());
    }

    template <typename V>
    void Hit(V& victim)
    {
        m_WeHit.Hit(victim.GetCollider());
    }

    template <typename V>
    bool CheckCollision(V& menace)
    {
        return m_Collider.CheckCollision(menace);
    }
};

template <typename T, typename U>
inline void Collision(T& t, U& u)
{
    if(u.CheckCollision(t.GetCollider()))
    {
        u.HitBy(t);
        t.Hit(u);
    }
}

没有必要从受害者或威胁效果继承。如果您想要包罗万象的解决方案,它们就在那里。 M 和 V 类型可以是任何对象,所以如果你想继承或不继承,或者你想使用它。我必须为 ptrs 等创建所有变体。如果所有 using 对象都继承自基类,那么它可以像使用动态多态性编写一样使用。如果您不想继承而是保留特殊的单独列表,您也可以这样做。

使用起来很简单

class Boundary
{
// Put whatever code here.
    bool CheckCollision(CollisionBox bounce)
};

class BallCommand
{

public:
    void Hit(Boundary& bounds)
    {
        if(bounds.XHit())
        {
            xDir *= -1;
        }
        if(bounds.YHit())
        {
            yDir *= -1;
        }
    }
};

您在代码中的某个地方使用它。

    Collidable<Boundary> boundC(bounds);
    Collidable<std::shared_ptr<CollisionBox>, BallCommand&> ballC(ballPhysics, ballBehavior);

    Collision(ballC, boundC);

还有更多的清理工作要做,但我认为这是一个很好的开始。我将转换为 std::function 以使其更容易。

感谢 Omnifarious 和 Giovanni 帮助我思考问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多