Автор: | StrVL [ 31.08.2010, 19:27 ] |
Тема блога: | Мультиплеер в trainz. Принцип. Технология. Протокол |
Несколько дней назад я обещал опубликовать мультиплеерный протокол для trainz. Вот, момент настал, решил назначить релиз на сегодня. Однако ж прежде следует ввести в курс дела тех, кто не читал. Как такового мультиплеера в trainz никогда не было и вряд ли он появится в ближайшее время. Тем не менее, как оказалось, реализовать его своими руками вполне возможно и даже несложно (правда, как говорил Эйнштейн, все в мире относительно ). Возможности траинзэтовсского скриптового движка позволяют так или иначе контролировать весь игровой процесс. То, что они не позволяют реализовать прямо, часто поддается реализации путем различных форм извращений над скриптами. Единственная не решаемая скриптом проблема состояла в том, чтобы каким-либо образом передать данные во «внешний мир». Немного общих слов. Этот вопрос был решен написанием внешнего приложения, внедряющегося в адресное пространство процесса trainz.exe и обменивающимся данными со скриптом через участок его памяти. Намеренно не стану сообщать, на каком языке она написана, так как тут же появятся [strike]проповедники языка истинного[/strike] «вумные» [strike]сионисты и пасквилянты[/strike] программисты , которым никак [strike]неймется[/strike] не удается [strike]не облить грязью всех вокруг и[/strike] свой [strike]фанатизм[/strike] богатый запас знаний [strike]засунуть в одно место[/strike] донести до [strike]«быдлокодеров»[/strike] невежественной толпы. Не буду вдаваться и в подробности реализации, скажу лишь самое основное. Участок памяти представляет собой строковый массив, единожды размещаемый скриптом в памяти. При инициализации в этот массив помещается особая сигнатура (к слову, всегда одна и та же), которую впоследствии и отыскивает в памяти процесса программа среди океана байт. Считывание-запись данных осуществляется по очереди то скриптом, то программой (определяется состоянием первого байта массива). Размер участка памяти строго фиксирован (пока 10 Кбайт) и в процессе игры не изменяется (впоследствии размер будет задаваться в правилах сессии). Кстати говоря, следует сказать пару слов по поводу следующего высказывания
Не спорю, ковыряться в чужом адресном пространстве не безопасно, но за время тестирования (более 50 запусков) не произошло ни одного "вылета" (до тех пор, пока не занялся голосовым чатом и не начал развешивать глобальный хук на клавиатуру ). Обмен данными между экземплярами программ-клиентов по сети осуществляется через DirectPlay. Что до вумных программистов, считающих интерфейсами зла все кроме сокетов, [strike]то пусть продолжают заниматься с winsock многопоточным онанизмом[/strike] желаю им приятного времяпровождения при конструировании давно изобретенных велосипедов. Так как на моих глючных F{пи-и-ик}YouBill’ьных окошках DirectX версии 8 и более новые частично не работает (зато отлично работает то, что криво работало в «чистой» системе ), пришлось довольствоваться DP из состава DirectX 7. Данные по сети передаются в сжатом с помощью библиотеки zlib виде. На настоящий момент реализован также голосовой чат. Чтобы можно было устанавливать соединения с кем-либо, нужно назначить во вкладке «голосовой чат» соответствующую горячую клавишу. Управление чатом следующее: нажимаем на клавишу «Alt», затем не отпуская его жмем и отпускаем назначенную данному игроку клавишу (Alt не отпускаем) – всё, «исходящий канал открыт». Для закрытия канала нужно всего-навсего отпустить «Alt». Голосовые сообщения отправляются по частям (1 часть – 0,2 мсек) также в сжатом виде. Для сжатия используется кодек speex (ох, и запыхался же с ним! ). Битрейт, однако же, получается просто неблагопристойно большой – 15-20 Кбайт/сек на сообщение (несмотря на то, что кодеку назначено сжимать до 15 Кбит/сек). Поддерживается только одно одновременное исходящее соединение (при этом оно может быть широковещательным и транслироваться сразу всем) и теоретически неограниченное количество входящих. Пожалуй, довольно лелеять функционал программы-клиента. Тем более, что сама программа ещё до конца не отлажена, и то ли из-за несовместимости глобальных клавиатурных хуков с отладчиком, то ли из-за неправильного использования CopyMemory (никак не получается сыскать источник-рассадник багов), все вместе с отладчиком систематически накрывается медным тазом, сопровождаясь победоносным восклицанием Билла: «память не может быть “read”!». (знаете, порой так хочется открыть kernel32.exe в каком-нибудь PELord’е, откуда родом эта CopyMemory, и в константе подменить «read» на какое-нибудь непристойное выражение… ) Протокол Полагаю, что всех уже утомил своими пустыми разглагольствованиями, поэтому этот раздел постараюсь написать коротко и ясно. За передачу данных в скрипте от игры до игры отвечает предназначенный для этой цели статический класс Modem. Данные передаются вот такими пакетами:
Пара слов о типе. Много разных дополнений могут одновременно посылать и получать пакеты. Чтобы различные объекты не путали пакеты, и таким образом не воцарился хаос, каждому пакету назначен тип, определяющий, какой объект его породил. При отправке пакета в поле type нужно ОБЯЗАТЕЛЬНО занести kuid вашего объекта. Можно, конечно, указать чужой kuid, но во избежание путаницы этого лучше не делать. В поле DestPlayerNum может принимать не только значения номеров игроков получателя, но и дополнительные 2 специальных значения: -1 – отправка служебного сообщения программе (для отправки специальных сообщений, например, управляющих голосовым чатом) -2 – отправка широковещательного сообщения (сообщения, отправляемого всем игрокам одновременно) Поле SrcPlayerNum можно не указывать: оно будет назначено при отправке сообщения. Так как фрагментация пакетов не поддерживается, длина пакета ограничена размером выходного буфера, и вам следует следить, чтобы она не была превышена (иначе пакет не будет помещен в очередь). Для этого у класса Modem есть метод public bool VeryBigPacket(Packet p), возвращающий истину, если пакет не умещается в буфере отправки. У класса Modem есть много различных public-методов, но в вашем распоряжении есть только следующие (остальные, что называется «системные»):
В поле TypeOfIncomingPackets ОБЯЗАТЕЛЬНО должен быть установлен куид пакетов, которые обрабатываются этим обработчиком. Никакие другие пакеты в данный обработчик поступать не будут (в принципе можно указать любой куид, не обязательно, чтобы он совпадал с куидом вашего объекта). При поступлении каждого сообщения с указанным куидом вызвается метод RecievePacket (блин, только сейчас узнал, что правильно пишется Receive ). Метод ConnectionOptionsChanged вызывается при каких-либо изменениях состояния соединения (установлено/разорвано соединение, подключился новый/отключился существующий игрок, …). Естественно, просто так обработчик сообщений исполнять своих функций не будет. Чтобы он начал обрабатывать сообщения, ссылку на него нужно передать модему, для чего у последнего есть процедура AddHandler. Она помещает ссылку на обработчик в особую иерархическую структуру – список обработчиков, после чего пакеты с соответствующим куидом начнут обрабатываться. На один и тот же куид может быть назначено несколько обработчиков. Можно и удалить обработчик из списка с помощью метода RemoveHandler. Существует статический класс LocalPlayerOwnership для описания объектов, доступ к которым локальному игроку открыт. Можно использовать его методы, чтобы определить, управляет ли некоторым объектом локальный игрок:
|
Автор: | TRam_ [ 31.08.2010, 21:33 ] |
>Размер участка памяти строго фиксирован (пока 10 Кбайт) и в процессе игры не изменяется (впоследствии размер будет задаваться в правилах сессии). о, я такое делал . Ты б знал, сколько меня ругали за то, что "размер области фиксирован". |
Автор: | UTUBE [ 01.09.2010, 00:22 ] |
Всем добрый вечер.Ну а как этим всем пользоваться,или ещё рано пока? Спасибо. |
Автор: | kemal [ 01.09.2010, 20:31 ] |
А обязательно писать Modem.AddHandler(me); ? Или можно сделать так, чтобы приёмом сообщений занимался отдельный класс? |
Автор: | AlexanderG [ 01.09.2010, 21:11 ] |
>В поле DestPlayerNum может принимать не только значения номеров игроков получателя, но и дополнительные 2 специальных значения: -1 – отправка служебного сообщения программе (для отправки специальных сообщений, например, управляющих голосовым чатом) -2 – отправка широковещательного сообщения (сообщения, отправляемого всем игрокам одновременно)< Лучше не использовать -1, это общепринятый аналог отсутствующего/ошибочного значения. |
Автор: | StrVL [ 01.09.2010, 21:24 ] |
В смысле, нужно ли делать обработчиком класс, представляющий реальный игровой объект (т. е. наследуемый от какого-нибудь Buildable/BaseIndustry и прочих и указываемый в конфиге)? Нет, не обязательно, можно написать отдельный класс-обработчик, но AddHandler вызывать все же необходимо:
Не вижу смысла использовать номер игрока в качестве признака ошибочного сообщения. Да и вообще я как бы не выделял "ошибочные" сообщения (а пустые тем более). В случае ошибки (неверный номер игрока, превышение лимита по размеру сообщения, неверный тип) сообщение просто не будет помещено в очередь отправки или отброшено в процессе доставки, не доходя до получателя. |
Автор: | agmike [ 06.09.2010, 16:20 ] |
Ох вау. Хочется только спросить, есть/планируется ли возможность передачи данных из ТРС в другие программы и наоборот, самое очевидное применение этому — выносной контроллер. Хотя я вряд ли стал бы это делать, меня больше волнует возможность добавить в игру новые клавиши с клавиатуры, желательно все. |
Автор: | StrVL [ 06.09.2010, 19:19 ] |
Возможность подключения «плагинов» запланирована, но в первой версии однозначно её не будет. Что касается клавиш, то уже сейчас в программе используется отслеживание нажатия всех клавиш клавиатуры (нужно для голосового чата). Могу немного дописать код так, чтобы соответствующие события дополнительно посылались в скрипт trainz’, и в последнем рассылалось широковещательное сообщение (PostMessage) о нажатии клавиши. Кстати, есть небольшая вероятность, что небольшая модификация кода позволит также «блокировать» нажатия заданных клавиш, чтобы оное не возымело какого-нибудь стандартного действия в trainz. Небольшая потому, что скорее всего trainz работает с клавиатурой напрямую средствами DirectInput (или аналога). В этом случае, как говорится, «номер не пройдет». Однако нужно ли это? Просто мне кажется, что большинство отнесется крайне скептически (либо лениво) к локомотиву, для работы которого потребуется где-то дополнительно скачивать и, самое главное, каждый раз запускать «какую-то» прогу. Пара слов о стадии разработки мультиплеера (некоторых, наверное, интересует …). Сейчас работа ведется над совершенствованием механизма синхронности движения поездов, а также над устранением «косяков». Всплыло несколько частных случаев, в которых данный механизм дает сбой. Готова синхронная подача свистка, управление пантографами и светом лобового прожектора. |
Автор: | agmike [ 06.09.2010, 22:57 ] |
За клавиши голосую, броадкастом. Блокировать ничего не надо, кейбоард.тхт легко редактируется и реально в нем используется пара десятков строк максимум. |
Автор: | AlexanderG [ 08.09.2010, 11:12 ] |
Насчет плагинов, у меня, когда я этим вопросом занимался, была мысль использовать сокеты — общаться с плагинами через лупбек-интерфейс. Это просто программируется и обеспечивает всю необходимую функциональность. |
Автор: | AlexanderG [ 11.09.2010, 00:42 ] |
Ну что, все сдохло? |
Часовой пояс: UTC + 4 часа | |
Powered by phpBB © 2002, 2006 phpBB Group www.phpbb.com |
Blogs powered by User Blog Mod © EXreaction www.lithiumstudios.org |