我正在尝试编写一个类(某种图形引擎),基本上它的目的是渲染我传入的任何内容。在我见过的大多数教程中,对象都会自行绘制。我不确定事情是否应该这样运作。我一直在互联网上搜索,试图想出不同的方法来解决这个问题,我一直在一遍又一遍地审查函数模板和类模板(这听起来像是我可能正在寻找的解决方案),但是当我尝试使用模板时,对我来说似乎很混乱(可能是因为我不完全了解如何使用它们),然后我会想把模板类拿下来, 然后我会再试一次,但后来我又把它拿下来了,我不确定这是否是要走的路,但它可能是。最初它只是基于平铺的(包括屏幕上的可移动播放器以及相机系统),但现在我尝试编写一个平铺地图编辑器,其中包含工具栏、列表、文本,甚至将来屏幕上的基元等内容,我想知道如何使用某个程序将所有这些元素绘制到屏幕上(该过程现在并不重要, 我稍后会发现的)。如果你们中的任何人要编写图形引擎类,您将如何让它区分不同类型的图形对象,例如未绘制为精灵的基元或未绘制为三角形基元的球体基元等?任何帮助将不胜感激。:)
这是它的标题,它现在不起作用,因为我一直在对它进行一些编辑,只需忽略我使用"new"关键字的部分,我仍在学习,但我希望这能为我想要完成的事情提供一个想法:
//graphicsEngine.h
#pragma once
#include<allegro5allegro.h>
#include<allegro5allegro_image.h>
#include<allegro5allegro_primitives.h>
template <class graphicObjectData>
class graphicsEngine
{
public:
static graphicObjectData graphicObject[];
static int numObjects;
static void setup()
{
al_init_image_addon();
al_init_primitives_addon();
graphicObject = new graphicObjectData [1]; //ignore this line
}
template <class graphicObjectData> static void registerObject(graphicObjectData &newGraphicObject) //I'm trying to use a template function to take any type of graphic object
{
graphicObject[numObjects] = &newObject;
numObjects++;
}
static void process() //This is the main process where EVERYTHING is supposed be drawn
{
int i;
al_clear_to_color(al_map_rgb(0,0,0));
for (i=0;i<numObjects;i++) drawObject(graphicObject[i]);
al_flip_display();
}
};
我是模板的忠实粉丝,但在这种情况下您可能会发现它们很麻烦(尽管不一定是错误的答案)。 由于您可能希望在绘图容器中具有不同的对象类型,因此继承实际上可能是一个更强大的解决方案。
您需要一个提供抽象绘图接口的基类型。 此类所需要的只是一些为实际绘制过程提供机制的函数。 它实际上并不关心绘图是如何发生的,重要的是派生类知道如何绘制自己(如果你想将你的绘图和你的对象分开,请继续阅读,我将尝试解释一种方法来实现这一点):
class Drawable {
public:
// This is our interface for drawing. Simply, we just need
// something to instruct our base class to draw something.
// Note: this method is pure virtual so that is must be
// overriden by a deriving class.
virtual void draw() = 0;
// In addition, we need to also give this class a default virtual
// destructor in case the deriving class needs to clean itself up.
virtual ~Drawable() { /* The deriving class might want to fill this in */ }
};
从这里开始,您只需编写从 Drawable 类继承的新类并提供必要的 draw() 覆盖。
class Circle : public Drawable {
public:
void draw() {
// Do whatever you need to make this render a circle.
}
~Circle() { /* Do cleanup code */ }
};
class Tetrahedron : public Drawable {
public:
void draw() {
// Do whatever you need to make this render a tetrahedron.
}
~Tetrahedron() { /* Do cleanup code */ }
};
class DrawableText : public Drawable {
public:
std::string _text;
// Just to illustrate that the state of the deriving class
// could be variable and even dependent on other classes:
DrawableText(std::string text) : _text(text) {}
void draw() {
// Yet another override of the Drawable::draw function.
}
~DrawableText() {
// Cleanup here again - in this case, _text will clean itself
// up so nothing to do here. You could even omit this since
// Drawable provides a default destructor.
}
};
现在,要将所有这些对象链接在一起,您只需将它们放在您选择的容器中,该容器接受引用或指针(或C++11及更高版本,unique_ptr,shared_ptr和朋友)。 设置您需要的任何绘制上下文,并遍历调用 draw() 的容器的所有内容。
void do_drawing() {
// This works, but consider checking out unique_ptr and shared_ptr for safer
// memory management
std::vector<Drawable*> drawable_objects;
drawable_objects.push_back(new Circle);
drawable_objects.push_back(new Tetrahedron);
drawable_objects.push_back(new DrawableText("Hello, Drawing Program!"));
// Loop through and draw our circle, tetrahedron and text.
for (auto drawable_object : drawable_objects) {
drawable_object->draw();
}
// Remember to clean up the allocations in drawable_objects!
}
如果要向绘制机制提供状态信息,可以要求将其作为 Drawable 基类的 draw() 例程中的参数:
class Drawable {
public:
// Now takes parameters which hold program state
virtual void draw(DrawContext& draw_context, WorldData& world_data) = 0;
virtual ~Drawable() { /* The deriving class might want to fill this in */ }
};
当然,派生类 Circle、Tetrahedron 和 DrawableText 需要更新它们的 draw() 签名才能采用新的程序状态,但这将允许您通过专为图形绘制而设计的对象进行所有低级绘制,而不是给主类增加此功能的负担。 您提供的状态完全取决于您和您的设计。 它非常灵活。
大更新 - 使用合成的另一种方法
我一直在仔细考虑,并决定分享我所做的事情。 我上面写的东西过去对我有用,但这一次我决定用我的引擎走一条不同的路线,完全放弃场景图。 我不确定我是否可以推荐这种做事方式,因为它会使事情变得复杂,但它也为巨大的灵活性打开了大门。 实际上,我已经编写了较低级别的对象,例如VertexBuffer,Effect,Texture等,这些对象允许我以任何我想要的方式组合对象。 这次我使用的不仅仅是继承(尽管为顶点缓冲区、纹理等提供实现仍然是必要的)。
我提出这个问题的原因是因为你在谈论获得更大程度的分离。 使用我描述的系统,我可以构建一个这样的世界对象:
class World {
public:
WorldGeometry geometry; // Would hold triangle data.
WorldOccluder occluder; // Runs occlusion tests against
// the geometry and flags what's visible and
// what is not.
WorldCollider collider; // Handles all routines for collision detections.
WorldDrawer drawer; // Draws the world geometry.
void process_and_draw();// Optionally calls everything in necessary
// order.
};
在这里,我将有多个对象,它们专注于引擎处理的单个方面。 WorldGeometry将存储有关此特定世界对象的所有多边形详细信息。 WorldOccluder会检查相机和几何形状,看看世界的哪些斑块实际上是可见的。 WorldCollider 将处理针对任何世界对象的碰撞检测(为简洁起见省略)。 最后,WorldDrawer实际上将负责绘制世界,并根据需要维护VertexBuffer和其他较低级别的绘图对象。
如您所见,这与您最初要求的内容更加接近,因为几何体实际上不仅用于渲染。 它是关于世界多边形的更多数据,但可以提供给WorldGeometry和WorldOccluder,它们不做任何绘图。 事实上,World 类的存在只是为了将这些相似的类组合在一起,但 WorldDrawer 可能不依赖于 World 对象。 相反,它可能需要一个 WorldGeometry 对象,甚至需要一个三角形列表。 基本上,您的程序结构变得非常灵活,依赖关系开始消失,因为对象不经常或根本不继承,只请求它们绝对需要的功能。 举个例子:
class WorldOccluder {
public:
// I do not need anything more than a WorldGeometry reference here //
WorldOccluder(WorldGeometry& geometry) : _geometry(geometry)
// At this point, all I need to function is the position of the camera //
WorldOccluderResult check_occlusion(const Float3& camera) {
// Do all of the world occlusion checks based on the passed
// geometry and then return a WorldOccluderResult
// Which hypothetically could contain lists for visible and occluded
// geometry
}
private:
WorldGeometry& _geometry;
};
我之所以选择WorldOccluder作为例子,是因为我花了大半天的时间为我的引擎做这样的事情,并且使用了与上面非常相似的类层次结构。 我有 3D 空间中的框根据是否应该看到它们来改变颜色。 我的课程非常简洁且易于遵循,我的整个项目层次结构也很容易遵循(我认为无论如何都是如此)。所以这似乎工作得很好! 我喜欢度假!
最后说明:我提到了模板,但没有解释它们。 如果我有一个围绕绘图进行处理的对象,那么模板非常适合此。 它避免了依赖关系(例如通过继承),同时仍然提供了很大程度的灵活性。 此外,编译器可以通过内联代码和避免虚拟样式调用来优化模板(如果编译器可以推断出此类优化):
template <typename TEffect, TDrawable>
void draw(TEffect& effect, TDrawable& drawable, const Matrix& world, const Matrix& view, const Matrix& projection) {
// Setup effect matrices - our effect template
// must provide these function signatures
effect.world(world);
effect.view(view);
effect.projection(projection);
// Do some drawing!
// (NOTE: could use some RAII stuff here in case drawable throws).
effect.begin();
for (int pass = 0; pass < effect.pass_count(); pass++) {
effect.begin_pass(pass);
drawable.draw(); // Once again, TDrawable objects must provide this signature
effect.end_pass(pass);
}
effect.end();
}
我的技术可能真的很糟糕,但我这样做。
class entity {
public:
virtual void render() {}
};
vector<entity> entities;
void render() {
for(auto c : entities) {
c->render();
}
}
然后我可以做这样的事情:
class cubeEntity : public entity {
public:
virtual void render() override {
drawCube();
}
};
class triangleEntity : public entity {
public:
virtual void render() override {
drawTriangle();
}
};
并使用它:
entities.push_back(new cubeEntity());
entities.push_back(new triangleEntity());
人们说使用动态继承是不好的。他们比我聪明得多,但这种方法已经工作了一段时间。确保使所有析构函数都虚拟!
SFML图形库绘制对象的方式(我认为最易于管理的方式)是让所有可绘制对象继承自"可绘制"类(如David Peterson的答案中的类),然后可以将其传递给图形引擎以进行绘制。
要绘制对象,我需要:
基类:
class Drawable
{
int XPosition;
int YPosition;
int PixelData[100][100]; //Or whatever storage system you're using
}
这可用于包含所有可绘制类共有的信息(如位置和某种形式的数据存储)。
派生子类:
class Triangle : public Drawable
{
Triangle() {} //overloaded constructors, additional variables etc
int indigenous_to_triangle;
}
由于每个子类在很大程度上都是唯一的,因此可以使用此方法创建从精灵到图形基元的任何内容。
然后,这些派生类中的每一个都可以通过引用传递给引擎
引用基类的"Draw"函数:
void GraphicsEngine::draw(const Drawable& _object);
使用此方法时,不再需要模板。不幸的是,您当前的 graphicObjectData 数组不起作用,因为派生类将被"切片"以适应它。但是,创建"const Drawable*"指针(或最好是智能指针)的列表或向量对于保持所有对象的选项卡同样有效,尽管实际对象必须存储在其他地方。
您可以使用这样的东西来使用指针向量绘制所有内容(我试图保留您的函数和变量名称):
std::vector<const Drawable*> graphicObject; //Smart pointers would be better here
static void process()
{
for (int i = 0; i < graphicObject.size(); ++i)
draw(graphicObject[i]);
}
您只需要确保在创建时将每个对象添加到列表中即可。如果你对此很聪明,你甚至可以在建造和破坏中做到这一点:
class Drawable; //So the compiler doesn't throw an error
std::vector<const Drawable*> graphicObject;
class Drawable
{
Triangle() {} //overloaded constructors, additional variables etc
int indigenous_to_triangle;
std::vector<const Drawable*>::iterator itPos;
Drawable() {
graphicObject.push_back(this);
itPos = graphicObject.end() - 1;
}
~Drawable() {
graphicObject.erase(itPos);
}
}
现在你可以只创建对象,当调用 process() 时,它们会自动绘制!一旦它们被销毁,它们甚至会从列表中删除!
以上所有想法在过去对我都有很好的帮助,所以我希望我能帮助你,或者至少给你一些思考。