Tách bản vẽ và logic trong trò chơi


11

Tôi là một nhà phát triển vừa mới bắt đầu loay hoay với việc phát triển trò chơi. Tôi là một người .Net, vì vậy tôi đã nhắn tin với XNA và hiện đang chơi xung quanh với Cocos2d cho iPhone. Câu hỏi của tôi thực sự là tổng quát hơn mặc dù.

Giả sử tôi đang xây dựng một trò chơi đơn giản. Tôi có một Balllớp học và một Paddlelớp học. Xuất phát từ sự phát triển của thế giới kinh doanh, bản năng đầu tiên của tôi là không có bất kỳ mã xử lý bản vẽ hoặc đầu vào nào trong một trong các lớp này.

//pseudo code
class Ball
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Move(){}
}

Không có gì trong lớp bóng xử lý đầu vào, hoặc giao dịch với bản vẽ. Sau đó tôi sẽ có một lớp khác, lớp của tôi Gamehoặc của tôi Scene.m(trong Cocos2d) sẽ tạo ra Quả bóng mới và trong vòng lặp trò chơi, nó sẽ điều khiển quả bóng khi cần.

Mặc dù vậy, trong nhiều hướng dẫn cho cả XNA và Cocos2d, tôi thấy một mô hình như thế này:

//pseudo code
class Ball : SomeUpdatableComponent
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Update(){}
   void Draw(){}
   void HandleInput(){}
}

Câu hỏi của tôi là, điều này có đúng không? Đây có phải là mô hình mà mọi người sử dụng trong phát triển trò chơi? Nó bằng cách nào đó đi ngược lại mọi thứ tôi đã từng làm, để lớp Ball của tôi làm mọi thứ. Hơn nữa, trong ví dụ thứ hai này, nơi tôi Ballbiết cách di chuyển, tôi sẽ xử lý phát hiện va chạm bằng cách Paddlenào? Sự Ballcần thiết phải có kiến ​​thức về Paddle? Trong ví dụ đầu tiên của tôi, Gamelớp sẽ có các tham chiếu đến cả BallPaddlevà sau đó gửi cả hai cho người CollisionDetectionquản lý hoặc một cái gì đó, nhưng làm thế nào để tôi xử lý sự phức tạp của các thành phần khác nhau, nếu mỗi thành phần riêng lẻ tự làm mọi thứ? (Tôi hy vọng tôi có ý nghĩa .....)


2
Thật không may, đó thực sự là cách nó được thực hiện trong nhiều trò chơi. Tôi sẽ không coi đó là thực hành tốt nhất mặc dù. Nhiều hướng dẫn bạn đang đọc có thể nhắm đến người mới bắt đầu và vì vậy họ sẽ không bắt đầu giải thích mô hình / chế độ xem / bộ điều khiển hoặc mô hình ách cho người đọc.
tenpn

@tenpn, bình luận của bạn dường như gợi ý rằng bạn không nghĩ đây là cách thực hành tốt, tôi tự hỏi liệu bạn có thể giải thích thêm không? Tôi đã luôn nghĩ rằng đó là sự sắp xếp phù hợp nhất do các phương thức có chứa mã có khả năng rất cụ thể cho từng loại; nhưng bản thân tôi có ít kinh nghiệm nên tôi rất thích nghe suy nghĩ của bạn.
sebf

1
@sebf nó không hoạt động nếu bạn thiết kế hướng dữ liệu và nó không hoạt động nếu bạn thích thiết kế hướng đối tượng. Xem các hoàng tử
tenpn

@tenpn. Bài viết và tài liệu được liên kết trong đó rất thú vị, cảm ơn bạn!
sebf

Câu trả lời:


10

Câu hỏi của tôi là, điều này có đúng không? Đây có phải là mô hình mà mọi người sử dụng trong phát triển trò chơi?

Các nhà phát triển trò chơi có xu hướng sử dụng bất cứ thứ gì hoạt động. Nó không nghiêm ngặt, nhưng hey, nó tàu.

