Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Game Engine Development
Gamedevelopment

Cách triển khai và sử dụng Message Queue trong game của bạn

by
Difficulty:IntermediateLength:LongLanguages:

Vietnamese (Tiếng Việt) translation by Andrea Ho (you can also view the original English article)

Game thường do một số thực thể khác biệt tạo thành, chúng tương tác với nhau. Những tương tác này rất sôi nổi và có kết nối rõ rệt đến gameplay (cách chơi). Hướng dẫn này nói đến các khai niệm và sự triển khai của hệ thống message queue, để đồng hoá các tương tác của thực thể, giúp code của bạn có thể quản lý và dễ bảo trì khi nó tăng trưởng phức tạp.

Giới thiệu

Một quả bom có thể tương tác với một nhân vật bằng cách gây nổ hoặc gây thương tổn, hộp y tế có thể cứu chữa một thực thể, chìa khoá dùng mở cánh cửa, và cứ vậy. Tương tác trong game là bất tận, nhưng làm sao ta có thể duy trì khả năng quản lý code trong khi vẫn có thể xử lý những tương tác đó? Làm sao ta chắc được code có thể thay đổi và vẫn hoạt động khi có nhiều tương tác mới và không mong đợi nảy sinh?

Interactions in a game tend to grow in complexity very quickly
Tương tác trong game có xu hướng tăng trưởng phức tạp nhanh chóng.

Khi tương tác được bổ sung (đặc biệt là những tương tác không mong muốn), thì code của bạn là càng lúc càng lộn xộn. Một triển khai ngờ nghệch sẽ nhanh chóng khiến bạn đặt ra những câu hỏi như:

"Đây là thực thể A, vậy tôi nên gọi phương thức damage() cho nó, đúng không?" Hoặc nó là damageByItem() phải không? Có lẽ là damageByWeapon() mới đúng?

Hình dung xem mớ hỗn độn dàn trải khắp tất cả thực thể game của bạn, bởi vì tất cả đều tương tác với nhau theo cách riêng biệt. Thật may, có một phương pháp quản lý tốt hơn, đơn giản hơn để thực hiện việc này.

Message Queue

Xem xét message queue. Ý tưởng cơ bản đăng sau khái niệm này là để triển khai tất cả tương tác game như một hệ thống giao tiếp (vẫn được dùng đến hôm nay): message exchange (trao đổi thông điệp). Mọi người đã giao tiếp thông qua message (các ký tự) nhiều thế kỷ vì đó là một hệ thống hữu hiệu và đơn giản.

Trong dịch vụ bưu điện thực tế, nội dung mỗi message có thể khác biệt, nhưng cách gửi và nhận chúng đều như nhau. Người gửi đưa thông tin vào trong bao thư và đưa nó đến địa chỉ đích đến. Nơi đến có thể có (hoặc không) phản hồi theo cùng một cơ chế, chỉ bằng việc thay đổi mục "from/to" trên bao thư.

Interactions made using a message queue system
Tương tác được xây dựng nhờ vào hệ thống message queue.

Áp dụng khái niệm này vào game, tất cả tương tác giữa các thực thể có thể xem như là message. Nếu thực thể game muốn tương tác với thực thể khác (hoặc một nhóm thực thể), thì tất cả thực thể đều phải gửi đi một message. Điểm tiếp nhận sẽ xử lý hoặc phản ứng với message dựa trên nội dung của nó và người gửi là ai.

Trong phương pháp này, giao tiếp giữa các thực thể game trở nên thống nhất. Tất cả thực thể có thể gửi và nhận message. Dù tương tác hoặc message có phức tạp thế nào, kênh giao tiếp vẫn hoạt động như nhau.

Xuyên suốt phần tiếp theo, tôi sẽ mô tả cách bạn có thể thực sự triển khai phương pháp message queue trong game của mình.

Thiết kế một Envelope (Message)

Hãy bắt đầu bằng cách thiết kế một envelope (phong bì), là yếu tố cơ bản nhất trong hệ thống message queue.

Envelope có thể được miêu tả như hình bên dưới:

Structure of a message
Cấu trúc của một message.

Hai field đầu tiên (senderdestination) là tham chiếu đến thực thể tạo ra và thực thể sẽ nhận message này, theo thứ tự. Sử dung những field này, cả sender (người gửi) và receiver (người nhận) có thể xác định message đi đến đâu và từ đâu đến.

Hai field khác (typedata) hoạt động cùng nhau để bảo đảm message được xử lý đúng. Field type miêu tả message này là gì, ví dụ nếu type là "damage", receiver sẽ xử lý message như một mệnh lện để giảm điểm sức khoẻ xuống; nếu type là "purse", receiver sẽ xem đó là một hướng dẫn để theo đuổi điều gì đó - và cứ thế.

