Mô-đun đồ họa: Tôi đang đi đúng hướng?


7

Tôi đang cố gắng viết mô-đun đồ họa của động cơ của tôi. Đó là, phần mã này chỉ cung cấp một giao diện để tải hình ảnh, phông chữ, vv và vẽ chúng trên màn hình. Nó cũng là một trình bao bọc cho thư viện tôi đang sử dụng (SDL trong trường hợp này).

Dưới đây là các giao diện cho tôi Image, FontGraphicsRenderercác lớp học. Xin vui lòng cho tôi biết nếu tôi đang đi đúng hướng.

Hình ảnh

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_;
};

Nét chữ

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();
};

Chỉnh sửa: Một số thay đổi về mã và biết thêm chi tiết.

Theo một số cuộc thảo luận ở đây, tôi quyết định thay thế việc sử dụng của mình void*bằng một cái gì đó như thế này:

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

(Rõ ràng là tôi sẽ làm điều tương tự cho Fontlớp học.)

Tôi cũng nên đề cập đến những chiếc mũ này không phải là lớp học hoàn chỉnh cuối cùng của tôi. Tôi chỉ hiển thị ở đây các chức năng cơ bản (tải và kết xuất). Thay vì cho tôi biết chức năng nào bạn nghĩ rằng tôi có thể cần thêm (xoay hình ảnh, nghiêng, chia tỷ lệ, v.v.) chỉ tập trung vào xem xét những gì tôi đã có. Tôi sẽ cố gắng bảo vệ những lựa chọn của mình nếu có thể hoặc thay đổi cách tiếp cận nếu không thể.


Câu hỏi: Đây là loại động cơ gì? Phạm vi dự kiến ​​của dự án là gì? Tôi hơi lo lắng rằng tôi có thể đưa ra lời khuyên tồi dựa trên quan niệm sai lầm về những gì bạn dự định dự án của bạn sẽ làm.
ChrisE

Câu trả lời:


1

Điều gây ngứa nhất cho tôi là void * 'lạm dụng'.

Con trỏ trống rỗng: Tôi không cần nó. Đó là cách để tôi giới hạn số lượng tệp bao gồm SDL.h (void * data_ chỉ là SDL_Surface * cast thành void *)

Chà, bạn có thể tránh đưa vào (mà tôi chấp thuận BTW) bằng cách chuyển tiếp tuyên bố nó ở đâu đó thuận tiện cho bạn.


Sau một số ý kiến ​​tôi quyết định rằng tôi sẽ xác định một tư nhân struct FontDatavà có một std::shared_ptr<FontData>thành viên riêng thay vì void*. Điều này có nghĩa là tôi cũng có thể tránh khai báo chuyển tiếp, điều đó có nghĩa là không có đề cập đến thư viện trong tệp tiêu đề. :)
Paul Manta

Ok, giải pháp tốt đẹp.
jv42

6

Trên giao diện (nói chung)

Vì vậy, bạn đã yêu cầu chúng tôi xem xét thiết kế của bạn cho các giao diện.

Bạn đã không cung cấp cho chúng tôi giao diện, bạn đã cho chúng tôi khai báo lớp đầy đủ. Nếu đây là các giao diện , tôi sẽ thấy một cái gì đó như:

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

Cái đó , trong C ++, là một giao diện. Tôi có thể ghi đè lên nó trong một lớp con thực hiện chức năng (trên thực tế, tôi phải!). Nếu bạn đang viết một giao diện, bạn đang thực thi chính sách và trên đây là cách bạn thực hiện điều này.

Một nửa số khiếu nại về việc sử dụng void * trong các câu trả lời khác sẽ được tránh nếu bạn vừa tiếp xúc với các chức năng giao diện và giữ các biến thành viên bị ẩn (như chúng phải có, trong một lớp giao diện ).

Nguyên

Trên giao diện (của bạn)

Hình: Sao chép

Bạn đã có một hàm tạo sao chép và một toán tử bằng. Vấn đề tôi thấy ở đây là không có cách nào tốt để ngăn người dùng tạo ra các bản sao hình ảnh ngớ ngẩn bên ngoài.

Đối với bạn, sử dụng SDL_surfaces, đây là một vấn đề lớn . Không có ý xúc phạm, tôi sẵn sàng cá rằng bạn chưa xem xét điều gì xảy ra khi bạn giải phóng một Hình ảnh trùng lặp với một hình ảnh khác. Tôi sẵn sàng đặt cược rằng bạn đã không có kế hoạch xử lý sao chép toàn bộ SDL_surface, và trong trường hợp đã nói ở trên, bạn có khả năng giải phóng một Hình ảnh, và sau đó các bản sao khác của nó sẽ phát nổ, giết chết mọi người bạn yêu thích .

