graphics模块:我正确的方式吗?

我试图写我的引擎的graphics模块。 也就是说,这部分代码只提供一个接口,通过它加载图像,字体等,并在屏幕上绘制它们。 它也是我正在使用的库(本例中是SDL)的包装器。

这里是我的ImageFontGraphicsRenderer类的接口。 请告诉我,如果我正确的方式。

图片

 class Image { public: Image(); Image(const Image& other); Image(const char* file); ~Image(); bool load(const char* file); void free(); bool isLoaded() const; Image& operator=(const Image& other); private: friend class GraphicsRenderer; void* data_; }; 

字形

 class Font { public: Font(); Font(const Font& other); Font(const char* file, int ptsize); ~Font(); void load(const char* file, int ptsize); void free(); bool isLoaded() const; Font& operator=(const Font& other); private: friend class GraphicsRenderer; void* data_; }; 

GrapphicsRenderer

 class GraphicsRenderer { public: static GraphicsRenderer* Instance(); void blitImage(const Image& img, int x, int y); void blitText(const char* string, const Font& font, int x, int y); void render(); protected: GraphicsRenderer(); GraphicsRenderer(const GraphicsRenderer& other); GraphicsRenderer& operator=(const GraphicsRenderer& other); ~GraphicsRenderer(); private: void* screen_; bool initialize(); void finalize(); }; 

编辑:对代码和更多细节的一些更改。

根据这里的一些讨论,我决定用这样的东西replace我对void*使用:

 class Image { private: struct ImageData; std::shared_ptr<ImageData> data_; }; 

(显然我会为Font类做同样的事情。)

我还应该提到这些不是我最后的完整课程。 我只显示这里的基本function(加载和渲染)。 而不是告诉我你认为我可能需要添加什么function(旋转图像,倾斜,缩放等),只是集中精力审查我已经有了什么。 我会尽力捍卫我的select,或者如果我不能改变我的方法。

对我来说最痒的是void * '滥用'。

无效指针:我不需要它。 这是限制包含SDL.h的文件数量的方法(void * data_只是SDL_Surface *强制转换为void *)

那么,你可以避免包容(我批准顺便说一句)通过转发声明方便你的地方。

在接口(一般)

所以,你要求我们审查你的devise接口。

你没有给我们接口,你给我们完整的类声明。 如果这些是接口 ,我希望看到像这样的东西:

 virtual bool load file(const char* file) = 0; 

在C ++中是一个接口。 我可以在实现function的子类中覆盖它(事实上,我必须!)。 如果你正在编写一个接口,你正在执行策略,上面是你如何做到这一点。

如果你刚刚暴露了接口函数,并且隐藏了成员variables(因为它们应该在接口类中),那么在其他答案中有关使用void *的一半投诉就可以避免。

RAWR。

在接口(你的)

图像:复制

你有一个拷贝构造函数和一个等号运算符。 我在这里看到的问题是,没有什么好方法可以防止用户做出多余的图像副本。

对于你来说,使用SDL_surfaces, 这是一个大问题 。 没有意义冒犯,我敢打赌,你没有考虑到当你释放一个图像是另一个图像的副本时会发生什么。 我更愿意打赌,你没有计划处理完整的SDL_surface复制,所以在上述情况下,你可能会释​​放一个图像,然后你的其他副本将爆炸,杀死你所爱的每个人。

解决scheme:无副本。 不要这样做,不要让它。 使用工厂或C样式加载器函数来创建图像的新实例,并使用这些实例,而不是允许复制或平等分配。 或者,完全弄清楚如何深度复制SDL_image(不是超级困难,但烦人)。

图像:操纵

一旦我加载了图像,我如何改变图像? 根据你的界面,我没有。 不过,你确定这是一个好主意吗? 如何找出图像的位深度? 它的高度? 宽度? 色彩空间?

字形

我如何绘制这种字体? 我如何得到它的名字? 如何防止上面我抱怨的复制问题? 我如何设置颜色? 字距? Unicode支持呢?

渲染器:一般

所以,我注意到你有几个blit *()函数,还有一个render()函数。 这似乎意味着您希望用户能够排队一堆blitting操作,然后使用render()调用一次全部刷新屏幕。

