我有一个 C++ 项目,使用 OpenFrameworks 进行渲染,它在窗口中弹跳几个球(圆圈)。它们是使用组件模式(有时称为策略模式)分别封装外观和行为来实现的。这使得外观(此处:Amiga Ball)和行为(此处:受重力弹跳)在运行时可交换。
模式本身的灵感来自 Robert Nystrom's book Game Programming Patterns 中的组件模式章节。 . 按下一个键后,“重力弹跳”的行为与“无重力弹跳”的行为交换(这可能是我编程而不是设计的原因)。 我想使用智能指针来实现该模式(在这种情况下,unique_ptr 可以,因为没有共享,并且一个组件对象始终由 GameObject 唯一持有)。 我的代码中有几个“丑陋”的部分与 unique_ptr 的使用以及我对组件使用多态性但似乎有时必须将它们视为派生类对象而不是基类这一事实有关。
我想知道它是否是一个好的和可行的设计和实现 w.r.t.一般模式的设计和实现,特别是智能指针的使用。
注意:我不关心这里的常量正确性,尽管我应该关心。所以我敢肯定有很多 const 可以放在那里,但请不要仅仅因为这个而抨击我,除非它会影响其他问题。
所以这里...
我们有一个 GameObject 类,它包含屏幕上任何对象应具有的最少信息: 游戏对象.hpp:
class GameObject {
public:
float x;
float y;
GameObject(unique_ptr<PhysicsComponent> physics, unique_ptr<GraphicsComponent> graphics);
GameObject();
GameObject(float x, float y);
void update();
void draw();
void setPhysicsComponent(unique_ptr<PhysicsComponent> pc);
void setGraphicsComponent(unique_ptr<GraphicsComponent> pc);
unique_ptr<PhysicsComponent>& getPhysicsComponent();
unique_ptr<GraphicsComponent>& getGraphicsComponent();
private:
unique_ptr<PhysicsComponent> physics_;
unique_ptr<GraphicsComponent> graphics_;
};
游戏对象.cpp:
GameObject::GameObject(unique_ptr<PhysicsComponent> physics, unique_ptr<GraphicsComponent> graphics) : physics_(move(physics)), graphics_(move(graphics)) {}
GameObject::GameObject() {}
GameObject::GameObject(float x, float y) : x(x), y(y) {}
void GameObject::update(){
physics_->update(*this);
}
void GameObject::draw() {
graphics_->draw(*this);
}
void GameObject::setPhysicsComponent(unique_ptr<PhysicsComponent> pc) {
physics_ = std::move(pc);
}
void GameObject::setGraphicsComponent(unique_ptr<GraphicsComponent> gc) {
graphics_ = std::move(gc);
}
std::unique_ptr<GraphicsComponent>& GameObject::getGraphicsComponent() {
return graphics_;
}
std::unique_ptr<PhysicsComponent>& GameObject::getPhysicsComponent() {
return physics_;
}
然后是组件的基类(在这种情况下,让我们关注物理组件,因为图形组件是等效的):
class GameObject;
class PhysicsComponent {
public:
virtual ~PhysicsComponent() {std::cout<<"~PhysicsComponent()"<<std::endl;}
virtual void update(GameObject& obj) = 0;
void setXSpeed(float xs) {xSpeed = xs;};
float getXSpeed() {return xSpeed;};
void setYSpeed(float ys) {ySpeed = ys;};
float getYSpeed() {return ySpeed;};
protected:
float ySpeed;
float xSpeed;
};
PhysicsComponent 的具体实现如下所示:
class GravityBouncePhysicsComponent : public PhysicsComponent {
public:
virtual ~GravityBouncePhysicsComponent() {std::cout<<"~GravityBouncePhysicsComponent()"<<std::endl;};
GravityBouncePhysicsComponent();
GravityBouncePhysicsComponent(float radius, float xSpeed, float ySpeed, float yAccel);
virtual void update(GameObject& obj);
void setRadius(float r) {radius = r;};
float getRadius() {return radius;};
private:
// Physics
float yAcceleration;
float radius;
};
在 GravityBouncePhysicsComponent.cpp 中,空的构造函数创建了这样一个组件,它具有 xSpeed 和 ySpeed(存在于基类中)和 yAcceleration(仅在派生组件类中有意义)的随机值。 然后还有另一个名为 FloatBouncePhysicsComponent 的类似代码。
现在我想在主程序中使用这些类,正好是OpenFrameworks中的ofApp类。头文件看起来像这样(省略了一些无关紧要的东西):
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
void keyPressed(int key);
// ... lots of other event functions for mouse etc.
private:
std::vector<GameObject> balls;
GameObject createAmigaBall(float x, float y);
// more variables and some methods here ...
};
现在在 ofApp.cpp 文件中它开始变得丑陋。例如,在创建一个新球的辅助函数中,它看起来像这样:
GameObject ofApp::createAmigaBall(float x, float y) {
GameObject go(x,y); // create an empty GameObject on the stack at position x,y
go.setGraphicsComponent(std::make_unique<AmigaBallGraphicsComponent>());
if(gravity) {
go.setPhysicsComponent(std::make_unique<GravityBouncePhysicsComponent>());
float r = (static_cast<AmigaBallGraphicsComponent*>((go.getGraphicsComponent()).get()))->getRadius();
(static_cast<GravityBouncePhysicsComponent*>((go.getPhysicsComponent()).get()))->setRadius(r);
} else {
go.setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>());
float r = (static_cast<AmigaBallGraphicsComponent*>((go.getGraphicsComponent()).get()))->getRadius();
(static_cast<FloatBouncePhysicsComponent*>((go.getPhysicsComponent()).get()))->setRadius(r);
}
return go; // calls the copy constructor for return value, so we have no dangling pointers or refs
}
此处 AmigaBallGraphicsComponent 空构造函数生成随机半径。这个半径属于这个派生类,因为它肯定不在每个 GraphicsComponent 中。 然而,这导致不得不笨拙地从生成的具有丑陋转换的组件中提取半径并访问原始指针,只是再次这样做以在 GravityBouncePhysicsComponent 中设置相同的半径。
如果您认为那很丑陋,请查看此代码段:
void ofApp::keyPressed(int key){
if(key == 'a') {
if(gravity) {
for(int i=0; i<balls.size(); i++) {
unique_ptr<GravityBouncePhysicsComponent> opc (static_cast<GravityBouncePhysicsComponent*>((balls[i].getPhysicsComponent()).release()));
float r = opc->getRadius();
float xs = opc->getXSpeed();
float ys = opc->getYSpeed();
balls[i].setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>(r,xs,ys));
}
} else {
// similar code for the non-gravity case omitted ...
}
}
gravity =!gravity;
}
}
而且,好像转换为派生类的东西还不够,结果证明这段代码不起作用
auto gc = std::make_unique<AmigaBallGraphicsComponent>();
go.setGraphicsComponent(gc);
虽然这个是。什么……?
go.setGraphicsComponent(std::make_unique<AmigaBallGraphicsComponent>());
这不应该完全一样吗?
在此先感谢您对整个困惑局面的任何见解(或者是吗?)。
最佳答案
我认为您的大部分问题都来自这样一个事实,即您让所有权 ( std::unique_ptr
) 和类型删除(多态性)问题从您的 API 中渗出,这需要大量不相关的生命周期管理和向下转型用户端。 getGraphicsComponent
(及其伙伴)可以这样实现:
template <class DerivedGraphicsComponent = GraphicsComponent>
auto &getGraphicsComponent() {
return static_cast<DerivedGraphicsComponent &>(*graphics_);
}
这使您能够编写:
float r = go.getGraphicsComponent<AmigaBallGraphicsComponent>().getRadius();
或者省略 <AmigaBallGraphicsComponent>
并收到一个普通的 GraphicsComponent &
.
您确实失去了 GameObject
的能力撤销其组件之一的所有权,并通过 std::unique_ptr
归还它,但看起来您实际上并不需要它。您使用该功能的代码段最终会执行以下操作:
balls[i].setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>(r,xs,ys));
... 无论如何都会通过 graphics_
破坏前一个组件的移动赋值运算符。如果您确实需要该功能,请务必添加另一个功能:
template <class DerivedGraphicsComponent = GraphicsComponent>
auto releaseGraphicsComponent() {
return std::unique_ptr<DerivedGraphicsComponent>(
static_cast<DerivedGraphicsComponent *>(graphics_.release())
);
}
关于c++ - C++ 中的组件/策略模式、设计和实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57988748/