netlib.narod.ru< Назад | Оглавление | Далее >

Синхронизация игроков

Что определяет состояние игрока? Не только местоположение игрока на экране, но также его счет и уровень энергии. Итак, чтобы состояние игры было синхронизированным, вам надо информировать другого игрока о состоянии его оппонента. Создадим для этого новое сообщение, показанное в таблице 6.4.


Таблица 6.4. Сообщение с состоянием игрока



Заголовок Сообщение

'S' Местоположение, счет, энергия


Сообщение 'S' передает всю необходимую для игрока информацию, и оба игрока (локальный игрок player1 и удаленный игрок player2) должны пересылать свое состояние через сеть.

Для удаленного игрока добавим следующий код в метод HandleClientData класса ActionScene:

case 'S':
    player2.Position =
        networkHelper.ClientPacketReader.ReadVector2();
    player2.Power =
        networkHelper.ClientPacketReader.ReadInt32();
    player2.Score =
        networkHelper.ClientPacketReader.ReadInt32();
    break;

Итак, если это сообщение 'S', за ним следует местоположение игрока (объект Vector2), а также счет игрока и уровень энергии (объекты Int32). Вам надо только обновить атрибуты объекта player2 этими полученными значениями.

Аналогично добавим следующий код, имеющий дело с перемещениями игрока на стороне сервера — на этот раз в метод HandleServerData:

case 'S':
    player1.Position =
        networkHelper.ServerPacketReader.ReadVector2();
    player1.Power =
        networkHelper.ServerPacketReader.ReadInt32();
    player1.Score =
        networkHelper.ServerPacketReader.ReadInt32();
    break;

Вы должны изменить класс Player (который представляет объекты player1 и player2), чтобы отправлять местоположение игрока через сеть. Фактически, класс должен заменять на остановку любое изменение его состояния удаленным игроком. Если изменение допустимо (такое, как смена местоположения), сообщение должно отправить эти изменения на сервер.

Добавление поддержки сети к классу Player

Если вы добавляете поддержку сети, вам нужен ваш экземпляр класса NetworkHelper. Итак, объявим его в классе Player:

// Сетевые ресурсы
private readonly NetworkHelper networkHelper;

Затем инициализируем его в конструкторе класса:

// Получаем текущее состояние сервера для сетевой многопользовательской игры
networkHelper = (NetworkHelper)
            Game.Services.GetService(typeof (NetworkHelper));

Теперь давайте изменим метод Update этого класса, чтобы он отправлял сообщение 'S' с состоянием корабля. Измените код метода следующим образом:

if (networkHelper.NetworkGameSession != null)
{
    if (gamer.IsLocal)
    {
        // Локальный игрок всегда использует
        // основной игровой пульт и клавиши клавиатуры
        HandleInput(PlayerIndex.One);
        UpdateShip(gameTime);
        UpdateNetworkData();
    }
}
else
{
    HandleInput(playerIndex);
    UpdateShip(gameTime);
}

Заметьте, что отправляются только сообщения локального игрока, поскольку вам не надо отправлять изменения удаленного игрока ему обратно. Также, в случае многопользовательской игры через сеть, двум игрокам не надо делить клавиатуру или использовать два игровых пульта, так что они всегда используют один и тот же игровой пульт или клавиши клавиатуры.

Показанный ниже метод UpdateNetworkData создает сообщения, которые должны быть отправлены:

/// <summary>
/// Обновление данных сервера с информацией корабля
/// </summary>
private void UpdateNetworkData()
{
    if (networkHelper.NetworkGameSession.IsHost)
    {
        networkHelper.ServerPacketWriter.Write('S');
        networkHelper.ServerPacketWriter.Write(position);
        networkHelper.ServerPacketWriter.Write(power);
        networkHelper.ServerPacketWriter.Write(score);
    }
    else
    {
        networkHelper.ClientPacketWriter.Write('S');
        networkHelper.ClientPacketWriter.Write(position);
        networkHelper.ClientPacketWriter.Write(power);
        networkHelper.ClientPacketWriter.Write(score);
    }
}

Здесь данные сообщения добавляются в соответствующий PacketWriter, точно так же, как вы делали раньше. Код, добавленный вами к методу Update класса Game1 отправит также и эти данные, а методы HandleClientData и HandleServerData класса ActionScene обработают их таким же способом, как они обрабатывают сообщение о паузе. Таким же образом вы добавляете поддержку сети ко всем другим объектам, которые содержат какое-нибудь состояние игры.

