Подтвердить что ты не робот

Структуры данных для передачи сообщений внутри программы?

Я пытаюсь написать простую RPG. Пока каждый раз, когда я пытаюсь запустить его, он становится беспорядочным, и я не знаю, как организовать что-либо. Поэтому я начинаю с того, что пытаюсь прототипировать новую структуру, которая в основном представляет собой структуру MVC. Мое приложение запускает выполнение в контроллере, где будет создано представление и модель. Затем он войдет в игровой цикл, и первым шагом в игровом цикле будет сбор пользовательского ввода.

Пользовательский ввод будет собираться частью представления, потому что он может меняться (3D-просмотр будет напрямую опросить пользовательский ввод, тогда как, возможно, удаленный просмотр получит его по telnet-соединению или в виде командной строки будет использоваться System.in). Ввод будет переведен в сообщения, и каждое сообщение будет передано Контроллеру (вызовом метода), который затем может интерпретировать сообщение для изменения данных модели или отправить данные по сети (поскольку я надеюсь, что у вас есть сетевой вариант).

Эта технология обработки сообщений также может использоваться в случае сетевой игры для обработки сетевых сообщений. До сих пор поддерживаю дух MVC?

В любом случае, мой вопрос: какой лучший способ представить эти сообщения?

Вот пример использования, причем каждое сообщение выделено курсивом: пусть говорят, что пользователь запускает игру и выбирает символ 2. Затем пользователь переходит в координаты (5,2). Затем он говорит в публичный чат: "Привет!". Затем он выбирает сохранение и выход.

Каким образом просмотр должен завершать эти сообщения во что-то, что может понять контроллер? Или вы думаете, что у меня должны быть отдельные методы контроллера, такие как chooseCharacter(), moveCharacterTo(), publicChat()? Я не уверен, что такая простая реализация будет работать, когда я перейду в сетевую игру. Но с другой стороны, я не хочу просто отправлять строки контроллеру. Это просто сложно, потому что действие select-character принимает одно целое, move-to принимает два целых числа, а чат принимает строку (и область действия (общедоступный глобальный), а в случае частного пользователя назначения); нет никакого реального набора данных для всего этого.

Также любые общие предложения приветствуются; я беспокоюсь об этом в нужное время? Я направился по правильному пути к хорошо продуманному MVC-приложению? Я что-то забыл?

Спасибо!

4b9b3361

Ответ 1

(Отказ от ответственности: я никогда не программировал игры на Java, только на С++. Но общая идея должна быть применима и на Java. Идеи, которые я представляю, не являются моими собственными, но месиво решений, которые я нашел в книгах или "в Интернете", см. В разделе ссылок. Я использую все это сам, и до сих пор это приводит к чистому дизайну, где я точно знаю, где добавить новые функции, которые я добавляю.)

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

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

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

Как было сказано выше, я не считаю, что MVC пригодится при разработке игры. Разделение модели/вида не является проблемой, а материал контроллера довольно сложный, слишком много, чтобы его просто называли "контроллером". Если вы хотите иметь подпакеты с именем model, view, control, идите вперед. В эту схему упаковки можно включить следующее: хотя другие, по крайней мере, разумны.

Трудно найти отправную точку в моем решении, поэтому я просто начинаю топ-самый:

В основной программе я просто создаю объект Application, запускаю его и запускаю. Приложение init() создаст серверы функций (см. Ниже) и добавит их. Также создается первое игровое состояние и нажимается сверху. (см. ниже)

Функциональные серверы инкапсулируют функции ортогональной игры. Они могут быть реализованы независимо и слабо связаны сообщениями. Примеры функций: звук, визуальное представление, обнаружение столкновения, искусственный интеллект/принятие решений, физика и т.д. Как организованы функции, описанные ниже.

Вход, поток управления и игровой цикл

Игровые состояния представляют собой способ организации управления вводом. Обычно у меня есть один класс, который собирает входные события или записывает входное состояние и затем опросает его (InputServer/InputManager). При использовании подхода, основанного на событиях, события присваиваются одному зарегистрированному состоянию активной игры.

При запуске игры это будет основное состояние игры в меню. Состояние игры имеет функцию init/destroy и resume/suspend. init() будет инициализировать состояние игры, в случае главного меню он отобразит верхний уровень меню. Resume() даст управление этому состоянию, теперь он принимает входные данные с InputServer. Suspend() очистит вид меню с экрана, а destroy() освободит любые ресурсы, необходимые для главного меню.

