Thiết kế hệ thống nhắn tin trò chơi


8

Tôi đang làm một trò chơi đơn giản và đã quyết định thử thực hiện một hệ thống nhắn tin.

Hệ thống về cơ bản trông như thế này:

Thực thể tạo tin nhắn -> tin nhắn được đăng lên hàng đợi tin nhắn toàn cầu -> messageManager thông báo cho mọi đối tượng của tin nhắn mới thông qua onMessageReceured (Tin nhắn tin nhắn) -> nếu đối tượng muốn, nó sẽ hoạt động trên tin nhắn.

Cách tôi tạo các đối tượng tin nhắn là như thế này:

//base message class, never actually instantiated
abstract class Message{
  Entity sender;
}

PlayerDiedMessage extends Message{
  int livesLeft;
}

Bây giờ SoundManagerEntity của tôi có thể làm một cái gì đó như thế này trong phương thức onMessageReceured () của nó

public void messageReceived(Message msg){
  if(msg instanceof PlayerDiedMessage){
     PlayerDiedMessage diedMessage = (PlayerDiedMessage) msg;
     if(diedMessage.livesLeft == 0)
       playSound(SOUND_DEATH);
  }
}

Ưu điểm của phương pháp này:

  1. Rất đơn giản và dễ thực hiện
  2. Tin nhắn có thể chứa nhiều thông tin như bạn muốn, bởi vì bạn chỉ cần tạo một lớp con Tin nhắn mới có bất kỳ thông tin nào cần thiết.

Nhược điểm:

  1. Tôi không thể tìm ra cách tôi có thể tái chế các đối tượng Tin nhắn thành một nhóm đối tượng , trừ khi tôi có một nhóm khác nhau cho mỗi lớp con của Tin nhắn. Vì vậy, tôi có rất nhiều phân bổ bộ nhớ / tạo đối tượng theo thời gian.
  2. Không thể gửi tin nhắn đến một người nhận cụ thể, nhưng tôi chưa cần điều đó trong trò chơi của mình nên tôi không bận tâm lắm.

Tôi đang thiếu gì ở đây? Phải có một triển khai tốt hơn hoặc một số ý tưởng mà tôi đang thiếu.


1) Tại sao bạn cần sử dụng các nhóm đối tượng đó, bất kể chúng là gì? (Một lưu ý phụ, chúng là gì và chúng phục vụ mục đích gì?) Không cần thiết kế ngoài những gì hoạt động. Và những gì bạn hiện đang xuất hiện không có vấn đề với điều đó. 2) Nếu bạn cần gửi tin nhắn đến một người nhận cụ thể, bạn sẽ tìm thấy nó và bạn gọi nó trênMessageReceured, không yêu cầu hệ thống ưa thích.
rắn5

Chà, ý tôi là hiện tại tôi không thể tái chế các đối tượng thành một nhóm đối tượng có mục đích chung. Vì vậy, nếu tôi muốn giảm phân bổ bộ nhớ cần thiết, tôi không thể. Ngay bây giờ điều này không thực sự là một vấn đề bởi vì trò chơi của tôi rất đơn giản và chạy rất tốt.
bạn786

Tôi đang giả sử đây là Java? Có vẻ như bạn đang cố gắng phát triển một hệ thống sự kiện.
Justin Skiles

@JustinSkiles Đúng, đây là Java. Tôi đoán bạn đã đúng, nó thực sự được sử dụng để hoạt động như một hệ thống sự kiện. Những cách khác bạn có thể sử dụng một hệ thống nhắn tin?
bạn786

Câu trả lời:


11

Câu trả lời đơn giản là bạn không muốn tái chế tin nhắn. Người ta tái chế các đối tượng được tạo ra (và loại bỏ) bởi hàng ngàn khung hình, như các hạt hoặc rất nặng (hàng chục thuộc tính, quá trình khởi tạo dài).

Nếu bạn có một hạt tuyết với một số bitmap, vận tốc, thuộc tính vật lý liên quan đã đi xuống dưới chế độ xem của bạn, bạn có thể muốn tái chế nó thay vì tạo một hạt mới và khởi tạo lại bitmap và ngẫu nhiên lại các thuộc tính. Trong ví dụ của bạn, không có gì hữu ích trong Thông báo để giữ nó và bạn sẽ lãng phí nhiều tài nguyên hơn trong việc xóa các thuộc tính cụ thể của nó, hơn là bạn chỉ tạo một đối tượng Tin nhắn mới.