没关系; 事实上,这就是我们团队的引擎技术也是如此。 🙂

在这里使用单例是可以接受的,主要是因为你似乎想让渲染器完全控制绘制东西。 如果只有一个实例(可能应该是这样),这不会伤害任何东西。 不是我们做的,但嘿,这是一个品味的问题。

不过,我在这里看到了几个大问题。

渲染器:转换

你似乎只在2D工作。 没关系。 但…

在绘制图像时,如何处理像旋转图像的东西? 缩放它? 您需要完全支持所谓的仿射转换 。 这使您可以轻松地旋转,缩放,翻译,歪斜,否则在一个漂亮的时尚图像frob。

这需要(以某种方式)支持文本和图像。

渲染器:着色和混合

我希望能够将颜色混合到我的图像上,并为我的文字设置颜色。 你应该暴露这个。

我也希望在blitting的时候能够像混合图像那样做事情,所以我可以做像半透明的鬼魂或烟雾或火焰的东西。

如何救自己的麻烦

使用SFML 。 对于SDL,几乎所有的devise都有更好的devise。 他们已经完成了你在这里要做的事情。 至less,看看他们如何规定他们的接口,以及他们如何devise他们的类层次结构。

另外请注意,他们解决了我之前在Drawable类中指出的转换问题。 和着色。 和混合。

他们有很好的教程和文档,所以可能值得花一点时间来摆弄一下,看看你的代码应该能够完成什么。

祝你好运!

我有点困扰“负载”的方法,打破了单一责任原则(顺便说一句,共产党鸭,我想这就是为什么他使用const char *,因为SDL加载图像函数,编码在C中,不要采取一个std ::string)。 它不应该是一个Image类的工作来加载自己,至less有两个原因:

  • 如果你打算优化你的内存使用,你会想要有专门的类来加载资源。
  • 你会更容易地处理exception与一个专门的类。

Singleton = BAD,立即删除。 字体和图像不应该有一个free()函数,这应该是析构函数的工作。 GraphicsRenderer不应该提供这些Blit函数,您应该提供面向对象的类,它将为每个最终结果提供一个位置,以及由GraphicsRenderer自动管理的实际渲染。 最后,对于封装,使用inheritance,而不是PIMPL, 绝对不使用void *,使用强types的不透明指针。

下面是我自己devise的一些摘录,尽管我使用了编译时切换而不是运行时inheritance。

 class D3D9Render { public: std::shared_ptr<D3D9Font> CreateFont(); }; class D3D9Font { public: // PUBLIC INTERFACE --------------------------------------------------- std::unique_ptr<D3D9Text> CreateText(); int Height(); D3D9Font* Height(char newheight); D3D9Font(D3D9Render& ref); int Width(); D3D9Font* Width(char newwidth); int Weight(); D3D9Font* Weight(short newweight); bool Italic(); D3D9Font* Italic(bool newitalic); string Font(); D3D9Font* Font(string str); D3D9Font* CommitChanges(); }; class D3D9Text { public: // PUBLIC INTERFACE --------------------------------------------------- D3D9Text* Text(string str); D3D9Text* PositionSizeX(short newx, short newxsize); D3D9Text* PositionSizeY(short newy, short newysize); D3D9Text* Font(std::shared_ptr<D3D9Font> ref); D3D9Text* Colour(unsigned int newcolour); string Text(); int PositionX(); int PositionY(); int SizeX(); int SizeY(); std::shared_ptr<D3D9Font> Font(); unsigned int Colour(); D3D9Text* CommitChanges(); }; 

在这里,内存是为您管理的 – 所有权都通过智能指点来管理,界面完全是面向对象的。

我可以发现的第一件事:

  • 单身人士通常非常糟糕。 这不是呃,我只想要其中之一吗? 但是更多的“呃,这其中会不止一个打破这个计划?”。
  • void *指针也相当危险。 你为什么需要一个? 坏的devise某处。
  • FontImage似乎密切相关。 也许你可以把一些function上升到一个Renderable
  • 我认为这是我,但为什么你使用const char*超过std::string任何理由?