Проектирование игровых сообщений


8

Я делаю простую игру и решил попробовать внедрить систему обмена сообщениями.

Система в основном выглядит так:

Объект генерирует сообщение -> сообщение отправляется в глобальную очередь сообщений -> messageManager уведомляет каждый объект о новом сообщении через onMessageReceived (Message msg) -> если объект хочет, он воздействует на сообщение.

То, как я создаю объекты сообщений, выглядит так:

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

PlayerDiedMessage extends Message{
  int livesLeft;
}

Теперь мой SoundManagerEntity может сделать что-то подобное в своем методе onMessageReceived ()

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

Плюсы к этому подходу:

  1. Очень просто и легко реализовать
  2. Сообщение может содержать столько информации, сколько вы хотите, потому что вы можете просто создать новый подкласс Message, в котором есть вся необходимая информация.

Минусы:

  1. Я не могу понять, как я могу переместить объекты Message в пул объектов , если у меня нет другого пула для каждого подкласса Message. Таким образом, у меня есть много-много объектов создания / выделения памяти с течением времени.
  2. Не могу отправить сообщение конкретному получателю, но мне это еще не нужно в моей игре, поэтому я не против этого.

Что мне здесь не хватает? Должна быть лучшая реализация или какая-то идея, которую мне не хватает.


1) Зачем вам нужно использовать эти пулы объектов, какими бы они ни были? (Заметьте, что они и для чего они служат?) Нет необходимости разрабатывать то, что работает. И то, что у вас сейчас, похоже, не имеет проблем с этим. 2) Если вам нужно отправить сообщение конкретному получателю, вы найдете его и вызовите его onMessageReceived, причудливые системы не требуются.
snake5

Ну, я имел в виду, что в настоящее время я не могу перерабатывать объекты в общий объектный пул. Поэтому, если я когда-нибудь захочу уменьшить необходимое количество памяти, я не смогу. Прямо сейчас это не проблема, потому что моя игра настолько проста, что она отлично работает.
you786

Я предполагаю, что это Java? Похоже, вы пытаетесь разработать систему событий.
Джастин Скилз

@JustinSkiles Правильно, это Java. Я думаю, вы правы, это действительно используется, чтобы действовать как система событий. Какие еще способы вы можете использовать систему обмена сообщениями?
you786

Ответы:


11

Простой ответ: вы не хотите перерабатывать сообщения. Каждый перерабатывает объекты, которые либо создаются (и удаляются) тысячами в каждом кадре, например частицы, либо очень тяжелые (десятки свойств, длительный процесс инициализации).

Если у вас есть частица снега с каким-то растровым изображением, скоростями, физическими атрибутами, которые просто спустились ниже вашего вида, вы можете захотеть переработать ее вместо создания новой частицы и повторной инициализации растрового изображения и рандомизации атрибутов. В вашем примере нет ничего полезного в Message, чтобы сохранить его, и вы потратите больше ресурсов на удаление его специфичных для экземпляра свойств, чем просто на создание нового объекта Message.


3
Хороший ответ. Я использую пул для своих сообщений, и я никогда не проверял, были ли они полезны или нет. (В прошлом) время в профиль!
Raptormeat

Хм. Что делать, если у меня есть сообщение, которое отправляется, может быть, каждый кадр? Я думаю, что ответом было бы иметь определенный пул для такого типа объекта, что имеет смысл.
you786

@ you786 Я только что сделал быстрый грязный тест в Actionscript 3. Создание массива и заполнение его 1000 сообщений занимает 1 мс в медленной среде отладки. Я надеюсь, что это проясняет ситуацию.
Маркус фон Броади

@ you786 Он прав, сначала определите это как узкое место, если вы хотите быть разработчиком инди-игры, вам нужно научиться делать вещи быстро и эффективно. Тратить время на точную настройку кода стоит только в том случае, если этот код в настоящее время замедляет вашу игру, и внесение этих изменений может значительно повысить производительность. Например, создание экземпляров Sprtie в AS3 заняло бы очень много времени, гораздо лучше их переработать. Создание объектов Point не является, их утилизация была бы пустой тратой времени на кодирование и удобочитаемость.
AturSams

Ну, я должен был сделать это более ясным, но я не беспокоюсь о распределении памяти. Я беспокоюсь о том, что Java-сборщик мусора работает в середине игры и вызывает замедление работы. Но ваше мнение о преждевременной оптимизации все еще верно.
you786