Giải pháp: KHÔNG CÓ BẢN SAO. Đừng làm điều đó, đừng cho phép nó. Sử dụng chức năng của trình tải của nhà máy hoặc kiểu C để tạo các phiên bản mới của Hình ảnh và sử dụng các trường hợp đó thay vì cho phép sao chép hoặc bằng các bài tập. Cách khác, hoàn toàn tìm ra cách sao chép sâu SDL_image (không quá khó, nhưng gây khó chịu).

Hình: thao tác

Làm cách nào để thay đổi hình ảnh của bạn sau khi tôi tải chúng? Theo giao diện của bạn, tôi không. Tuy nhiên, bạn có chắc chắn đó là một ý tưởng tốt? Làm cách nào để tìm ra độ sâu bit của hình ảnh? Chiều cao của nó? Chiều rộng? Không gian màu?

Nét chữ

Làm thế nào để tôi vẽ với phông chữ này? Làm thế nào để tôi có được tên của nó? Làm cách nào để ngăn chặn các vấn đề sao chép mà tôi đã khiếu nại ở trên? Làm thế nào để tôi thiết lập màu sắc của nó? Kerning? Hỗ trợ Unicode thì sao?

Trình kết xuất: Chung

Vì vậy, tôi nhận thấy bạn có một vài hàm blit * () và cả hàm render (). Điều này dường như ngụ ý rằng bạn muốn người dùng có thể xếp hàng một loạt các thao tác làm mờ, và sau đó xóa tất cả chúng để sàng lọc cùng một lúc bằng cách sử dụng lệnh gọi render ().

Tốt rồi; thực tế, đó là cách mà công nghệ động cơ của nhóm tôi cũng xử lý nó. :)

Việc sử dụng một singleton ở đây là chấp nhận được, chủ yếu là vì bạn dường như muốn để cho trình kết xuất có toàn quyền kiểm soát bản vẽ. Nếu chỉ có một ví dụ của nó (như có lẽ nên có), điều này sẽ không làm tổn thương gì. Không phải những gì chúng ta làm, nhưng hey, đó là một vấn đề của hương vị.

Có một vài vấn đề lớn tôi thấy ở đây.

Trình kết xuất: Biến đổi

Bạn dường như chỉ làm việc trong 2D. Tốt rồi. NHƯNG...

Làm thế nào để bạn xử lý những thứ như xoay một hình ảnh khi bạn vẽ nó? Thu nhỏ nó? Bạn cần hỗ trợ đầy đủ cho một cái gì đó gọi là biến đổi affine . Điều này cho phép bạn dễ dàng xoay, chia tỷ lệ, dịch, nghiêng và nếu không thì xáo trộn trong một hình ảnh thời trang đẹp.

Điều này cần phải được hỗ trợ (bằng cách nào đó) cho cả văn bản và hình ảnh.

Trình kết xuất: Tô màu và Trộn

Tôi muốn có thể pha trộn màu sắc vào hình ảnh của mình và đặt màu cho văn bản của mình. Bạn nên phơi bày điều này.

Tôi cũng muốn có thể làm những việc như pha trộn hình ảnh khi mờ, vì vậy tôi có thể làm những việc như ma mờ hoặc khói hoặc lửa.

Làm thế nào để tự cứu mình khỏi rắc rối

Sử dụng SFML . Nó có thiết kế tốt hơn cho, tốt, khá nhiều thứ so với SDL. Họ đã thực hiện những gì bạn đang cố gắng làm ở đây. Ít nhất, hãy nhìn vào cách họ đã chỉ định giao diện của họ và cách họ đã thiết kế hệ thống phân cấp lớp của họ.

Cũng lưu ý rằng họ giải quyết vấn đề về các phép biến đổi mà tôi đã chỉ ra trước đó trong lớp Drawable của họ. Và tô màu. Và pha trộn.

Họ đã có các hướng dẫn và tài liệu tốt, vì vậy có thể đáng để bạn dành thời gian tìm hiểu về nó một chút và cảm nhận về những gì mã của bạn có thể thực hiện được.

Chúc may mắn!


1
Có những quyết định phong cách khác mà bạn đã được thực hiện đối với các phương thức (load (), void *, sử dụng singletons, v.v.), thật lòng mà nói, hoàn toàn bị lu mờ bởi sự thiếu chức năng mà giao diện đề xuất của bạn lộ ra. Bạn không nên lãng phí thời gian để lo lắng về việc phát triển phần mềm mà bạn nên sử dụng khi kỹ thuật phần mềm của bạn ở đây quá tệ, nó làm cho mắt tôi bị chảy máu.
ChrisE