Nó bằng cách nào đó đi ngược lại mọi thứ tôi đã từng làm, để lớp Ball của tôi làm mọi thứ.

Vì vậy, một thành ngữ tổng quát hơn cho những gì bạn có đó là handleMessages / update / draw. Trong các hệ thống sử dụng nhiều thông điệp (có những ưu và nhược điểm như bạn mong đợi), một thực thể trò chơi sẽ lấy bất kỳ thông điệp nào nó quan tâm, thực hiện logic trên các tin nhắn đó và sau đó tự rút ra.

Lưu ý rằng lệnh draw () có thể không thực sự có nghĩa là thực thể gọi putPixel hoặc bất cứ thứ gì bên trong chính nó; trong một số trường hợp, điều đó chỉ có nghĩa là cập nhật dữ liệu mà sau đó được thăm dò bởi mã chịu trách nhiệm vẽ. Trong các trường hợp nâng cao hơn, nó "tự vẽ" bằng cách gọi các phương thức được hiển thị bởi trình kết xuất. Trong công nghệ tôi đang làm việc ngay bây giờ, đối tượng sẽ tự vẽ bằng các lệnh gọi của trình kết xuất và sau đó mọi khung hình, luồng kết xuất sẽ tổ chức tất cả các cuộc gọi được thực hiện bởi tất cả các đối tượng, tối ưu hóa cho những thứ như thay đổi trạng thái và sau đó rút ra toàn bộ (tất cả điều này xảy ra trên một luồng tách biệt với logic trò chơi, vì vậy chúng tôi có được kết xuất không đồng bộ).

Việc ủy ​​thác trách nhiệm thuộc về lập trình viên; đối với một trò chơi pong đơn giản, điều hợp lý là mỗi đối tượng xử lý bản vẽ của riêng mình (hoàn thành với các cuộc gọi cấp thấp) hoàn toàn. Đó là một vấn đề về phong cách và trí tuệ.

Hơn nữa, trong ví dụ thứ hai này, nơi Ball của tôi biết cách di chuyển, tôi sẽ xử lý phát hiện va chạm với Paddle như thế nào? Bóng sẽ cần phải có kiến ​​thức về Paddle?

Vì vậy, một cách tự nhiên để giải quyết vấn đề ở đây là để mọi đối tượng lấy mọi đối tượng khác làm một loại tham số để kiểm tra va chạm. Điều này quy mô kém, và có thể có kết quả kỳ lạ.

Yêu cầu mỗi lớp thực hiện một số loại giao diện Collidable và sau đó có một đoạn mã cấp cao chỉ lặp trên tất cả các đối tượng Collidable trong giai đoạn cập nhật trò chơi của bạn và xử lý các va chạm, thiết lập các vị trí đối tượng khi cần.

Một lần nữa, bạn chỉ cần phát triển ý thức (hoặc tạo nên suy nghĩ của bạn) về trách nhiệm đối với những thứ khác nhau trong trò chơi của bạn cần được ủy thác. Kết xuất là một hoạt động đơn độc và có thể được xử lý bởi một đối tượng trong chân không; va chạm và vật lý nói chung không thể.

Trong ví dụ đầu tiên của tôi, lớp Trò chơi sẽ có các tham chiếu đến cả Bóng và Mái chèo, sau đó chuyển cả hai thứ đó cho một người quản lý CollisionDetection hoặc một cái gì đó, nhưng làm thế nào để tôi xử lý sự phức tạp của các thành phần khác nhau, nếu mỗi thành phần riêng lẻ mọi thứ tự?