GameStates могут быть уложены в стек, когда пользователь запускает игру, используя опцию "новая игра", тогда состояние игры MainMenu приостанавливается, и PlayerControlGameState будет помещен в стек и теперь получает входные события. Таким образом, вы можете обрабатывать ввод в зависимости от состояния вашей игры. Когда только один контроллер активен в любой момент времени, вы значительно упрощаете поток управления.

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

Игровые объекты и функции

Сердцем этого дизайна является взаимодействие игровых объектов и функций. Как показано выше, функция в этом смысле представляет собой функциональность игры, которая может быть реализована независимо друг от друга. Игровой объект - это все, что взаимодействует с игроком или любыми другими игровыми объектами. Примеры: сам аватар игрока является игровым объектом. Факел - игровой объект, NPC - игровые объекты, такие как зоны освещения и источники звука или любая их комбинация.

Традиционно игровые объекты RPG являются высшим классом некоторой сложной иерархии классов, но на самом деле этот подход просто неверен. Многие ортогональные аспекты не могут быть помещены в иерархию и даже с использованием интерфейсов, в конце концов вы должны иметь конкретные классы. Элемент представляет собой игровой объект, элемент с возможностью выбора - игровой объект, в котором сундук представляет собой предмет, но создание сундука или нет - это либо решение с таким подходом, как если бы у вас был один иерархия. И это становится более сложным, когда вы хотите иметь разговорную волшебную сундук, который открывается только тогда, когда на загадку отвечают. Нет единственной подходящей иерархии.

Лучший подход состоит в том, чтобы иметь только один класс игрового объекта и поместить каждый ортогональный аспект, который обычно выражается в иерархии классов, в свой собственный класс компонентов/объектов. Может ли игровой объект удерживать другие предметы? Затем добавьте ContainerFeature к нему, поговорите, добавьте в него TalkTargetFeature и т.д.

В моем проекте GameObject имеет только уникальный уникальный идентификатор, имя и местоположение, все остальное добавляется как компонент компонента. Компоненты можно добавлять во время выполнения через интерфейс GameObject, вызывая addComponent(), removeComponent(). Чтобы сделать его видимым, добавьте VisibleComponent, сделайте его звуком, добавьте AudibleComponent, сделайте его контейнером, добавьте ContainerComponent.

VisibleComponent важен для вашего вопроса, так как это класс, который обеспечивает связь между моделью и представлением. Не все нуждается в взгляде в классическом смысле. Зона триггера не будет видна, окружающая звуковая зона тоже не будет. Будут видны только игровые объекты, имеющие VisibleComponent. Визуальное представление обновляется в основном цикле, когда обновляется VisibleFeatureServer. Затем он обновляет представление в соответствии с зарегистрированными на него VisibleComponents. Независимо от того, запрашивает ли он состояние каждой или только очереди сообщений, полученных от них, зависит от вашего приложения и базовой библиотеки визуализации.

В моем случае я использую Ogre3D. Здесь, когда VisibleComponent привязан к игровому объекту, он создает SceneNode, который привязан к графу сцены, и к сцене node Entity (представление 3d-сетки). Каждый TransformMessage (см. Ниже) обрабатывается немедленно. Затем VisibleFeatureServer делает Ogre3d перерисовыванием сцены в RenderWindow (по сути, детали сложнее, как всегда)

Сообщения

Итак, как эти функции, игровые состояния и игровые объекты общаются друг с другом? Через сообщения. Сообщение в этом проекте - это просто любой подкласс класса Message. Каждое конкретное сообщение может иметь собственный интерфейс, удобный для своей задачи.

Сообщения могут быть отправлены из одного GameObject в другие GameObjects, из GameObject в его компоненты и из FeatureServers в компоненты, за которые они отвечают.

Когда FeatureComponent создается и добавляется в игровой объект, он регистрируется в игровом объекте, вызывая myGameObject.registerMessageHandler(это MessageID) для каждого сообщения, которое он хочет получить. Он также регистрируется на своем функциональном сервере для каждого сообщения, которое он хочет получить оттуда.

Если игрок пытается поговорить с символом, который у него есть в фокусе, тогда пользователь каким-то образом вызовет действие разговора. Например: Если фокус char является дружественным NPC, то нажатием кнопки мыши запускается стандартное взаимодействие. Требование стандартного действия целевых игровых объектов запрашивается путем отправки ему GetStandardActionMessage. Целевой игровой объект получает сообщение и, начиная с первого зарегистрированного, уведомляет о своих компонентах компонента, которые хотят знать о сообщении. Первый компонент для этого сообщения затем установит стандартное действие на тот, который будет запускаться сам (TalkTargetComponent установит стандартное действие на Talk, которое он получит слишком первым), а затем отметьте сообщение как потребленное. GameObject будет тестировать потребление и видеть, что он действительно потребляется и возвращается к вызывающему. Затем измененное сообщение затем оценивается и результирующее действие вызывает