1
Bạn đang có một khởi đầu tốt, bạn chỉ cần thêm một số kinh nghiệm để bạn có thể học cách phát hiện ra chức năng nào bạn đang thiếu. Hãy xem SFML, tạo một số ứng dụng nhỏ thú vị và sau đó quay lại vấn đề này khi bạn có ý tưởng rõ ràng hơn về những khả năng bạn cần thể hiện. Đừng lo lắng hệ thống ống nước xấu xí như thế nào cho đến khi bạn có mọi thứ bạn cần nếu không.
ChrisE

1
Giao diện: Có, nó là một giao diện. Đó là loại giao diện API, không phải loại giao diện lớp ảo. Con trỏ trống: Đã được sắp xếp. Xem ý kiến ​​của tôi về câu trả lời của jv42. | Sao chép hình ảnh: Bạn thực hiện giả định đó dựa trên những gì? | Thao tác: Rất nhiều chi tiết về các lớp tôi chưa thực hiện hoặc tôi chỉ coi là một sự phân tâm cho câu hỏi. Ngoài ra, bạn dường như đang yêu cầu những thứ như xoay hình ảnh, lấy tên của phông chữ, v.v. Tôi sẽ thực hiện những điều đó khi và nếu có nhu cầu, không phải bây giờ. [tiếp tục]
Paul Manta

1
[cont.] SFML: Thật vậy, tôi mới phát hiện ra thư viện này và tôi thích nó hơn rất nhiều. | Điểm mấu chốt: Những gì tôi đã trình bày ở đây chỉ là cốt lõi (tải và kết xuất). Các chi tiết khác tôi coi là không quan trọng để hiển thị. Ngoài ra, tôi sẽ không bắt đầu triển khai bất kỳ tính năng nào (xoay, xiên, chia tỷ lệ) trừ khi sau đó tôi thấy tôi cần chúng. Nó sẽ dễ dàng để làm điều đó anyway.
Paul Manta

1
+1 cho SFML. Nó sẽ loại bỏ hầu hết các công cụ giao diện này với API của nó. : P
Vịt Cộng sản

0

Tôi hơi bận tâm về phương pháp "tải", phá vỡ Nguyên tắc có trách nhiệm đơn lẻ (nhân tiện, với Vịt Cộng sản, tôi đoán đây là lý do tại sao anh ta sử dụng const char *, bởi vì chức năng tải hình ảnh SDL, được mã hóa trong C, không lấy chuỗi std ::. Nó không phải là một công việc của lớp Image để tự tải, vì ít nhất hai lý do:

  • Nếu bạn có ý định tối ưu hóa việc sử dụng bộ nhớ, bạn sẽ muốn có các lớp chuyên biệt để tải tài nguyên.
  • Bạn sẽ xử lý các trường hợp ngoại lệ dễ dàng hơn với một lớp dành riêng.

Tải: Không, nó phải là một công việc của lớp Image để tự tải, giống như cách std :: ifflow là công việc tự tải. Đây không phải là nơi để thực hiện quản lý mem: nó sẽ được thực hiện trong một lớp ResourceManager riêng. | Chuỗi: Không khó std::stringđể chuyển đổi char*, vì vậy đó không phải là lý do. Lý do đơn giản là std::stringsẽ không cung cấp lợi thế.
Paul Manta

-1

Singleton = BAD, loại bỏ ngay lập tức. Phông chữ và hình ảnh không nên có free()chức năng, đó sẽ là công việc của hàm hủy. Không GraphicsRenderernên cung cấp các Blitchức năng đó, bạn nên cung cấp các lớp hướng đối tượng sẽ cung cấp vị trí cho từng kết quả cuối cùng và kết xuất thực tế được GraphicsRenderer quản lý tự động. Cuối cùng, để đóng gói, sử dụng tính kế thừa, không phải PIMPL và chắc chắn không sử dụng khoảng trống *, sử dụng một con trỏ mờ được gõ mạnh.

Dưới đây là một số trích đoạn từ thiết kế của riêng tôi, mặc dù tôi đã sử dụng chuyển đổi thời gian biên dịch và không kế thừa thời gian chạy.

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();
};

Ở đây, bộ nhớ được quản lý cho bạn - tất cả quyền sở hữu được quản lý thông qua trỏ thông minh và giao diện hoàn toàn hướng đối tượng.


2
Singleton: Singletons không tệ nếu chúng được sử dụng đúng. Về cơ bản, trình kết xuất không có bất kỳ trạng thái nào để nói về việc singleton vẫn ổn ở đây (mặc dù nó phức tạp hơn thế). | Miễn phí: Deallocation được xử lý trong destructor. Các free()chức năng là có vì lý do tương tự các ifstream::close()chức năng tồn tại. | Blit: Tôi đang cố tách dữ liệu khỏi cơ chế sử dụng dữ liệu. | Sprites: Đây chỉ là mô-đun đồ họa. Sprites sẽ được xử lý ở các cấp độ cao hơn của động cơ, theo cách tương tự như những gì bạn mô tả. [tiếp tục]
Paul Manta