Xem ở trên để khám phá về điều này. Nếu bạn đang ở một ngôn ngữ hỗ trợ các giao diện dễ dàng (ví dụ: Java, C #), thì điều này thật dễ dàng. Nếu bạn đang ở trong C ++, điều này có thể ... thú vị. Tôi thích sử dụng nhắn tin để giải quyết các vấn đề này (các lớp xử lý các tin nhắn họ có thể, bỏ qua những người khác), một số người khác như thành phần thành phần.

Một lần nữa, bất cứ điều gì làm việc và là dễ dàng nhất cho bạn.


2
Ồ, và luôn luôn nhớ: YAGNI (Bạn sẽ không cần nó) thực sự quan trọng ở đây. Bạn có thể lãng phí rất nhiều thời gian để cố gắng đưa ra hệ thống linh hoạt lý tưởng để xử lý mọi vấn đề có thể xảy ra, và sau đó không bao giờ hoàn thành công việc. Ý tôi là, nếu bạn thực sự muốn có một xương sống nhiều người chơi tốt cho tất cả các kết quả có thể, bạn sẽ sử dụng CORBA, phải không? :)
ChrisE

5

Gần đây tôi đã thực hiện một trò chơi Space Invadors đơn giản bằng cách sử dụng 'hệ thống thực thể'. Đó là một mô hình phân tách các thuộc tính và hành vi cực kỳ tốt. Tôi đã mất vài lần lặp lại để hiểu đầy đủ về nó, nhưng một khi bạn nhận được một vài thành phần được thiết kế, nó sẽ trở nên cực kỳ đơn giản để soạn thảo các đối tượng mới bằng cách sử dụng các thành phần hiện có của bạn.

Bạn nên đọc nó:

http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/

Nó được cập nhật thường xuyên bởi một anh chàng cực kỳ hiểu biết. Đây cũng là cuộc thảo luận hệ thống thực thể duy nhất với các ví dụ mã cụ thể.

Lặp đi lặp lại của tôi như sau:

Lặp lại đầu tiên có một đối tượng "EntitySystem" giống như Adam mô tả; tuy nhiên các thành phần của tôi vẫn có các phương thức - thành phần 'có thể hiển thị' của tôi có phương thức paint () và thành phần vị trí của tôi có phương thức move () và v.v. Khi tôi bắt đầu xác định các thực thể tôi nhận ra rằng tôi cần bắt đầu chuyển tin nhắn giữa các thành phần và ra lệnh thực hiện các cập nhật thành phần .... cách quá lộn xộn.

Vì vậy, tôi đã quay lại và đọc lại blog của T-Machines. Có rất nhiều thông tin trong các luồng nhận xét - và trong đó, ông thực sự nhấn mạnh rằng các thành phần không có hành vi được cung cấp bởi các hệ thống thực thể. Theo cách này, bạn không cần chuyển thông điệp giữa các thành phần và cập nhật thành phần vì thứ tự được xác định theo thứ tự toàn cầu của hệ thống thực thi. Đồng ý. Có lẽ đó là quá trừu tượng.

Dù sao cho lần lặp # 2, đây là những gì tôi lượm lặt được từ blog:

EntityManager - hoạt động như "cơ sở dữ liệu" thành phần, có thể được truy vấn cho các thực thể có chứa một số loại thành phần nhất định. Điều này thậm chí có thể được hỗ trợ bởi cơ sở dữ liệu trong bộ nhớ để truy cập nhanh ... xem phần 5 của máy t để biết thêm thông tin.

EntitySystem - Mỗi hệ thống về cơ bản chỉ là một phương thức hoạt động trên một tập hợp các quyền. Mỗi hệ thống sẽ sử dụng thành phần x, y và z của một thực thể để hoàn thành công việc. Vì vậy, bạn sẽ truy vấn người quản lý cho các thực thể với các thành phần x, y và z sau đó chuyển kết quả đó cho hệ thống.

Thực thể - chỉ là một id, giống như một dài. Thực thể là nhóm các nhóm thành phần với nhau thành một 'thực thể'.

Thành phần - một tập hợp các lĩnh vực .... không có hành vi! Khi bạn bắt đầu thêm các hành vi, nó bắt đầu trở nên lộn xộn ... ngay cả trong một trò chơi Space Invadors đơn giản.

Chỉnh sửa : nhân tiện, 'dt' là thời gian delta kể từ lần gọi vòng lặp chính cuối cùng

