c++粒子系统继承

c++ particle system inheritance

本文关键字:继承 粒子系统 c++      更新时间:2024-05-09

我正在创建粒子系统,我想有可能选择屏幕上显示的对象类型(比如简单的像素或圆形(。我有一个类,所有参数都存储在其中(ParticleSettings(,但没有那些存储点或圆形等的实体。我想我可以创建纯虚拟类(ParticlesInterface(作为基类,以及它的派生类,如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; }
...
}

所以。。。我知道我无法使用polimorpism访问getParticleVertex((方法。我真的很想有这样的机会。我想问一下是否有更好的解决方案。我真的很难决定如何将所有这些联系在一起。我的意思是,我也在考虑使用模板类,但我需要它是动态绑定,而不是静态的。我认为这种多态性的想法是可以的,但我真的需要在那个选项中访问那个方法。你能帮我怎么做吗?我想知道什么是最好的方法,如果我决定以上面的方式来解决这个问题,是否有什么好的答案。

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

这里推荐的方法是:每当你需要根据实际的具体类型做不同事情的代码时,把这些"不同的事情"变成接口中的虚拟函数。

所以从开始

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,一个类型需要一些自定义的东西这一事实也暗示着其他未来的类型或对现有类型的更改可能也需要自己的自定义行为。相反,从代码对接口所做的事情更改为可以要求接口做的事情:

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

现在粒子类更"活跃"了——它们积极地做事,而不仅仅是被作用

再举一个例子,假设您发现ParticlesCircle,而不是ParticlesVertex,需要在坐标更改时进行一些成员数据更新。您可以在ParticlesInterface中添加一个virtual void coordChangeCB() {},并在每个运动模型滴答作响后或任何时候调用它。由于接口类中的{}定义为空,任何像ParticlesVertex这样不关心回调的类都不需要覆盖它

按照单一责任原则,尽量保持接口的虚拟功能的意图简单。如果你不能用一两句话写下函数的目的或预期行为,那么它可能太复杂了,也许可以更容易地用更小的步骤来思考。或者,如果您发现多个类中的虚拟重写具有相似的模式,那么这些实现中的一些较小部分可能是有意义的虚拟函数;更大的功能可能是虚拟的,也可能不是虚拟的,这取决于剩下的功能是否真的适用于接口。

(编程最佳实践是建议,有充分的理由支持,但不是绝对的法律:我不会说"永远不要使用dynamic_cast"。有时出于各种原因,违反规则是有意义的。(