Как подключить эту систему сущностей к сети?

33

Я разработал систему сущностей для FPS. Это в основном работает так:

У нас есть «мир» -объект, называемый GameWorld. Он содержит массив GameObject, а также массив ComponentManager.

GameObject содержит массив Component. Он также предоставляет механизм событий, который действительно прост. Сами компоненты могут отправлять событие объекту, которое передается всем компонентам.

Компонент - это то, что придает GameObject определенные свойства, и, поскольку GameObject на самом деле является их контейнером, все, что связано с игровым объектом, происходит в Компонентах. Примеры включают ViewComponent, PhysicsComponent и LogicComponent. Если связь между ними необходима, это можно сделать с помощью событий.

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

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

Для настройки я буду использовать фабрику для сущностей, которая будет читать либо XML-файл, либо скрипт, создавать компоненты, указанные в файле (который также добавляет ссылку на него в правильном диспетчере компонентов для массовых обновлений), и затем вставьте их в объект GameObject.

Теперь возникает моя проблема: я собираюсь попытаться использовать это для многопользовательских игр. Я понятия не имею, как подойти к этому.

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

В редакторе уровней вы можете создавать «кисти» и «сущности». Щетки предназначены для таких вещей, как стены, полы и потолки, в основном простых форм. Сущности - это GameObject, о котором я вам говорил. При создании сущностей в редакторе уровней вы можете указать свойства для каждого из его компонентов. Эти свойства передаются непосредственно какому-либо конструктору в скрипте объекта.

Когда вы сохраняете уровень для загрузки движка, он раскладывается на список сущностей и связанных с ними свойств. Кисти превращаются в сущность "мировое возрождение".

Когда вы загружаете этот уровень, он просто создает все сущности. Звучит просто, а?

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

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

Другой вопрос, как отправить информацию. Я думаю, что сервер может использовать дельта-сжатие, что означает, что он отправляет новую информацию только тогда, когда что-то меняется, а не отправляет снимок клиенту при каждом кадре. Хотя это означает, что сервер должен отслеживать, что каждый клиент знает в данный момент.

И, наконец, как сеть должна быть введена в двигатель? Я имею в виду компонент NetworkComponent, который внедряется в каждую сущность, которая должна быть подключена к сети. Но как сетевой компонент должен знать, какие переменные должны быть в сети, и как получить к ним доступ, и, наконец, как соответствующий сетевой компонент на клиенте должен знать, как изменять сетевые переменные?

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

возчик
источник

Ответы:

13

Это чертовски (простите) зверь вопроса с большим количеством деталей +1 там. Определенно достаточно, чтобы помочь людям, которые наткнулись на это.

Я просто хотел добавить свои 2 цента о том, что физические данные не отправляются !! Я, честно говоря, не могу подчеркнуть это достаточно. Даже если вы до сих пор оптимизировали его так, что вы можете практически отправить 40 сфер, которые подпрыгивают с микроколлизиями, и он может разогнаться до полной скорости в потрясающей комнате, которая даже не уменьшает частоту кадров. Я имею в виду выполнение вашего «дельта-сжатия / кодирования», также известного как различие данных, которое вы обсуждали. Это очень похоже на то, что я собирался поднять.

Мертвая расплата VS Дифференциация данных: они достаточно разные и на самом деле не используют одни и те же методы, а это означает, что вы могли бы реализовать оба варианта, чтобы еще больше повысить оптимизацию! Примечание: я не использовал их вместе, но работал с обоими.

Дельта-кодирование или различие данных: сервер передает данные о том, что знают клиенты, и отправляет только различия между старыми данными и тем, что следует изменить клиенту. например, псевдо-> в одном примере вы можете отправить данные «315 435 222 3546 33», когда данные уже «310 435 210 4000 40» Некоторые из них только слегка изменены, а один не изменился вообще! Вместо этого вы отправите (в дельте) "5 0 12 -454 -7", что значительно короче.