Vì vậy, vòng lặp Invadors chính của tôi là:

Collection<Entity> entitiesWithGuns = manager.getEntitiesWith(Gun.class);
Collection<Entity> entitiesWithDamagable =
manager.getEntitiesWith(BulletDamagable.class);
Collection<Entity> entitiesWithInvadorDamagable = manager.getEntitiesWith(InvadorDamagable.class);

keyboardShipControllerSystem.update(entitiesWithGuns, dt);
touchInputSystem.update(entitiesWithGuns, dt);
Collection<Entity> entitiesWithInvadorMovement = manager.getEntitiesWith(InvadorMovement.class);
invadorMovementSystem.update(entitiesWithInvadorMovement);

Collection<Entity> entitiesWithVelocity = manager.getEntitiesWith(Velocity.class);
movementSystem.move(entitiesWithVelocity, dt);
gunSystem.update(entitiesWithGuns, System.currentTimeMillis());
Collection<Entity> entitiesWithPositionAndForm = manager.getEntitiesWith(Position.class, Form.class);
collisionSystem.checkCollisions(entitiesWithPositionAndForm);

Thoạt nhìn có vẻ hơi lạ, nhưng nó cực kỳ linh hoạt. Nó cũng rất dễ dàng để tối ưu hóa; đối với các loại thành phần khác nhau, bạn có thể có các kho dữ liệu sao lưu khác nhau để phục hồi nhanh hơn. Đối với lớp 'biểu mẫu', bạn có thể hỗ trợ lớp bốn để tăng tốc độ truy cập để phát hiện va chạm.

Tôi thích bạn; Tôi là một nhà phát triển dày dạn kinh nghiệm nhưng không có kinh nghiệm viết trò chơi. Tôi đã dành một chút thời gian nghiên cứu đưa ra các mẫu dev, và cái này lọt vào mắt tôi. Đây không phải là cách duy nhất để làm mọi thứ, nhưng tôi đã thấy nó rất trực quan và mạnh mẽ. Tôi tin rằng mô hình đã được thảo luận chính thức trong cuốn 6 của loạt bài "Đá quý lập trình trò chơi" - http://www.amazon.com/Game-Programming-Gems/dp/1584500492 . Bản thân tôi chưa đọc bất kỳ cuốn sách nào nhưng tôi nghe chúng là tài liệu tham khảo thực tế cho lập trình trò chơi.


1

Không có quy tắc rõ ràng nào bạn phải tuân theo khi lập trình trò chơi. Tôi thấy cả hai mẫu hoàn toàn chấp nhận được, miễn là bạn theo cùng một mẫu thiết kế trên tất cả trò chơi của bạn. Bạn sẽ ngạc nhiên khi có nhiều mẫu thiết kế hơn cho các trò chơi, một vài trong số đó thậm chí không nằm trên OOP.

Bây giờ, theo sở thích cá nhân của tôi, tôi thích tách mã theo hành vi (một lớp / luồng để vẽ, một lớp khác để cập nhật), trong khi có các đối tượng tối giản. Tôi thấy việc song song hóa dễ dàng hơn và tôi thường thấy mình làm việc với ít tệp hơn cùng một lúc. Tôi muốn nói rằng đây là một cách làm thủ tục.

Nhưng nếu bạn đến từ thế giới kinh doanh, có lẽ bạn sẽ thoải mái hơn khi viết các lớp học biết cách tự làm mọi thứ cho mình.

Một lần nữa, tôi khuyên bạn nên viết những gì bạn cảm thấy tự nhiên nhất đối với bạn.


Tôi nghĩ rằng bạn đã hiểu nhầm câu hỏi của tôi. Tôi đang nói rằng tôi không thích có những lớp học tự làm mọi thứ. Tôi đã rơi như một quả bóng nên chỉ là một đại diện hợp lý của một quả bóng, tuy nhiên nó không biết gì về vòng lặp trò chơi, xử lý đầu vào, v.v ...
BFree 17/211