Добавление поддержки сети к классу PowerSource

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

Итак, создадим сообщение, говорящее о местоположении этого предмета (таблица 6.5).


Таблица 6.5. Сообщение о местоположении источника энергии



Заголовок Сообщение

'L' Местоположение


Это состояние только хранится на сервере. Так что добавьте следующий код к методу HandleServerData класса ActionScene:

case 'L':
    powerSource.Position =
        networkHelper.ServerPacketReader.ReadVector2();
    break;

Видите повтор? Великолепно! Теперь добавьте атрибут типа NetworkHelper и инициализируйте его в конструкторе класса PowerSource, точно так же, как вы делали с классом Player, а затем измените метод Update следующим образом:

/// <summary>
/// Позволяет игровому компоненту обновлять себя
/// </summary>
/// <param name="gameTime">Предоставляет снимок значения таймера</param>
public override void Update(GameTime gameTime)
{
    if ((networkHelper.NetworkGameSession == null) ||
        (networkHelper.NetworkGameSession.IsHost))
    {
        // Проверяем, остается ли объект видимым
        if (position.Y >= Game.Window.ClientBounds.Height)
        {
            PutinStartPosition();
        }

        // Перемещение
        position.Y += 1;

        networkHelper.ServerPacketWriter.Write('L');
        networkHelper.ServerPacketWriter.Write(position);
    }

    base.Update(gameTime);
}

Таким образом, метод Update только обновляет местоположение объекта, что происходит на стороне сервера. Метод HandleServerData устанавливает местоположение объекта на стороне клиента, согласно данным, полученным от экземпляра, выполняющегося на стороне сервера, чтобы обе стороны оставались синхронизированными.

Вы уже синхронизировали игроков, источники энергии и игровую паузу. Остались только астероиды.

Добавление сетевой поддержки для астероидов

Астероиды в игре представлены двумя различными классами: классом Meteor, который представляет собственно спрайт астероида, и классом MeteorsManager, представляющим в игре целое поле астероидов. Каждый из них меняет игровое состояние своим собственным способом, и вы по отдельности измените их код для добавления поддержки сетевой игры.

В классе Meteor только методы PutinStartPosition и Update меняют атрибуты экземпляра. Поэтому вы измените эти методы. Но какое сообщение мы будем отправлять, чтобы представить состояние астероида?

В Rock Rain каждый астероид меняет только свое местоположение на экране, так что вы должны отправлять сообщение, показанное в таблице 6.6.


Таблица 6.6. Сообщение с местоположением астероида



Заголовок Сообщение

'R' Индекс, местоположение


Каждый астероид на экране отправляет такое сообщение, чтобы информировать клиента о своем местоположении в игре. Поскольку значение свойства Index позволяет идентифицировать каждый астероид, будем передавать его вместе с остальными сведениями, чтобы клиент знал, о местоположении какого астероида его проинформировали.

Сначала добавим инициализацию экземпляра класса NetworkHelper, как мы уже делали. Изменим метод PutinStartPosition:

/// <summary>
/// Инициализация местоположения и скорости астероида
/// </summary>
public void PutinStartPosition()
{
    // Только сервер может устанавливать атрибуты астероида
    if ((networkHelper.NetworkGameSession == null) ||
        (networkHelper.NetworkGameSession.IsHost))
    {
        position.X = random.Next(Game.Window.ClientBounds.Width -
                                              currentFrame.Width);
        position.Y = 0;
        YSpeed = 1 + random.Next(9);
        XSpeed = random.Next(3) - 1;
    }
}

Следующий код предназначен для метода Update:

/// <summary>
/// Обновление местоположения астероида
/// </summary>
public override void Update(GameTime gameTime)
{
    // Проверяем, остается ли видим астероид
    if ((position.Y >= Game.Window.ClientBounds.Height) ||
        (position.X >= Game.Window.ClientBounds.Width)  ||
        (position.X <= 0))
    {
        PutinStartPosition();
    }

    // Перемещаем астероид
    position.Y += Yspeed;
    position.X += Xspeed;

    // Отправляем данные астероида клиенту
    if ((networkHelper.NetworkGameSession != null) &&
        (networkHelper.NetworkGameSession.IsHost))
    {
        networkHelper.ServerPacketWriter.Write('R');
        networkHelper.ServerPacketWriter.Write(index);
        networkHelper.ServerPacketWriter.Write(position);
    }

    base.Update(gameTime);
}