3
Câu trả lời tốt. Tôi đang sử dụng một nhóm cho các tin nhắn của mình và tôi chưa bao giờ thực sự điều tra xem chúng có hữu ích hay không. (Quá khứ-) thời gian để hồ sơ!
Raptormeat

Hừm. Điều gì sẽ xảy ra nếu tôi có một thông điệp được phát ra có thể mỗi khung hình? Tôi đoán câu trả lời sẽ là có một nhóm cụ thể cho loại đối tượng đó, điều này có ý nghĩa.
bạn786

@ you786 Tôi vừa thực hiện một thử nghiệm bẩn nhanh chóng trong Actioncript 3. Tạo một Mảng và điền nó với 1000 Tin nhắn mất 1ms trong môi trường Gỡ lỗi chậm. Tôi hy vọng rằng sẽ làm sáng tỏ mọi thứ.
Markus von Broady

@ you786 Anh ấy đúng, trước tiên hãy xác định đây là một nút cổ chai, nếu bạn muốn trở thành một nhà phát triển game độc ​​lập, bạn cần học cách làm mọi thứ nhanh chóng và hiệu quả. Đầu tư thời gian để tinh chỉnh mã của bạn chỉ có giá trị nếu mã đó hiện đang làm chậm trò chơi của bạn và thực hiện những thay đổi này có thể tăng hiệu suất đáng kể. Ví dụ, việc tạo các phiên bản Sprtie trong AS3 sẽ rất tốn thời gian, tốt hơn là tái chế chúng. Tạo các đối tượng Point thì không, tái chế chúng sẽ lãng phí thời gian mã hóa và khả năng đọc.
AturSams

Chà, lẽ ra tôi nên nói rõ hơn, nhưng tôi không lo lắng về việc phân bổ bộ nhớ. Tôi lo lắng về trình thu gom rác của Java chạy giữa trò chơi và gây ra sự chậm lại. Nhưng quan điểm của bạn về tối ưu hóa sớm vẫn còn hiệu lực.
bạn786

7

Trong trò chơi dựa trên Java của tôi, tôi đã đưa ra một giải pháp rất giống nhau, ngoại trừ mã xử lý tin nhắn của tôi trông như thế này:

@EventHandler
public void messageReceived(PlayerDiedMessage msg){
  if(diedMessage.livesLeft == 0)
     playSound(SOUND_DEATH);
}

Tôi sử dụng các chú thích để đánh dấu một phương thức như một trình xử lý sự kiện. Sau đó, khi bắt đầu trò chơi, tôi sử dụng sự phản chiếu để tính toán ánh xạ (hoặc, như tôi gọi nó là "đường ống") của các thông điệp - phương thức nào để gọi đối tượng nào khi một sự kiện của một lớp cụ thể được gửi. Điều này hoạt động ... tuyệt vời.

Việc tính toán đường ống có thể hơi phức tạp nếu bạn muốn thêm giao diện và các lớp con vào hỗn hợp, nhưng dù sao nó cũng khá đơn giản. Có một chút chậm trong khi tải trò chơi khi tất cả các lớp cần được quét, nhưng nó chỉ được thực hiện một lần (và có thể có thể được chuyển sang một luồng riêng). Thay vào đó, việc gửi tin nhắn thực tế rẻ hơn - tôi không phải gọi mọi phương thức "messageReceured" trong cơ sở mã chỉ để kiểm tra xem sự kiện đó có phải là một lớp tốt hay không.


3
Vì vậy, về cơ bản bạn đã tìm thấy một cách để trò chơi của bạn chạy chậm hơn? : D
Markus von Broady

3
@MarkusvonBroady Nếu nó một lần tải, hoàn toàn xứng đáng. So sánh điều đó với sự quái dị trong câu trả lời của tôi.
michael.bartnett

1
@MarkusvonBroady Invocation thông qua sự phản chiếu chỉ chậm hơn một chút so với cách gọi thông thường, nếu bạn có tất cả các phần (ít nhất là hồ sơ của tôi không hiển thị bất kỳ vấn đề nào ở đó). Khám phá là tốn kém, chắc chắn.
Liosan

@ michael.bartnett Tôi mã cho một người dùng, không phải cho chính tôi. Tôi có thể sống với mã của mình lớn hơn để thực thi nhanh hơn. Nhưng đó chỉ là nhận xét chủ quan của tôi.
Markus von Broady

+1, đường dẫn chú thích là điều tôi chưa bao giờ nghĩ tới. Vì vậy, để làm rõ, về cơ bản, bạn có một loạt các phương thức quá tải được gọi là messageReceured với các lớp con khác nhau của Message. Sau đó, trong thời gian chạy, bạn sử dụng sự phản chiếu để tạo ra một số loại bản đồ tính toán MessageSub class => OverloadedMethod?
bạn786