[cont.] Thừa kế: Tôi không hiểu làm thế nào tôi có thể sử dụng thừa kế ở đây. | Con trỏ trống rỗng: Tại sao nó không nên được sử dụng? Tôi nghĩ rằng đó là một cách tốt để giới hạn số lượng tệp có quyền truy cập vào thư viện của bên thứ 3. data_chỉ là một SDL_Surface*diễn viên để void*. Bất cứ khi nào tôi sử dụng nó, tôi quay trở lại SDL_Surface*. | Blit [contd.]: Một cách nghĩ khác về điều này là, hình ảnh không tự vẽ & mdash; trình kết xuất vẽ nó. Hình ảnh đơn giản là tồn tại .
Paul Manta

Cảm ơn đã chỉ cho tôi std::shared_ptr, mặc dù. Cảm ơn chắc chắn sẽ có ích.
Paul Manta

1
Miễn phí : tùy thuộc vào hệ thống, phá hủy đối tượng và các tài nguyên liên quan của nó (chẳng hạn như một kết cấu) có thể cần phải xảy ra trong các luồng khác nhau, do đó cần một free()hàm; Ngoài ra, người ta có thể đang sử dụng một sơ đồ phân bổ tùy chỉnh không gọi hàm hủy.
sam hocevar

1
@DeadMG Tôi cũng bối rối về cách cung cấp đóng gói với thừa kế. Tôi luôn luôn tìm ra cách tốt nhất để che giấu các thành viên tư nhân sẽ gắn bó với con trỏ mờ được gõ mạnh. Bạn có thể giải thích về đề nghị thừa kế của bạn?
michael.bartnett

-1

Điều đầu tiên tôi có thể nhận ra:

  • Singletons rất rất xấu, thường. Đó không phải là 'Hừm, tôi chỉ muốn một trong số này thôi à?' nhưng nhiều hơn 'Hừm, nhiều hơn một trong số này sẽ phá vỡ chương trình chứ?'.
  • void * con trỏ cũng khá rủi ro. Tại sao bạn lại cần có một cái? Thiết kế xấu ở đâu đó.
  • FontImagedường như có liên quan chặt chẽ. Có lẽ bạn có thể mang một số chức năng lên hierachy đến mộtRenderable .
  • Tôi nghĩ rằng đây là tôi, nhưng bất kỳ lý do tại sao bạn đang sử dụng const char*hơn std::string?

Điểm tốt trên const char*, tôi đã không nhận thấy rằng.
DeadMG

Con trỏ trống rỗng: Tôi không cần nó. Đó là một cách để tôi giới hạn số lượng tệp bao gồm SDL.h( void* data_chỉ là một SDL_Surface*vai trò void*). | Chuỗi: Tôi có bất kỳ lý do để không sử dụng chúng? Tôi không làm bất kỳ thao tác nào, tôi chỉ cần một chuỗi. Tôi được tự do sử dụng std::stringbên ngoài các lớp học này, nếu tôi rất mong muốn. | Singletons: Có, nhiều GR sẽ phá mã của tôi. GR khởi tạo SDL. Có một singleton tôi không phải lo lắng khi SDL được khởi tạo hoặc thoát.
Paul Manta

Ưu điểm của ít tệp hơn bao gồm SDL.hkhá nhiều. Bằng cách nào đó, tôi sẽ không có trình kết xuất nội bộ SDL. Tôi sẽ trừu tượng điều đó với động cơ chính, hoặc một cái gì đó khác. Điều gì nếu bạn muốn các phần khác của SDL, ngoại trừ đồ họa?
Vịt Cộng sản

Các thành phần khác: Tôi sẽ khởi tạo chúng một cách riêng biệt, trong các thành phần cần các bộ phận đó. | Khởi tạo: Công cụ chính (GameManager, thường) không nên quan tâm đến các chi tiết như thư viện tôi đang sử dụng. Trong thiết lập hiện tại của tôi, tôi không phải lo lắng về việc khởi tạo một bit, mọi thứ sẽ tự động xảy ra (và được đảm bảo xảy ra chính xác khi tôi cần nó xảy ra, không muộn hơn, không sớm hơn). | Tiêu đề: Có, không có lợi thế kỹ thuật thực sự. Tuy nhiên, tôi thấy mã trở nên gọn gàng hơn khi thư viện bên dưới không tiếp xúc với tôi trong giao diện lớp.
Paul Manta

1
Thêm một điều mà những điểm duy nhất tôi có thể thấy sai với thiết kế mà bạn có lý do. Tôi vẫn đứng bên cạnh rằng các singletons không cần thiết ở đây và các con trỏ void nên được thay thế bằng các SDL_Surface. : P
Vịt Cộng sản
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.