Это также сообщение, которое отправляется только сервером, поэтому обрабатывать его будем в методе HandleServerData класса ActionScene, аналогично тому, как мы делаем это с другими сообщениями:

case 'R':
    int meteorId = networkHelper.ServerPacketReader.ReadInt32();
    meteors.AllMeteors[meteorId].Position =
                 networkHelper.ServerPacketReader.ReadVector2();
    break;

В показанном выше коде вы просто устанавливаете местоположение указанного астероида, согласно полученным в сообщении данным. Делая это для каждого астероида, вы, таким образом, синхронизируете местоположение всех их в ходе игры.

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

Итак, добавим и инициализируем экземпляр объекта NetworkHelper и изменим метод Start класса MeteorsManager:

/// <summary>
/// Запускаем астероидный дождь
/// </summary>
public void Start()
{
    if ((networkHelper.NetworkGameSession == null) ||
        (networkHelper.NetworkGameSession.IsHost))
    {
        // Инициализируем счетчик
        elapsedTime = TimeSpan.Zero;

        // Добавляем астероиды
        for (int i = 0; i < STARTMETEORCOUNT; i++)
        {
            AddNewMeteor();
        }
    }
}

Таким образом, только сервер способен добавлять новые астероиды, и, когда новый астероид добавлен, вы должны отправить сообщение, чтобы проинформировать клиента. Это сообщение содержит атрибуты астероида (см. таблицу 6.7).


Таблица 6.7. Сообщение с состоянием астероида



Заголовок Сообщение

'M' Индекс, местоположение, горизонтальная скорость, вертикальная скорость


Затем изменим метод AddNewMeteor, чтобы отправлять сообщение с атрибутами нового астероида:

/// <summary>
/// Добавляем к сцене новый астероид
/// </summary>
/// <returns>Новый астероид</returns>
private Meteor AddNewMeteor()
{
    Meteor newMeteor = new Meteor(Game, ref meteorTexture);
    newMeteor.Initialize();
    meteors.Add(newMeteor);
    newMeteor.Index = meteors.Count - 1;

    // Отправляем данные нового астероида клиенту
    if ((networkHelper.NetworkGameSession != null) &&
        (networkHelper.NetworkGameSession.IsHost))
    {
        networkHelper.ServerPacketWriter.Write('M');
        networkHelper.ServerPacketWriter.Write(newMeteor.Index);
        networkHelper.ServerPacketWriter.Write(newMeteor.Position);
        networkHelper.ServerPacketWriter.Write(newMeteor.XSpeed);
        networkHelper.ServerPacketWriter.Write(newMeteor.YSpeed);
    }

    return newMeteor;
}

И снова обработаем это сообщение в методе HandleServerData класса ActionScene, чтобы новый астероид был добавлен в список астероидов на стороне клиента:

case 'M':
    int index = networkHelper.ServerPacketReader.ReadInt32();
    Vector2 position =
        networkHelper.ServerPacketReader.ReadVector2();
    int xspeed = networkHelper.ServerPacketReader.ReadInt32();
    int yspeed = networkHelper.ServerPacketReader.ReadInt32();

    meteors.AddNewMeteor(index,position,xspeed,yspeed);

    break;

Теперь движение, а также добавление новых астероидов синхронизированы с клиентом.

Осталась третья точка, где меняется состояние игры: проверка необходимости добавления нового астероида в методе Update. Только сервер может выполнять эту операцию, так что измените метод следующим образом:

// Только сервер может добавлять новые астероиды
if ((networkHelper.NetworkGameSession == null) ||
    (networkHelper.NetworkGameSession.IsHost))
{
    CheckforNewMeteor(gameTime);
}

Вот так! Все объекты теперь синхронизированы и сервер управляет всеми игровыми состояниями и отправляет сообщения клиенту, чтобы сохранять синхронизированность игры. Клиент получает все эти сообщения и меняет состояние своих объектов в соответствии с содержащейся в сообщениях информацией, чтобы поддерживать сетевой матч так, будто вы находитесь рядом с вашим противником. Рис. 6.3 показывает сетевой трафик между игроками. Теперь позвоните вашему другу, который живет в Японии и предложите ему сыграть в Rock Rain!


Рис. 6.3. Общение между сервером и клиентом

Рис. 6.3. Общение между сервером и клиентом



netlib.narod.ru< Назад | Оглавление | Далее >

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