Да, этот пример кажется сложным, но он уже является одним из наиболее сложных. Другие, такие как TransformMessage для уведомления об изменении позиции и ориентации, легче обрабатывать. TransformMassage интересен многим функциональным серверам. VisualisationServer нуждается в обновлении визуального представления GameObject на экране. SoundServer для обновления положения 3D-звука и т.д.

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

Сохранение и сортировка

Это приводит нас к тому, как передавать сообщения по сети. Инкапсулируя взаимодействие GameObject/Feature с сообщениями, нам остается только беспокоиться о том, как передавать сообщения по сети. В идеале вы приносите сообщения в универсальную форму и помещаете их в пакет UDP и отправляете его. Приемник распаковывает сообщение в экземпляр соответствующего класса и направляет его на приемник или передает его в зависимости от сообщения. Я не знаю, соответствует ли Java встроенная сериализация. Но даже если нет, существует множество библиотек, которые могут это сделать.

GameObjects и компоненты делают свое постоянное состояние доступным через свойства (С++ не имеет встроенной Serialization). У них есть интерфейс, похожий на PropertyBag на Java, с которым их состояние можно восстановить и восстановить.

Ссылки

  • The Brain Dump: блог профессионального разработчика игр. Также авторы двигателя Nebula с открытым исходным кодом, игровой движок, используемый в коммерчески успешных играх. Большая часть представленного здесь проекта взята из слоя приложения туманности.
  • Примечательная статья в этом блоге, в ней описывается прикладной уровень движка. Другой подход к тому, что я пытался описать выше.
  • Длительное обсуждение о том, как выложить игровую архитектуру. В основном Ogre конкретный, но достаточно общий, чтобы быть полезным и для других.
  • Еще один аргумент для проектов на основе компонентов, с полезными ссылками внизу.

Ответ 2

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

Это зависит от того, сколько пользователей вы хотите поддерживать одновременно и возможностей вашего игрового сервера. Вы можете начать с ввода-вывода на основе текста, а затем перейти к представлению в двоичном или XML-представлении по мере созревания вашего проекта.

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

Ваш внешний анализатор создаст объекты UserAction (фактически подклассы, T extends UserAction) из уровня контроллера сети/вида- > . Это позволяет вам изменить, как ваша сеть работает по линии, не разрывая ваше основное приложение. Вероятно, вы уже думаете, что можете использовать пользовательскую сериализацию или аналогичную для сообщений с этими объектами UserAction. Этот UserAction будет передан в свою реализацию UserActionHandler (Command) с помощью Factory или просто проверит поле CommandEnum внутри коммутатора. Затем Said Handler выполнит необходимую магию на модели, и контроллер заметит изменения состояния модели и отправит уведомления другим игрокам/представлениям и т.д. И т.д.

Ответ 3

Сделайте еще один ответ для "MVC считается потенциально опасным в играх". Если ваш 3D-рендеринг является "представлением", а ваш сетевой трафик - "представлением", то не заключайте ли вы в конечном итоге с удаленными клиентами, в основном, рассматривая представление как модель? (Сетевой трафик может выглядеть как еще один механизм просмотра при его отправке, но на том конце приема, что ваша окончательная модель, на которой основана ваша игра.) Храните MVC там, где она есть, - разделение визуального представления от логики.

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

Скажем, пользователь запускает игру и выбирает символ 2. Затем пользователь перемещается в координаты (5,2). Затем он говорит в общедоступный чат, "привет!". Затем он выбирает сохранение и выход.

Держите его простым. MUDs просто отправляли команду в виде простого текста (например, "SELECT character2", "MOVE TO 5,2", "SAY Hi" ), и у вас мало причин, по которым вы не могли этого сделать, если вам удобнее писать текстовый парсер.

Более структурированной альтернативой было бы отправить простой объект XML, так как я знаю, что Java-парни любят XML;)

<message>
    <choose-character number='2'/>
</message>


<message>
    <move-character x='5' y='2'/>
</message>

<!--- etc --->

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

Ответ 4

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

Архитектура игры: Model-View-Controller