Лучшими примерами могут быть что-то, что меняется намного дальше, чем, например, скажем, у меня есть связанный список с 45 связанными объектами в нем прямо сейчас. Я хочу убить 30 из них, поэтому я делаю это, а затем отправляю всем, что представляют собой новые пакетные данные, которые замедлили бы работу сервера, если бы он еще не был создан для таких вещей, и это произошло потому, что он пытался исправить себя например. В дельта-кодировании вы бы просто поместили (псевдо) "list.kill 30 at 5", и он уберет 30 объектов из списка после 5-го, после чего аутентифицирует данные, но на каждом клиенте, а не на сервере.

Плюсы: (могу думать только об одном из каждого прямо сейчас)

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

Минусы:

  1. Если вы обновляете свою систему и хотите добавить больше данных, которые должны быть отредактированы через дельту, вам нужно будет создать новые функции для изменения этих данных! (например, как и ранее "list.kill 30 at 5" О, черт, мне нужен метод отмены, добавленный к клиенту! "list.kill undo")

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

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

Допустим, мой персонаж движется в каком-то направлении, многие серверы будут отправлять клиентам данные, которые сообщают (почти за кадр), где находится игрок и что он движется (по причинам анимации). Это так много ненужных данных! Почему, черт возьми, мне нужно обновлять каждый отдельный кадр, где находится юнит и в каком направлении он направлен И что он движется? Проще говоря: нет. Вы обновляете клиентов только тогда, когда меняется направление, когда меняется глагол (isMoving = true?) И что это за объект! Тогда каждый клиент будет перемещать объект соответственно.

Лично это тактика здравого смысла. Это то, что, как я думал, я придумал, когда придумал давным-давно, и оказалось, что оно используется постоянно.

ответы

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

Лично я бы создавал данные на клиенте, когда он получает информацию об этом с сервера (что вы предложили).

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

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

Что касается того, как он должен знать, какие переменные должны быть в сети, я мог бы иметь компонент, который действительно является SUB-объектом, и дать ему компоненты, которые вы хотели бы подключить к сети. Другая идея - не только иметь, AddComponent("whatever")но и AddNetComponent("and what have you")просто потому, что это звучит умнее лично.

Джошуа Хеджес
источник
Это смехотворно длинный ответ! Я ужасно сожалею об этом. Поскольку я намеревался предоставить лишь небольшое количество знаний, а затем мои 2 цента о некоторых вещах. Так что я понимаю, что многое из этого может быть немного ненужным, чтобы отметить.
Джошуа Хеджес
3

Собирался написать комментарий, но решил, что это может быть достаточно информации для ответа.

Во-первых, +1 за такой хорошо написанный вопрос с тоннами деталей, по которым можно судить об ответе.

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

Во-вторых, не создавайте компонент NetworkComponent, так как он будет делать только репликацию данных в других существующих компонентах (физика, анимация и т. П. - некоторые общие вещи для отправки). Чтобы использовать собственное наименование, вы можете создать NetworkComponentManager. Это будет немного отличаться от других ваших отношений между Component и ComponentManager, но это может быть реализовано, когда вы запускаете сетевую игру, и компоненты любого типа, имеющие сетевой аспект, передают свои данные менеджеру, чтобы он мог их упаковать. и отправь это. Это где ваши функции сохранения / загрузки могут быть использованы, если у вас есть какой-то механизм сериализации / десериализации, который вы также можете использовать для упаковки данных, как уже упоминалось,

Учитывая ваш вопрос и уровень информации, я не думаю, что мне нужно вдаваться в подробности, но если что-то не ясно, пожалуйста, оставьте комментарий, и я обновлю ответ, чтобы решить эту проблему.

Надеюсь это поможет.

Джеймс
источник
Итак, вы говорите, что компоненты, которые должны быть подключены к сети, должны реализовывать некоторый интерфейс, подобный следующему: void SetNetworkedVariable (имя строки, значение NetworkedVariable); NetworkedVariable GetNetworkedVariable (имя строки); Где NetworkedVariable используется для целей интерполяции и других сетевых вещей. Я не знаю, как определить, какие компоненты, которые реализуют это, хотя. Я мог бы использовать идентификацию типа во время выполнения, но это кажется мне уродливым.
Картер