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);
        }
    }
}

 

ПРИМЕЧАНИЕ
Будьте осторожны, определяя формат ваших сообщений. Сетевой трафик сильно влияет на производительность сетевых игр. Вообще, старайтесь использовать как можно меньше трафика, чтобы сервер не обрабатывал сообщения слишком долго. Помимо модели клиент/сервер XNA предоставляет одноранговую (P2P) модель, которая может оказаться более подходящей для игр с чрезмерным обменом сообщениями или с большими состояниями, таких как крупномасштабные многопользовательские сетевые игры.

Как видите, вы сперва помещаете заголовок сообщения ('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< Назад | Оглавление | Далее >

Сайт управляется системой uCoz