| 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 | < Назад | Оглавление | Далее > |