5

Câu trả lời của EDIT Liosan là quyến rũ hơn. Nhìn vào nó.


Giống như Markus đã nói, tin nhắn không phải là một ứng cử viên phổ biến cho nhóm đối tượng. Đừng bận tâm với nó vì những lý do anh ấy đề cập. Nếu trò chơi của bạn gửi hàng tấn tin nhắn thuộc các loại cụ thể, thì có lẽ nó đáng giá. Nhưng sau đó, tôi khuyên bạn nên chuyển sang các cuộc gọi phương thức trực tiếp.

Nói về mà,

Không thể gửi tin nhắn đến một người nhận cụ thể, nhưng tôi chưa cần điều đó trong trò chơi của mình nên tôi không bận tâm lắm.

Nếu bạn biết một người nhận cụ thể mà bạn muốn gửi nó đến, sẽ không dễ dàng để có được một tham chiếu đến đối tượng đó và thực hiện một cuộc gọi phương thức trực tiếp trên nó? Bạn cũng có thể ẩn các đối tượng cụ thể đằng sau các lớp người quản lý để truy cập dễ dàng hơn trên các hệ thống.

Có một yếu tố để bạn thực hiện và đó là tiềm năng cho rất nhiều đối tượng nhận được thông điệp mà họ không thực sự quan tâm. Lý tưởng nhất là các lớp học của bạn sẽ chỉ nhận được tin nhắn mà họ thực sự quan tâm.

Bạn có thể sử dụng một HashMap<Class, LinkedList<Messagable>>để liên kết các loại tin nhắn với một danh sách các đối tượng muốn nhận loại tin nhắn đó.

Đăng ký một loại tin nhắn sẽ trông giống như thế này:

globalMessageQueue.subscribe(PlayerDiedMessage.getClass(), this);

Bạn có thể thực hiện subscribenhư vậy (tha thứ cho tôi một số lỗi, đã không viết java trong một vài năm):

private HashMap<Class, LinkedList<? extends Messagable>> subscriberMap;

void subscribe(Class messageType, Messagable receiver) {
    LinkedList<Messagable> subscriberList = subscriberMap.get(messageType);
    if (subscriberList == null) {
        subscriberList = new LinkedList<? extends Messagable>();
        subscriberMap.put(messageType, subscriberList);
    }

    subscriberList.add(receiver);
}

Và sau đó bạn có thể phát một thông điệp như thế này:

globalMessageQueue.broadcastMessage(new PlayerDiedMessage(42));

broadcastMessagecó thể được thực hiện như thế này:

void broadcastMessage(Message message) {
    Class messageType = message.getClass();
    LinkedList<? extends Messagable> subscribers = subscriberMap.get(messageType);

    for (Messagable receiver : subscribers) {
        receiver.onMessage(message);
    }
}

Bạn cũng có thể đăng ký tin nhắn theo cách mà bạn có thể phân chia việc xử lý tin nhắn của mình thành các chức năng ẩn danh khác nhau:

void setupMessageSubscribers() {
    globalMessageQueue.subscribe(PlayerDiedMessage.getClass(), new Messagable() {
        public void onMessage(Message message) {
            PlayerDiedMessage msg = (PlayerDiedMessage)message;
            if (msg.livesLeft <= 0) {
                doSomething();
            }
        }
    });

    globalMessageQueue.subscriber(EnemySpawnedMessage.getClass(), new Messagable() {
        public void onMessage(Message message) {
            EnemySpawnedMessage msg = (EnemySpawnedMessage)message;
            if (msg.isBoss) {
                freakOut();
            }
        }
    });
}

Đó là một chút dài dòng, nhưng giúp tổ chức. Bạn cũng có thể thực hiện một chức năng thứ hai, subscribeAllnó sẽ giữ một danh sách riêng biệt Messagablemuốn nghe về mọi thứ.


1

1 . Đừng.

Tin nhắn có chi phí thấp. Tôi đồng ý với Markus von Broady. GC sẽ thu thập chúng nhanh chóng. Cố gắng không đính kèm nhiều thông tin bổ sung cho tin nhắn. Chúng chỉ là tin nhắn. Tìm ví dụ là một thông tin của chính nó. Sử dụng nó (như bạn đã làm trong ví dụ của bạn).

2 . Bạn có thể thử sử dụng MessageDispatcher .

Ngược lại với giải pháp đầu tiên, bạn có thể có bộ điều phối thư tập trung xử lý các tin nhắn và chuyển chúng đến các đối tượng dự định.

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.