7

В моей Java-игре я придумал очень похожее решение, за исключением того, что мой код обработки сообщений выглядит следующим образом:

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

Я использую аннотации, чтобы пометить метод как обработчик событий. Затем, в начале игры, я использую отражение, чтобы вычислить отображение (или, как я это называю, «трубопровод») сообщений - какой метод вызывать для какого объекта при отправке события определенного класса. Это работает ... потрясающе.

Расчет трубопроводов может быть немного сложным, если вы хотите добавить интерфейсы и подклассы к миксу, но, тем не менее, это довольно просто. Во время загрузки игры происходит небольшое замедление, когда все классы нужно сканировать, но это делается только один раз (и, вероятно, может быть перемещено в отдельный поток). Вместо этого получается, что фактическая отправка сообщений обходится дешевле - мне не нужно вызывать каждый метод «messageReceived» в кодовой базе, просто чтобы он проверил, является ли событие хорошим классом.


3
Таким образом, вы в основном нашли способ для вашей игры работать медленнее? : D
Маркус фон Броади

3
@MarkusvonBroady Если это когда-то на загрузке, полностью стоит. Сравните это с чудовищем в моем ответе.
michael.bartnett

1
@MarkusvonBroady Вызов с помощью отражения только немного медленнее, чем обычный вызов, если у вас есть все части на месте (по крайней мере, мое профилирование не показывает никаких проблем). Открытие, конечно, дорого.
Лиосан

@ michael.bartnett Я пишу код для пользователя, а не для себя. Я могу жить с моим кодом больше для более быстрого выполнения. Но это было только мое субъективное замечание.
Маркус фон Броади

+1, путь аннотаций - это то, о чем я никогда не думал. Так что просто для пояснения, у вас в основном будет куча перегруженных методов, которые называются messageReceived с различными подклассами Message. Затем во время выполнения вы используете отражение, чтобы создать какую-то карту, которая вычисляет MessageSubclass => OverloadedMethod?
you786

5

РЕДАКТИРОВАТЬ Ответ Лиосан является более сексуальным. Посмотри на это.


Как сказал Маркус, сообщения не являются обычным кандидатом в пулы объектов. Не беспокойтесь по причинам, которые он упомянул. Если ваша игра отправляет тонны сообщений определенных типов, то, возможно, это того стоит. Но тогда я бы предложил вам переключиться на прямые вызовы методов.

Говоря о которых,

Не могу отправить сообщение конкретному получателю, но мне это еще не нужно в моей игре, поэтому я не против этого.

Если вы знаете конкретного получателя, которому хотите отправить его, не будет ли довольно просто получить ссылку на этот объект и выполнить прямой вызов метода? Вы также можете скрыть определенные объекты за классами менеджера для более легкого доступа через системы.

В вашей реализации есть один недостаток, и есть вероятность того, что множество объектов получат сообщения, которые им не нужны. В идеале ваши классы будут получать только те сообщения, которые им действительно нужны.

Вы можете использовать HashMap<Class, LinkedList<Messagable>>для связывания типов сообщений со списком объектов, которые хотят получать сообщения этого типа.

Подписка на тип сообщения будет выглядеть примерно так:

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

Вы могли бы реализовать это subscribeтак (прости меня за некоторые ошибки, я не писал Java в течение нескольких лет):

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);
}

И тогда вы могли бы передать сообщение, как это:

globalMessageQueue.broadcastMessage(new PlayerDiedMessage(42));

И broadcastMessageможет быть реализовано так:

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

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

Вы также можете подписаться на сообщение таким образом, чтобы разделить обработку сообщений на различные анонимные функции:

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();
            }
        }
    });
}

Это немного многословно, но помогает с организацией. Вы также можете реализовать вторую функцию, subscribeAllкоторая будет хранить отдельный список, Messagableкоторый хочет услышать обо всем.


1

1 . Не.

Сообщения стоят недорого. Я согласен с Маркусом фон Броади. GC будет собирать их быстро. Старайтесь не прикреплять много дополнительной информации к сообщениям. Это просто сообщения. Поиск экземпляра - это информация сама по себе. Используйте это (как вы сделали в своем примере).

2 . Вы можете попробовать использовать MessageDispatcher .

В отличие от первого решения вы могли бы иметь централизованный диспетчер сообщений, который обрабатывает сообщения и передает их намеченным объектам.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.