C++粒子系统继承

c++ particle system inheritance

我正在创建粒子系统,我希望能够选择在屏幕上显示哪种对象(例如简单的像素或圆形)。我有一个 class,其中存储了所有参数(ParticleSettings),但没有那些存储点或圆形等的实体。我认为我可以创建纯虚拟 class(ParticlesInterface)作为base class 及其派生的 classes,如 ParticlesVertex 或 ParticlesCircles,用于存储这些可绘制对象。是这样的:

class ParticlesInterface
{
protected:
    std::vector<ParticleSettings>   m_particleAttributes;   

public:
    ParticlesInterface(long int amount = 100, sf::Vector2f position = { 0.0,0.0 });
    const std::vector<ParticleSettings>& getParticleAttributes() { return m_particleAttributes; }
...
}

和:

class ParticlesVertex : public ParticlesInterface
{
private:                            
    std::vector<sf::Vertex>         m_particleVertex;
public:
    ParticlesVertex(long int amount = 100, sf::Vector2f position = { 0.0,0.0 });
    std::vector<sf::Vertex>& getParticleVertex() { return m_particleVertex; }
...
}

所以...我知道我无法通过使用多态性来访问 getParticleVertex() 方法。我真的很想拥有这种访问权限。我想问一下是否有更好的解决方案。我真的很难决定如何将所有这些联系在一起。我的意思是我也在考虑使用模板 classes 但我需要它是动态绑定而不是静态的。我认为这种多态性的想法是可以的,但我真的需要在该选项中访问该方法。你能帮我怎么做吗?我想知道这里最好的方法是什么,如果我决定按照上面向您展示的方式来解决这个问题,我是否有任何好的答案。

听上去,ParticlesInterface 抽象 class 不只是有一个虚拟的 getParticleVertex 因为这在一般情况下没有意义,只对特定类型有意义ParticlesVertex,或者可能是一组相关类型。

此处推荐的方法是:每当您需要根据实际具体类型执行不同操作的代码时,将这些 "different things" 设为接口中的虚函数。

所以从:

开始
void GraphicsDriver::drawUpdate(ParticlesInterface &particles) {
    if (auto* vparticles = dynamic_cast<ParticlesVertex*>(&particles)) {
        for (sf::Vertex v : vparticles->getParticleVertex()) {
            draw_one_vertex(v, getCanvas());
        }
    } else if (auto* cparticles = dynamic_cast<ParticlesCircle*>(&particles)) {
        for (CircleWidget& c : cparticles->getParticleCircles()) {
            draw_one_circle(c, getCanvas());
        }
    }
    // else ... ?
}

(CircleWidget是编的,我对sf不熟悉,但这不是重点。)

因为 getParticleVertex 对每种 ParticleInterface 都没有意义,任何从界面使用它的代码都必须进行某种类似于 if 的检查,和一个 dynamic_cast 来获取实际数据。如果需要更多类型,上面的 drawUpdate 也是不可扩展的。即使有一个通用的 else 可以 "should" 处理其他一切,一个类型需要一些自定义的事实暗示其他未来类型或对现有类型的更改可能也需要它自己的自定义行为.相反,从代码对接口所做的事情更改为可以要求接口做的事情:

class ParticlesInterface {
    // ...
public:
    virtual void drawUpdate(CanvasWidget& canvas) = 0;
    // ...
};

class ParticlesVertex {
    // ...
    void drawUpdate(CanvasWidget& canvas) override;
    // ...
};
class ParticlesCircle {
    // ...
    void drawUpdate(CanvasWidget& canvas) override;
    // ...
};

现在粒子class更多了"alive"——它们主动做事,而不是仅仅被作用。

再举个例子,假设你发现ParticlesCircle,但不是ParticlesVertex,每当坐标改变时,需要更新一些成员数据。您可以将 virtual void coordChangeCB() {} 添加到 ParticlesInterface 并在每个运动模型 tick 之后或任何时候调用它。使用接口 class 中的 {} 空定义,任何不关心回调的 class 如 ParticlesVertex 不需要覆盖它。

遵循单一职责原则,尽量使接口的虚函数在意图上保持简单。如果您不能用一两句话写出该函数的一般目的或预期行为是什么,它可能太复杂了,也许它可以更容易地在更小的步骤中被想到。或者,如果您发现多个 classes 中的虚拟覆盖具有相似的模式,那么这些实现中的一些较小的部分可能是有意义的虚拟函数;更大的功能可能会或可能不会保持虚拟,这取决于剩下的部分是否可以被认为是界面的真正通用。

(编程最佳实践是建议,有充分的理由支持,但不是绝对的法律:我不会说 "NEVER use dynamic_cast"。有时出于各种原因 break the rules 可能有意义。 )