Các diễn viên trong một trò chơi có nên chịu trách nhiệm tự vẽ?


41

Tôi rất mới để phát triển trò chơi, nhưng không lập trình.

Tôi (một lần nữa) chơi xung quanh với một trò chơi loại Pông bằng canvasphần tử JavaScript .

Tôi đã tạo một Paddleđối tượng có các thuộc tính sau ...

  • width
  • height
  • x
  • y
  • colour

Tôi cũng có một Pongđối tượng có các thuộc tính như ...

  • width
  • height
  • backgroundColour
  • draw().

Các draw()phương pháp hiện đang thiết lập lại canvasvà đó là nơi mà một câu hỏi nảy ra.

Nên các Paddleđối tượng có một draw()phương pháp có trách nhiệm đối với bản vẽ của nó, hoặc nên draw()các Pongđối tượng chịu trách nhiệm cho việc vẽ các diễn viên của mình (tôi cho rằng là một thuật ngữ chính xác, hãy sửa lại cho tôi nếu tôi không chính xác).

Tôi hình dung rằng sẽ rất thuận lợi cho việc Paddletự vẽ, khi tôi khởi tạo hai đối tượng PlayerEnemy. Nếu nó không được trong Pong's draw(), tôi cần phải viết mã tương tự hai lần.

Thực hành tốt nhất ở đây là gì?

Cảm ơn.


Câu trả lời:


49

Có diễn viên tự vẽ không phải là một thiết kế tốt, vì hai lý do chính:

1) nó vi phạm nguyên tắc trách nhiệm duy nhất , vì những diễn viên đó có lẽ phải làm một công việc khác trước khi bạn đẩy mã kết xuất vào họ.

2) nó làm cho việc mở rộng trở nên khó khăn; nếu mỗi loại diễn viên thực hiện bản vẽ của riêng mình và bạn cần thay đổi cách bạn vẽ nói chung , bạn có thể phải sửa đổi rất nhiều mã. Tránh lạm dụng thừa kế có thể làm giảm bớt điều này đến một mức độ nào đó, nhưng không hoàn toàn.

Nó tốt hơn cho trình kết xuất của bạn để xử lý bản vẽ. Rốt cuộc, đó là những gì nó có nghĩa là một trình kết xuất. Phương thức vẽ của trình kết xuất sẽ lấy một đối tượng "mô tả kết xuất", chứa mọi thứ bạn cần để kết xuất một vật. Tài liệu tham khảo (có thể được chia sẻ) dữ liệu hình học, các phép biến đổi cụ thể hoặc các thuộc tính vật liệu như màu sắc, et cetera. Sau đó, nó rút ra điều đó, và không quan tâm mô tả kết xuất được cho là "được".

Diễn viên của bạn sau đó có thể giữ một mô tả kết xuất mà họ tự tạo. Vì các tác nhân thường là các kiểu xử lý logic, nên chúng có thể đẩy các thay đổi trạng thái sang mô tả kết xuất khi cần - ví dụ: khi diễn viên bị hỏng, nó có thể đặt màu của mô tả kết xuất thành màu đỏ để biểu thị điều này.

Sau đó, bạn có thể chỉ cần lặp lại mọi diễn viên có thể nhìn thấy, đưa ra các mô tả kết xuất của họ vào trình kết xuất và để nó thực hiện công việc của nó (về cơ bản; bạn có thể khái quát hóa điều này hơn nữa).


14
+1 cho "mã bản vẽ kết xuất", nhưng -1 cho nguyên tắc trách nhiệm duy nhất, điều này đã làm cho các lập trình viên bị tổn hại nhiều hơn là tốt . Đó là ý tưởng đúng (tách các mối quan tâm) , nhưng cách nó được nêu ("mỗi lớp chỉ nên có một trách nhiệm và / hoặc chỉ một lý do để thay đổi") đơn giản là sai .
BlueRaja - Daniel Pflughoeft

2
-1 cho 1), như BlueRaja đã chỉ ra, nguyên tắc này là duy tâm, nhưng không thực tế. -1 cho 2) vì nó không làm cho việc mở rộng trở nên khó khăn hơn đáng kể. Nếu bạn phải thay đổi toàn bộ công cụ kết xuất của mình, bạn sẽ có nhiều mã để thay đổi hơn so với những gì bạn có trong mã diễn viên của mình. Tôi sẽ thích actor.draw(renderer,x,y)hơn nhiều renderer.draw(actor.getAttribs(),x,y).
corsiKa

1
Câu trả lời tốt! Chà "Bạn sẽ không vi phạm nguyên tắc trách nhiệm duy nhất" không nằm trong danh mục của tôi, nhưng vẫn là một nguyên tắc tốt để tính đến
FxIII