Field data trực tiếp liên kết đến field type. Trong ví dụ trước đó, nếu type là "damage", thì field data sẽ gồm một con số - 10, mô tả lượng tổn thương sẽ áp dụng cho điểm sửc khoẻ của receiver. Nếu type là "purse", thì "data" sẽ gồm một đối tượng mô tả mục tiêu phải được đuổi theo.

Trường data có thể chứa thông tin bất kỳ, giúp envelope có ý nghĩa giao tiếp đa dạng. Bất kỳ điều gì có thể đặt vào field này, integer, float, string, và thậm chí object. Nguyên tắc quan trọng đầu tiên là receiver phải biết field data có gì dựa trên field type là gì.

Tất cả lý thuyết này có thể chuyển thành class đơn giản Message. Class này có 5 properties (thuộc tính), mỗi property cho từng field.

Ví dụ cho cách dùng là nếu thực thể A muốn gửi một message "damage" đến thực thể B, tất cả việc nó phải làm là khởi tạo một object của class Message, xét property to thành B, xét property from thành A, xét type thành "damage", và sau cùng xét "data" thành một con số (ví dụ như 10):

Giờ ta có cách để tạo ra message, giờ là lúc nghĩ đến class sẽ lưu và truyền tải nó.

Triển khai một queue

Class chịu trách nhiệm lưu và truyền tải các message sẽ gọi là MessageQueue. Nó hoạt động như một bưu điện, tất cả message được đưa về class này, nó bảo đảm các message được chuyển đến các điểm đích.

Hiện giờ, class MessageQueue sẽ có cấu trúc rất đơn giản:

Thuộc tính messages là array (mảng). Nó sẽ lưu tất cả message chuẩn bị được truyền đi bằng MessageQueue. Phương thức add() nhận object của class Message làm đối số, và bổ sung object đó vào danh sách của message.

Đây là cách thực A thông báo cho thực thể B về thương tổn sẽ được dùng trong class MessageQueue:

Giờ chúng ta có một cách để tạo ra và lưu message trong queue (hàng). Giờ là lúc truyền tải chúng đến các điểm nhận.

Truyền tải message

Để làm cho class MessageQueue thực sự gửi message, đầu tiên chúng ta cần xác định cách mà các thực thể sẽ xử lý và nhận message. Phương pháp dễ nhất là bổ sung vào môt phương thức onMessage() giúp mỗi thực thể có thể nhận được message:

Class MessageQueue sẽ gọi phương thức onMessage() của mỗi thực thể nhận message. Tham số truyền vào phương thức này là message được hệ thống message truyền đi (và được nhận ở điểm đến).

Class MessageQueue sẽ hướng message trong hàng của nó một lần, trong phương thức dispatch():

Phương thức này chạy qua tất cả message trong queue và mỗi message, field to được dùng để lấy một tham chiếu dến receiver. Phương thức onMessage() của receiver sau đó được gọi, với message hiện tại làm tham số, và message đã truyền đi sau đó được xoá ra khỏi danh sách MessageQueue. Quá trình này lặp lại cho đến khi tất cả message được truyền đi.

Sử dụng message queue

Đây là lúc nhìn lại tất cả chi tiết của triển khai này hoạt động cùng nhau. Hãy dùng hệ thống message queue trong một demo rất đơn giản gồm một vài thực thể đang di chuyển và tương tác với nhau. Để cho đơn giản, chúng ta sẽ có 3 thực thể: Healer, RunnerHunter.

Runner có thanh sức khoẻ và di chuyển ngẫu nhiên xung quanh. Healer sẽ chữa trị cho Runner bất kỳ chạy ngang qua, mặt khác, Hunter sẽ gây thiệt hại cho bất kỳ Runner xung quanh. Tất cả tương tác sẽ được xử lý bằng hệ thống message queue.

Bổ sung Message Queue

Hãy bắt đầu tạo ra PlayState sẽ gồm có một danh sách các thực thể (healer, runner và hunter) và một giá trị của class MessageQueue:

Trong vòng lặp game, được hiển thị bằng phương thức update(), phương thức dispatch() của message queue được kích hoạt, vậy tất cả message được truyền đi ở frame cuối cùng của mỗi game.

Bổ sung các Runner

Class Runner có cấu trúc như sau:

Phần quan trọng nhất của phương thức onMessage(), được kích hoạt bởi message queue mỗi khi có một message cho giá trị này. Như đã giải thích trước đó, field type trong message được sử dụng để quyết định xem giao tiếp này là gì.

Dựa trên type của message, hành động đúng được thực hiện: nếu type là "damage", điểm sức khoẻ giảm, nếu type là "heal", điểm sức khoẻ tăng lên. Số điểm sức khoẻ tăng hay giảm được xác định bởi field data từ sender của message.

Trong PlayState, chúng ta thêm vào danh sách thực thể một vài runner:

Kết quả là có thêm nhiều runner di chuyển ngẫu nhiên xung quanh:

Bổ sung Hunter

