netlib.narod.ru | < Назад | Оглавление | Далее > |
Коммуникационный протокол — это «язык» на котором ведется разговор между клиентом и сервером. Он определяет способ отправки и получения сообщений, чтобы этот обмен сообщениями позволил вам поддерживать синхронизацию состояния вашей игры.
В предыдущей главе вы видели, что сообщения отправляются и получаются через объекты классов PacketWriter и PacketReader, соответственно. С помощью этих классов вы можете отправлять и получать любые типы данных, но вы должны определить протокол так, чтобы взаимодействие было эффективным.
Представьте следующую ситуацию. Вы играете по сети с другом, находящимся на другом краю мира, и внезапно включаете паузу в игре. Вы должны каким-то образом сообщить другому игроку, что поставили игру на паузу, и, следовательно, в его игре тоже должна быть сделана пауза, чтобы он не получил каких-либо преимуществ пока вы бегаете в туалет. Как «сказать» другому игроку, что вы включили паузу, и как сделать, чтобы игрок узнал, что вы вернулись к игре?
В случае Rock Rain протокол простой. Каждое сообщение, отправляемое другому игроку, состоит из заголовка, содержащего символ, описывающий, какое сообщение было послано, за которым идет само сообщение. При включении паузы в игре отправляется сообщение, показанное в таблице 6.1.
Таблица 6.1. Сообщение о паузе
Заголовок | Сообщение |
'P' | true или false, согласно состоянию паузы |
Если игрок включает паузу, другому игроку отправляется сообщение из таблицы 6.2.
Таблица 6.2. Сообщение, отправляемое когда игрок включает паузу
Заголовок | Сообщение |
'P' | true |
Когда игрок отключает паузу, вы отправляете сообщение из таблицы 6.3.
Таблица 6.3. Сообщение, отправляемое когда игрок возобновляет игру
Заголовок | Сообщение |
'P' | false |
Итак, если вы обнаруживаете, что игрок хочет включить или выключить паузу, вы должны послать эти данные объекту PacketWriter, соответствующему клиенту или серверу, в зависимости от того, кто хочет изменить состояние паузы. Для этого измените метод HandleActionInput класса Game1, добавив следующие строки:
if (enterKey) { if (actionScene.GameOver) { ShowScene(startScene); } else { audioComponent.PlayCue("menu_back"); actionScene.Paused = !actionScene.Paused; // Отправляем команду паузы другому игроку if (networkHelper.NetworkGameSession != null) { // Если мы сервер, отправляем используя пакеты сервера if (networkHelper.NetworkGameSession.IsHost) { networkHelper.ServerPacketWriter.Write('P'); networkHelper.ServerPacketWriter.Write( actionScene.Paused); } else { networkHelper.ClientPacketWriter.Write('P'); networkHelper.ClientPacketWriter.Write( actionScene.Paused); } } } if (backKey) { if (networkHelper.NetworkGameSession != null) { CloseSession(); networkScene.State = NetworkScene.NetworkGameState.idle; networkScene.Message = ""; ShowScene(networkScene); } else { ShowScene(startScene); } } }
Как видите, вы сперва помещаете заголовок сообщения ('P') в ClientPacketWriter или ServerPacketWriter, а затем включаете само сообщение (actionScene.Paused) и, таким образом, теперь сообщение отформатировано и готово к отправке.
Вы также добавили новый код к обработке кнопки Back. Если она активируется в ходе сетевой игры, код разрывает сетевое соединение игры и возвращается к сетевой сцене, вместо того, чтобы просто вернуться к начальной сцене.
Теперь вы должны прочитать это сообщение, интерпретировать его, и изменить состояние игры (включить или выключить паузу) в соответствии с содержимым сообщения. Хорошим стилем разработки считается размещение метода, имеющего дело с сообщениями, внутри класса, содержащего само состояние игры. В случае Rock Rain это класс, представляющий сцену игры.
Прежде, чем делать что-либо еще, вам необходим ваш объект NetworkHelper. Итак, объявим его в классе ActionScene:
// Сетевые ресурсы private readonly NetworkHelper networkHelper;
Инициализируем его в конструкторе класса:
// Получаем текущее состояние сервера для // сетевой многопользовательской игры networkHelper = (NetworkHelper) Game.Services.GetService(typeof (NetworkHelper));
Теперь вы создадите два метода в классе ActionScene: один для интерпретации сообщений, которые приходят от клиента, и другой — для сообщений сервера. Итак, добавим следующий метод в класс ActionScene:
/// <summary> /// Обработка всех данных, поступающих от клиента /// </summary> public void HandleClientData() { while (networkHelper.ClientPacketReader.PeekChar() != -1) { char header = networkHelper.ClientPacketReader.ReadChar(); switch (header) { case 'P': Paused = networkHelper.ClientPacketReader.ReadBoolean(); break; } } }
Этот метод будет вызываться, когда вам потребуется интерпретировать какое-либо сообщение, поступившее от удаленного игрока (клиента). Заметьте, что цикл while перебирает все PacketReader клиента для чтения всех сообщений, таким же образом, как это делалось в предыдущей главе, и их интерпретации соответствующим образом. В случае сообщения 'P', для паузы, все, что вы делаете, это присваивание значения из сообщения атрибуту Paused сцены, который включает и отключает паузу.
Для сообщения о паузе, которое приходит от сервера, код практически тот же самый:
/// <summary> /// Обрабатываем все данные, поступающие от сервера /// </summary> public void HandleServerData() { while (networkHelper.ServerPacketReader.PeekChar() != -1) { char header = networkHelper.ServerPacketReader.ReadChar(); switch (header) { case 'P': Paused = networkHelper.ServerPacketReader.ReadBoolean(); break; } } }
Единственное отличие в том, что вы здесь используете PacketReader сервера. Обратите внимание, что поскольку сервер управляет состоянием игры, здесь создается и интерпретируется много новых сообщений, в то время клиент отправляет только это сообщение о паузе и другое сообщение с местоположением удаленного игрока. Мы вернемся к этим методам позже.
Теперь вам надо время от времени вызывать эти методы; значит надо поместить всю отправку и получение сетевых данных в игровой цикл. Таким же образом, как мы делали в предыдущей главе, добавим все это в метод Update класса Game1 и используем методы класса NetworkHelper для отправки и получения данных. Итак, добавим следующий код к методу Update класса Game1:
// Поддержка сетевой сессии if (networkHelper.NetworkGameSession != null) { // Только отправляем, если мы не сервер. // Нет никакого смысла отправлять пакеты самому себе, // поскольку мы уже знаем, что они будут содержать! if (!networkHelper.NetworkGameSession.IsHost) { networkHelper.SendClientData(); } else { // Если мы сервер, передаем состояние игры networkHelper.SendServerData(); } // Проталкиваем данные networkHelper.NetworkGameSession.Update(); // Читаем любые входящие сетевые пакеты foreach (LocalNetworkGamer gamer in networkHelper.NetworkGameSession.LocalGamers) { // Продолжаем чтение, пока есть входящие пакеты. while (gamer.IsDataAvailable) { NetworkGamer sender; if (gamer.IsHost) { sender = networkHelper.ReadClientData(gamer); if (!sender.IsLocal) { actionScene.HandleClientData(); } } else { sender = networkHelper.ReadServerData(gamer); if (!sender.IsLocal) { actionScene.HandleServerData(); } } } } }
Итак, в каждом игровом цикле вы всегда читаете и отправляете необходимые пакеты данных.
Вы также должны предоставить доступ к объектам Player, чтобы связывать их с сетевым классом Gamer для каждого игрока, присоединившегося к игровой сессии. Добавьте следующий код:
public Player Player1 { get { return player1; } } public Player Player2 { get { return player2; } }
Теперь давайте добавим новые сообщения для других игровых состояний.
netlib.narod.ru | < Назад | Оглавление | Далее > |