oop — C ++ слушатели изменения переменных (более 100 классов)

Я пытаюсь создать архитектуру для MMO-игры, и я не могу понять, как я могу хранить столько переменных, сколько мне нужно, в GameObjects без большого количества вызовов, чтобы отправлять их по проводам, в то же время я обновляю их.

Что у меня сейчас есть:

Game::ChangePosition(Vector3 newPos) {
gameobject.ChangePosition(newPos);
SendOnWireNEWPOSITION(gameobject.id, newPos);
}

Это делает код мусором, трудно поддерживать, понимать, расширять. Так что подумайте о примере Чемпиона:

Пример GameObject Champion

Мне пришлось бы сделать много функций для каждой переменной. И это всего лишь обобщение для этого Чемпиона, у меня может быть 1-2 других переменных-члена для каждого типа / класса Чемпиона.

Было бы идеально, если бы я мог иметь OnPropertyChange из .NET или что-то подобное. Архитектура, которую я пытаюсь угадать, будет хорошо работать, если у меня будет что-то похожее на:

Для HP: когда я обновляю его, автоматически SendFloatOnWire("HP", hp);

Для положения: когда я обновляю его, автоматически SendVector3OnWire("Position", Position)

Для имени: когда я обновляю его, автоматически SendSOnWire("Name", Name);

Что именно SendFloatOnWire, SendVector3OnWire, SendSOnWire ? Функции, которые сериализуют эти типы в буфере символов.

ИЛИ МЕТОД 2 (предпочтительно), но может быть дорогим

Обновите Hp, Позиционируйте нормально, а затем каждую галочку Network Thread просканируйте все экземпляры GameObject на сервере на предмет измененных переменных и отправьте их.

Как это будет реализовано на высококлассном игровом сервере и какие у меня варианты? Любая полезная книга для таких случаев?

Макросы окажутся полезными? Я думаю, что я был знаком с некоторым исходным кодом чего-то подобного, и я думаю, что он использовал макросы.

Заранее спасибо.

РЕДАКТИРОВАТЬЯ думаю, что нашел решение, но я не знаю, насколько оно действительно надежно. Я собираюсь пойти на это и посмотреть, где я стою после этого. https://developer.valvesoftware.com/wiki/Networking_Entities

5

Решение

По способу 1:

Такой подход может быть относительно «легким» для реализации с использованием карт, доступ к которым осуществляется через геттеры / сеттеры. Общая идея будет выглядеть примерно так:

class GameCharacter {
map<string, int> myints;
// same for doubles, floats, strings
public:
GameCharacter() {
myints["HP"]=100;
myints["FP"]=50;
}
int getInt(string fld) { return myints[fld]; };
void setInt(string fld, int val) { myints[fld]=val; sendIntOnWire(fld,val); }
};

Онлайн демо

Если вы предпочитаете сохранять свойства в своем классе, вместо значений вы должны использовать карту с указателями или указателями элементов. При построении вы должны инициализировать карту с соответствующими указателями. Если вы решите изменить переменную-член, вы должны всегда проходить через установщик.

Вы могли бы даже пойти дальше и абстрагировать Champion сделав его просто набором свойств и поведений, которые будут доступны через карту. Эта архитектура компонента представлена Майк МакШаффри в Кодирование игры завершено (обязательная книга для любого разработчика игр). Есть сайт сообщества для книги с некоторым исходным кодом для загрузки. Вы можете взглянуть на actor.h а также actor.cpp файл. Тем не менее, я действительно рекомендую прочитать полные объяснения в книге.

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

По способу 2:

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

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

1

Другие решения

Общий вывод, к которому я пришел: еще один звонок после того, как я обновлю позицию, не так уж и плох. Это строка кода длиннее, но лучше по разным мотивам:

  1. Это явно. Вы точно знаете, что происходит.
  2. Вы не замедляете код, делая все виды хаков, чтобы заставить его работать.
  3. Вы не используете дополнительную память.

Методы, которые я пробовал:

  1. Наличие карт для каждого типа, как предлагает @Christophe. Основным недостатком было то, что он не подвержен ошибкам. Вы могли бы объявить HP и Hp на одной и той же карте, и это могло бы добавить еще один слой проблем и разочарований, таких как объявление карт для каждого типа и затем предшествование каждой переменной с именем карты.
  2. Используя что-то АНАЛОГИЧНЫЙ к клапанам двигатель: Он создал отдельный класс для каждой сетевой переменной, которую вы хотели. Затем он использовал шаблон, чтобы обернуть объявленные вами базовые типы (int, float, bool), а также расширенные операторы для этого шаблона. Он использовал слишком много памяти и дополнительные вызовы для базовой функциональности.
  3. Используя картограф данных это добавило указатели для каждой переменной в конструкторе, а затем отправило их со смещением. Я ушел из проекта преждевременно, когда понял, что код начинает сбивать с толку и его трудно поддерживать.
  4. Использование структуры, которая отправляется каждый раз, когда что-то меняется, вручную. Это легко сделать с помощью Protobuf. Расширение структур также легко.
  5. Каждый тик, генерируйте новую структуру с данными для классов и отправляйте ее. Это всегда актуально для очень важных вещей, но потребляет много трафика.
  6. Используйте отражение с помощью Boost. Это не было отличным решением.

В конце концов, я использовал смесь 4 и 5. И теперь я внедряю это в свою игру. Одним из огромных преимуществ protobuf является возможность генерировать структуры из файла .proto, а также предлагать сериализацию для структуры. Это невероятно быстро.

Для тех специальных именованных переменных, которые появляются в подклассах, у меня есть другая структура. В качестве альтернативы, с помощью protobuf я мог бы получить массив свойств, таких как: ENUM_KEY_BYTE VALUE, куда ENUM_KEY_BYTE это просто байт, который ссылается на enum к свойствам, таким как IS_FLYING, IS_UP, IS_POISONED, а также VALUE это string,

Самая важная вещь, которую я узнал из этого, — это иметь как можно больше сериализации. Лучше использовать больше процессоров на обоих концах, чем иметь больше ввода&Выход.

Если у кого-то есть какие-либо вопросы, прокомментируйте, и я сделаю все возможное, чтобы помочь вам.

ioanb7

0

По вопросам рекламы [email protected]