Class Hunter có cấu trúc như sau:

Hunter cũng sẽ di chuyển xung quanh, nhưng họ sẽ gây tổn thương cho các runner gần đó. Hành vi này được triển khai trong phương thức update(), ở đó tất cả thực thể trong game được kiểm tra và runner được thông tin về mức tổn hại.

Message cho thương tổn được tạo ra như sau:

Message chứa thông tin về nơi nhận (entity, trong trường hợp này là entity được phân tích trong vòng lặp hiện tại), sender (this, đại diện cho hunter đang tấn công) type của message ("damage") và số lượng tổn thương (2, trong trường hợp này được gán cho field data của message).

Message sau đó được chuyên đến điểm đến thông qua lệnh this.getMessageQueue().add(msg), lệnh này bổ sung message vừa được tạo ra vào message queue.

Sau cùng, ta bổ sung Hunter vào danh sách của thực thể trong PlayState:

Kết quả là có vài runner di chuyển xung quanh, nhận message từ hunter khi chúng tiến lại gần nhau:

Tôi đã bổ sung flying envelope làm hướng dẫn trực quan để giúp hiển thị điều đang diễn ra.

Bổ sung Healder

Class Healer có cấu trúc như sau:

Code và cấu trúc tương tự với class Hunter, ngoại trừ vài chỗ khác biệt. Tương tự như triển khai của Hunter, phương thức update() của healer lặp lại trên danh sách của các thực thể trong game, truyền tin đến bất kỳ thực thể nào trong phạm vi cứu chữa:

Message cũng có một điểm đến (entity), một sender (this), đó là healer thực hiện hành động này, type của message ("heal") và điểm số cứu chữa (2, được gán vào field data của message).

Chúng ta bổ sung Healer vào danh sách thực thể trong PlayState giống với cách ta đã làm với Hunter và kết quả một bối cảnh gồm các runner, một hunter, và một healer:

Và xong! Chúng ta đã có 3 thực thể khác biệt tương tác với nhau bằng cách trao đổi message.

Thảo luận về tính linh hoạt

Hệ thống message queue là phương pháp linh hoạt để quản lý tương tác trong game. Tương tác được thực hiện thông qua một kênh giao tiếp thống nhất và có giao diện riêng lẻ dễ triển khai và dễ sử dụng.

Khi game của bạn phát triển phức tạp, có thể cần có tương tác mới. Một số có thể là ngoài ý muốn, vì thế bạn cần phải thích ứng code của mình để xử lý chúng. Nếu bạn đang dùng hệ thống message queue, đây là vấn đề của việc bổ sung một message mới ở đâu đó và xử lý nó trong một nơi khác.

Ví dụ, hình dung bạn muốn để Hunter tương tác với Healer, bạn chỉ phải để Hunter gửi message cùng một tương tác mới, ví dụ "flee" và bảo đảm rằng Healer có thử xử lý nó trong phương thức onMessage:

Tại sao không trực tiếp gửi message đi?

Dù việc trao đổi message giữa các thực thể có thể hữu dụng, có lẽ bạn đang nghĩ tại sao cần có MessageQueue. Bạn không thể chỉ tự mình kích hoạt phương thức onMessage() thay vì dựa vào MessageQueue, như code bên dưới?

Bạn chắc chắn có thể thực hiện một hệ thống message như thế, nhưng việc dùng một MessageQueue mang đến vài lợi thế.

Ví dụ, bằng cách tập trung gửi message, bạn có thể thực hiện một số tính năng thú vị như message bị trì hoãn, khả năng nhắn tin cho một nhóm thực thể và thông tin gỡ lỗi trực quan (chẳng hạn như flying envelope được sử dụng trong hướng dẫn này).

Vẫn có không gian để sáng tạo trong class MessageQueue, tùy thuộc vào bạn và yêu cầu trong trò chơi của bạn.

Tổng kết

Xử lý tương tác giữa các thực thể trong game bằng hệ thống message queue là phương pháp để giữ mã của bạn được sắp xếp và sẵn sàng cho tương lai. Tương tác mới có thể dễ dàng và nhanh chóng được bổ sung vào, ngay cả những ý tưởng phức tạp nhất của bạn, miễn là chúng được đóng gói dưới dạng message.

Như đã thảo luận trong hướng dẫn, bạn có thể bỏ qua việc dùng message queue và chỉ gửi message trực tiếp đến các thực thể. Bạn cũng có thể tập trung hoá việc giao tiếp bằng cách sử dụng class MessageQueue để dành chỗ cho các tính năng mới trong tương lai, chẳng hạn như message bị trì hoãn.

Tôi hy vọng phương pháp này hữu dụng và bổ sung vào các công cụ phát triển game của bạn. Phương pháp có lẽ quá mức cho các dự án nhỏ, nhưng chắc chắn nó sẽ giúp bạn bớt đau đầu trong các dự án game dài hơi.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.