@glowcoder Không phải là vấn đề, bạn có thể duy trì Act.draw (), được định nghĩa là {renderer.draw (lưới, attribs, biến đổi); }. Trong OpenGL, tôi duy trì struct RenderDetail { glVAO* vao; int shader; int texture; Matrix4f* transform; }ít nhiều cho trình kết xuất của mình. Bằng cách đó, trình kết xuất không quan tâm dữ liệu đại diện cho cái gì, nó chỉ xử lý nó. Dựa trên thông tin này, sau đó tôi cũng có thể quyết định có sắp xếp thứ tự các cuộc gọi rút thăm để giảm các cuộc gọi GPU tổng thể hay không.
giảm tốc

16

Mẫu khách có thể hữu ích ở đây.

Những gì bạn có thể làm là có một giao diện Trình kết xuất biết cách vẽ từng đối tượng và phương thức "tự vẽ" trong mỗi tác nhân xác định phương thức trình kết xuất (cụ thể) nào sẽ gọi, ví dụ:

interface Renderer {
    void drawPaddle(Player owner, Rectangle position);
    // Note: the Renderer chooses the Color based on which player the paddle belongs to

    // Also drawBackground, drawBall etc.
}

interface Actor {
    void draw(Renderer renderer);
}

class Paddle implements Actor {
    void draw(Renderer renderer) {
        renderer.drawPaddle(this.owner, this.getBounds());
    }
}

Bằng cách này, vẫn dễ dàng chuyển sang thư viện hoặc khung công tác đồ họa khác (tôi đã từng chuyển một trò chơi từ Swing / Java2D sang LWJGL và rất vui vì tôi đã sử dụng mô hình này thay vì đi Graphics2Dvòng quanh.)

Có một lợi thế khác: Trình kết xuất có thể được kiểm tra riêng với bất kỳ mã tác nhân nào


2

Trong ví dụ của bạn, bạn muốn có các đối tượng Pong thực hiện draw () và tự hiển thị.

Mặc dù bạn sẽ không nhận thấy bất kỳ lợi ích đáng kể nào trong một dự án có kích thước này, nhưng nói chung tách biệt logic trò chơi và biểu diễn trực quan (kết xuất) của chúng là một hoạt động đáng giá.

Điều này có nghĩa là bạn có các đối tượng trò chơi của bạn có thể được cập nhật () nhưng họ không biết chúng được hiển thị như thế nào, họ chỉ quan tâm đến mô phỏng.

Sau đó, bạn sẽ có một lớp PongRenderer () có tham chiếu đến một đối tượng Pong và sau đó nó chịu trách nhiệm hiển thị lớp Pong (), điều này có thể liên quan đến việc hiển thị Paddles hoặc có lớp PaddleRenderer để chăm sóc nó.

Sự tách biệt các mối quan tâm trong trường hợp này là một điều khá tự nhiên, có nghĩa là các lớp của bạn có thể ít phình to hơn và dễ dàng sửa đổi cách mọi thứ được hiển thị hơn, nó không còn phải tuân theo hệ thống phân cấp mà mô phỏng của bạn thực hiện.


-1

Nếu bạn đã làm điều đó trong Pongdraw (), bạn sẽ không cần phải sao chép mã - chỉ cần gọi hàm Paddledraw () cho mỗi paddles của bạn. Vì vậy, trong bản Pongvẽ của bạn () bạn có

humanPaddle.draw();
compPaddle.draw();

-1

Mỗi đối tượng nên chứa hàm vẽ riêng của nó sẽ được gọi bởi mã cập nhật trò chơi hoặc mã vẽ. Như

class X
{
  public void Draw( )
  {
     // Do something 
  } 
}

class Y
{
  public void Draw( )
   { // Do Something 
   } 
}

class Game
{
  X objX;
  Y objY;

  @Override
  public void OnDraw( )
  {
      objX.Draw( );
      objY.Draw( ); 
  } 
}

Đây không phải là một mã mà chỉ để hiển thị, làm thế nào để vẽ các đối tượng.


2
Đây không phải là "Làm thế nào để vẽ" mà là một cách duy nhất để làm điều đó. Như đã đề cập trong các câu trả lời trước, phương pháp này có một số sai sót và một số lợi ích, trong khi các mô hình khác có lợi ích và tính linh hoạt đáng kể.
Troyseph

Tôi đánh giá cao nhưng vì một ví dụ đơn giản là được đưa ra các cách vẽ diễn viên khác nhau trong cảnh, tôi đã đề cập đến cách này.
dùng43609
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.