Công bằng mà nói, trong lập trình kinh doanh nếu bạn đun sôi nó, có hai đoàn tàu: các đối tượng kinh doanh tải, duy trì và thực hiện logic trên chính chúng, và DTO + AppLogic + Lớp lưu trữ / xác nhận ...
Nate

1

đối tượng 'Ball' có thuộc tính & hành vi. Tôi thấy thật hợp lý khi đưa các thuộc tính & hành vi độc đáo của đối tượng vào lớp riêng của họ. Cách cập nhật đối tượng Ball khác với cách một mái chèo sẽ cập nhật nên có ý nghĩa rằng đây là những hành vi độc đáo đảm bảo được đưa vào lớp. Tương tự với bản vẽ. Thường có sự khác biệt duy nhất trong cách vẽ mái chèo so với Bóng và tôi thấy việc sửa đổi phương thức vẽ của một đối tượng riêng lẻ phù hợp với những nhu cầu này dễ dàng hơn so với việc đánh giá điều kiện trong một lớp khác để giải quyết chúng.

Pong là một trò chơi tương đối đơn giản về số lượng đối tượng và hành vi, nhưng khi bạn tham gia vào hàng chục hoặc hàng trăm đối tượng trò chơi độc đáo, bạn có thể thấy ví dụ thứ 2 của mình dễ dàng hơn một chút để mô đun hóa mọi thứ.

Hầu hết mọi người đều sử dụng một lớp đầu vào có kết quả khả dụng cho tất cả các đối tượng trò chơi thông qua Dịch vụ hoặc lớp tĩnh.


0

Tôi nghĩ những gì bạn có thể sử dụng trong thế giới kinh doanh là tách biệt sự trừu tượng và thực hiện.

Trong thế giới dev game, bạn thường muốn nghĩ về tốc độ. Càng nhiều đối tượng bạn có càng nhiều chuyển đổi ngữ cảnh sẽ xảy ra có thể làm mọi thứ chậm lại tùy thuộc vào tải hiện tại.

Đây chỉ là suy đoán của tôi vì tôi là nhà phát triển trò chơi Wannabe nhưng là nhà phát triển chuyên nghiệp.


0

Không, đó không phải là "đúng" hay "sai", đó chỉ là một sự đơn giản hóa.

Một số mã doanh nghiệp hoàn toàn giống nhau, trừ khi bạn làm việc với một hệ thống buộc tách biệt phần trình bày và logic.

Ở đây, một ví dụ VB.NET, trong đó "logic nghiệp vụ" (thêm số) được viết trong trình xử lý sự kiện GUI - http://www.homeandlearn.co.uk/net/nets1p12.html

Làm thế nào để tôi đối phó với sự phức tạp của các thành phần khác nhau, nếu mỗi thành phần riêng lẻ tự làm mọi thứ?

Nếu và khi nó trở nên phức tạp, thì bạn có thể tính đến nó.

class Ball
{
    GraphicalModel ballVisual;
    void Draw()
    {
        ballVisual.Draw();
    }
};

0

Từ kinh nghiệm phát triển của tôi, không có cách nào đúng hay sai để làm mọi việc, trừ khi có một thực tiễn được chấp nhận trong nhóm bạn làm việc cùng. Tôi đã gặp phải sự phản đối của các nhà phát triển, những người không thích tách biệt các hành vi, giao diện, v.v. Và tôi chắc chắn họ sẽ nói như vậy về việc tôi đặt trước một lớp học bao gồm mọi thứ dưới ánh mặt trời. Hãy nhớ rằng bạn không chỉ viết nó, mà còn có thể đọc mã của bạn vào ngày mai và vào một ngày trong tương lai, gỡ lỗi nó và có thể xây dựng nó trong lần lặp tiếp theo. Bạn nên chọn một con đường phù hợp với bạn (và nhóm). Chọn một tuyến đường mà người khác sử dụng (và phản trực giác với bạn) sẽ làm bạn chậm lại.

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.