Game Programming Patterns (P3)

Cùng điểm qua các design pattern hay sử dụng trong phát triển game

Game pattern

Observer

Trong runtime mọi thứ đều có thể xảy ra.

Khi bạn vượt qua một level

Khi enemy bị tiêu diệt

Khi bạn sử dụng skill …

Bạn thường cần một cơ chế cho phép một số đối tượng thông báo cho những đối tượng khác mà không trực tiếp tham chiếu đến chúng. Để tránh tạo ra những phụ thuộc không cần thiết.

Observer là một giải pháp phổ biến cho vấn đề này. Nó cho phép các đối tượng của bạn giao tiếp nhưng vẫn là liên kết lỏng lẻo (loosely coupled) bằng cách sử dụng mối quan hệ “1 - nhiều”

Khi một đối tượng thay đổi trạng thái tất cả các phụ thuộc (dependency) sẽ nhận được thông báo tự động.

Bạn có thể tưởng tượng nó giống như một đài phát thanh. Đúng 19h nhà đài phát thời sự trên VTV3 thì những ai đang mở kênh VTV3 thì đều xem thời sự

Như hình trên thì đài phát thanh gọi là “Subject”. Các đối tượng khác đang lắng nghe (người xem, người quan sát) gọi là “Observer”

Mô hình này tách biệt subject và observer, subject không quan tâm đến observer nhận được thông báo (signal) thì sẽ làm gì công việc của nó chỉ là thông báo mà thôi. Trong khi observer phụ thuộc vào subject các observer lại không biết gì về nhau

Event

Observer pattern phổ biến đến mức nó được tích hợp sẵn vào c#. Bạn có thể tự thiết kế các class subject-observer của riêng mình nhưng điều đó là không cần thiết (Don’t Reinvent The Wheel, Unless You Plan on Learning More About Wheels)

C# đã cung cấp sẵn một triển khai observer thông qua việc sử dụng event.

event chỉ đơn giản là một thông báo cho biết điều gì đã xảy ra. Nó bao gồm các đặc điểm sau:

  • publisher (subject) sẽ tạo event dựa trên việc sử dụng delegate để thiết lập một kênh cụ thể (có thể coi như định danh để xác định phương thức). event chỉ là một số hành động mà đối tượng sẽ thực hiện trong runtime như TakeDamage, ReceiveReward, LevelUp, Death, Attack, ClickButton …
  • Sau đó, subscribers (observers) sẽ tạo một phương thức gọi là event handler, phương thức này phải khớp với delegate được thiết lập bởi publisher (subject)
  • event handler của mỗi subcriber sẽ đăng ký event của publisher. Số lượng subcriber tham gia đăng ký là không giới hạn, chúng đều chờ đợi event được kích hoạt
  • Khi publisher thông báo là event đang diễn trong runtime (rise event), điều này sẽ gọi event handler của mỗi subscriber, event handler này sẽ chạy logic bên trong của riêng nó

Bằng cách này, bạn có thể làm cho nhiều thành phần phản ứng với một sự kiện duy nhất từ subject. Nếu đối tượng cho biết rằng một nút đã được nhấn, thì những observer có thể play animation hay sound, chuyển scene hay lưu dữ liệu người dùng lại. Phản hồi của nó có thể là bất cứ thứ gì, đó là lý do tại sao bạn sẽ thường thấy observer pattern được sử dụng để gửi thông điệp giữa các đối tượng

Ví dụ ta có class subject là GameCenter

Mặc dù bạn có thể sử dụng delegate nhưng sử dụng Action trong namespace System hoạt động đủ tốt cho hầu hết mọi trường hợp

Các Observer lần lượt là CoinObserver, GemObserver, CoinArchivementObserver

Class UserData hiện cung cấp hai property là CurrentCoin và CurrentGem

Các class Observer kể trên hiện đang kế thừa từ MonoBehaviour nhưng điều đó là không bắt buộc.

Phương thức event handler trong class CoinObserver là OnCoinChangedValue. Của class GemObserver là OnGemChangedValue. Bên trong event handler có thể chứa bất kỳ logic xử lý nào và tên của chúng thường bắt đầu bằng tiền tố “On” bạn cũng đặt theo quy tắc của riêng mình miễn là nó đồng nhất.

Bên trong Awake hoặc Start chúng ta có thể đăng ký event bằng toán tử += hoặc sử dụng các phương thức wrapper cho event của bạn như sau

Nếu phương thức OnCurrencyChangeEvent của class subject GameCenter được gọi nó sẽ thực thi event CurrencyChangedEvent và bất cứ event handle nào liên kết với nó sẽ được thực thi ngay lập tức

Lưu ý rằng nếu đối tượng observer bị xóa bỏ trong runtime trong khi nó vẫn còn liên kết event với subject thì khi event được gọi có thể sẽ gây ra lỗi. Chính vì vậy trong phương thức OnDestroy chúng ta sẽ hủy liên kết bằng cách sử dụng toán tử -=

Bạn có thể sử dụng Observer Pattern cho hầu hết mọi thứ xảy ra trong trò chơi của mình như :

  • Gửi event khi người chơi tiêu diệt enemy , tấn công enemy, …
  • Khi người chơi nhặt được vật phẩm
  • Khi người chơi xem xong quảng cáo
  • Khi người chơi mua vật phẩm trong shop
  • Xử lý khi người chơi đạt được archievement
  • Điều kiện xử lý Win/Lose
  • Xử lý UI (User Interface)

Bổ sung

Trong Unity cũng cung cấp riêng UnityEvent bạn có thể bắt gặp nó khi sử dụng Button UI của Unity với event onClick của button nó được triển khai dưới dạng Observer pattern

Ưu điểm và nhược điểm

Những ưu điểm của observer pattern đó là :

  • Giúp tách rời mối quan hệ giữa các đối tượng: subject không cần biết điều gì về những observer đã đăng ký event cả. Thay vì tạo ra phụ thuộc trực tiếp giữa đối tượng này và đối tượng khác subject và observer vẫn có cho mình mối liên kết nhưng được duy trì ở mức độ tách biệt
  • Bạn không cần phải viết lại hoàn toàn pattern: C# có sẵn Action Unity thì cung cấp sẵn UnityEventUnityAction
  • Mỗi observer (người lắng nghe hay người quan sát) có thể có logic xử lý riêng của nó : bằng cách này sẽ giúp bạn quản lý cũng như bảo trì logic cho tính năng dễ dàng hơn
  • Nó rất phù hợp để sử dụng chung với interface: như clean architecture, hay MVP hay MVC là các kiến trúc phân tầng rõ ràng, logic xử lý gameplay sẽ tách biệt hoàn toàn khỏi logic xử lý UI hay logic xử lý dữ liệu. Observer pattern được sử dụng để đóng vai trò làm cầu nối truyền tải event giữa các tầng kiến trúc với nhau.

Còn dưới đây là những nhược điểm của nó:

  • Nó tăng độ phức tạp trong code của bạn: Bạn sẽ cần Register cũng như là UnRegister event vào thời điểm thích hợp (Awake hay Start và OnDestroy). Bạn cần phải đảm bảo luôn hủy đăng ký event trước khi đối tượng bị phá hủy
  • Observer cần tham chiếu đến class định nghĩa event: Như ví dụ đã nêu bên trên thì các Observer cần phải tham chiếu đến class chứa event có tên là GameCenter
  • Về Perfomance: Observer pattern là một kiến trúc event-driven nên bản thân nó cũng có những chi phí nhất định về hiệu năng, khi trong scene lớn có nhiều gameobject với số lượng event truyền tải có thể gây cản trở performance
Lượt nghé thăm