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

Знакомство с работой с сетью в XNA

XNA 2.0 предоставляет через пространства имен Microsoft.Xna.Framework.GamerServices и Microsoft.Xna.Framework.Net набор функций и компонентов, позволяющих создавать главные узлы многопользовательских игр (к которым могут подключаться другие игроки), поддерживать соединения и обмен сообщениями между игроками и главным узлом, и включает многие дополнительные возможности, такие как встроенная поддержка для голосовых коммуникаций.

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

Хотя программирование завершенной многопользовательской игры может стать вызовом, базовый шаг — создание главного узла многопользовательской игры, к которому смогут подключаться другие игроки. Главным узлом игры может быть один из игроков в одноранговых играх, или серверная машина, если вы используете подход клиент/сервер. Вот четыре этапа создания главного узла:

  1. Зарегистрировать игрока (с локальным или удаленным профилем).

  2. Создать сессию, установить ее свойства, включая доступные слоты.

  3. Подождать, пока другие игроки подключатся и будут готовы.

  4. Сменить состояние сессии на «Игра запущена».

Аналогичным образом вы можете разделить создание игрового клиента на четыре простых этапа, которые подходят как для одноранговых, так и для клиент/серверных игр:

  1. Зарегистрировать игрока (с локальным или удаленным профилем).

  2. Найти какую-нибудь сессию со свободными слотами для подключения.

  3. Подключиться к сессии.

  4. Сменить состояние игрока на «Готов».

Далее мы представим вам класс NetworkHelper, который создан, чтобы помочь вашей программе использовать базовые возможности работы с сетью из XNA.

Запуск компонента служб игрока

В 2002 году Microsoft создала Xbox LIVE (официально пишется заглавными буквами), сетевой сервис для распространения игрового содержимого (такого, как демонстрационные версии, трейлеры и дополнительное содержимое для игр) и объединения игроков Xbox. Возможность играть на вашей консоли в игры с удаленными игроками, публиковать ваши рекорды в сети и многое другое привели к широкому распространению LIVE. Это заставило Microsoft расширить сетевой сервис для игр под Windows, запустив в 2007 году Games for Windows LIVE. В XNA 2.0 вы можете подключаться к обоим сервисам, в зависимости от того, на какой платформе запущена ваша игра.

Команда разработчиков XNA упаковала все сложности управления профилями LIVE в пространство имен GamerServices, упростив для разработчиков использование возможностей LIVE, таких как создание локальных учетных записей, подключение к профилю LIVE и использование пользовательского интерфейса множества доступных экранов LIVE Guide для управления информацией об игроке.

Проще всего получить доступ к возможностям LIVE через GamerServicesComponent, который, при его создании в игре, запускает через регулярные интервалы подкачку данных со служб игрока. Это позволяет вашей игре, например, реагировать на действия пользователя, показывая экран LIVE Guide, когда пользователь нажимает клавишу Home.

Давайте посмотрим, как это действует, в простом проекте. Начнем с создания нового проекта Windows game, назвав его XNADemo. Затем откроем класс Game1 и добавим в конструктор класса следующую строку кода, сразу после строки, устанавливающей корневой каталог содержимого:

Components.Add(new GamerServicesComponent(this));

Теперь запустите игру. Если у вас уже есть профиль LIVE, настроенный для автоматической регистрации, ваш профиль будет активирован и вы увилите кнопку в центре нижней стороны пустого игрового экрана, как показано на рис. 5.5.


Рис. 5.5. Возможность автоматической регистрации игрока

Рис. 5.5. Возможность автоматической регистрации игрока


Если у вас нет локального профиля, LIVE автоматически отобразит экран Welcome (рис. 5.6) и позволит вам создать новый профиль игрока. Если по какой-либо причине экран, показанный на рис. 5.6, не отображается, нажмите на клавишу Home, чтобы перейти к нему.


Рис. 5.6. Экран LIVE Guide Create New Profile

Рис. 5.6. Экран LIVE Guide Create New Profile


Выберите на этом экране Create New Profile. LIVE Guide покажет новый экран, где вы вводите имя вашего нового профиля (рис. 5.7).


Рис. 5.7. Экран LIVE Guide Gamer Profile

Рис. 5.7. Экран LIVE Guide Gamer Profile


Выберите имя для вашего профиля и щелкните на этом экране по Submit. Имя профиля (которое позднее вы можете изменить) будет использоваться для вашей идентификации при игре в сетевые игры.

После щелчка по Submit ваш локальный профиль будет создан и LIVE Guide переведет вас на другой экран, показанный на рис. 5.8.


Рис. 5.8. Экран LIVE Guide Profile Created

Рис. 5.8. Экран LIVE Guide Profile Created


На этом последнем экране есть три кнопки, позволяющие вам сделать несколько вещей. Щелчок по Join LIVE откроет Internet Explorer и направит вас на сайт Games for Windows LIVE. Кнопка Customize Profile позволит вам настроить ваш профиль (например, связанную с профилем картинку) согласно вашим предпочтениям. Щелкнув по кнопке Done вы закроете окно.

После настройки вашего профиля и, если пожелаете, присоединения к LIVE, — мы рекомендуем вам сделать это, — щелкните по кнопке Done и давайте перейдем к программированию нашего примера!

Определение класса NetworkHelper

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

Откройте созданный в предыдущем разделе проект и щелкните правой кнопкой по имени проекта в Solution Explorer. В появившемся контекстном меню выберите Add, а затем Class, чтобы создать новый пустой класс и назвать его NetworkHelper.

Добавьте в начало класса ссылки на пространства имен Microsoft.Xna.Framework.Net и Microsoft.Xna.Framework.GamerServices, и вы готовы к работе. Новый класс показан в следующем фрагменте кода:

using System;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.GamerServices;

namespace XNADemo
{
    class clsNetWorkHelper
    {
    }
}

В следующих разделах вы будете следовать показанным выше этапам создания главного узла, используя их как руководство по созданию методов и свойств в вашем вспомогательном классе работы с сетью. Как помните, этапы следующие: регистрация игрока, создание сессии, ожидание, пока другие игроки подключатся и будут готовы, и затем изменение состояния сессии на «Игра запущена».

Регистрация игрока

В разделе «Запуск компонента служб игрока» вы уже создали локальный профиль с автоматической регистрацией, так что фактически вам не надо кодировать что-либо еще для регистрации игрока. Однако, поскольку наща цель здесь обучение, вы создадите в классе NetworkHelper метод с именем SignInGamer, который позволит вам программно отображать экраны LIVE Guide:

public void SignInGamer()
{
    if (!Guide.IsVisible)
    {
        Guide.ShowSignIn(1, false);
    }
}

В показанном фрагменте кода вы используете класс Guide для показа LIVE Guide. Этот класс — точка входа для любых операций, относящихся к LIVE Guide, и содержит методы для представления Guide, показа окон сообщений и обработки текстового ввода и других элементов интерфейса. Эти методы работают как на Xbox 360, так и в Windows.

В примере кода вы сначала проверяете, видим ли экран Guide, и, если нет, показываете его через метод ShowSignIn. Этот метод получает два аргумента: количество панелей, отображаемых для регистрируемых игроков (всегда 1 в Windows; 1, 2 или 4 для Xbox 360) и флаг, показывающий должны ли отображаться только подключенные профили. В данном случае вы выбираете показ одной панели и отображение как подключенных, так и отключенных профилей.

Теперь, если вы хотите отобразить LIVE Guide — например, когда пользователь нажимает клавишу F1 на клавиатуре — вы должны создать вспомогательный сетевой объект и вызвать этот метод. Для этого вы должны определить новый объект в классе Game1:

NetworkHelper networkHelper;

Затем, в методе Initialize класса Game1 вы должны создать объект:

networkHelper = new NetworkHelper();

И, наконец, вы должны вызвать метод в методе Update класса Game1, который после ваших изменений должен выглядеть следующим образом:

protected override void Update(GameTime gameTime)
{
    // Позволяет выйти из игры
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
                                          ButtonState.Pressed)
        this.Exit();

    // Показывает LIVE Guide для регистрации
    if (Keyboard.GetState().IsKeyDown(Keys.F1))
        networkHelper.SignInGamer();

    base.Update(gameTime);
}

Теперь запустите игру и нажмите клавишу F1 на клавиатуре. На экране появится всплывающее окно LIVE Guide.

Теперь у вас есть зарегистрированный игрок и следующий этап — создание сессии. Давайте займемся этим в следующем разделе.

Создание сессии

Класс XNA Framework NetworkSession представляет многопользовательскую сессию, и используется для создания, поиска, присоединения и завершения сессии. Он также предоставляет набор свойств, которые позволяют вам собирать информацию о текущей сессии.

ПРИМЕЧАНИЕ
XNA Framework 2.0 позволяет запускать на одной машине только одну сетевую программу с поддержкой Games for Windows LIVE, так что для запуска и тестирования ваших примеров вам потребуются две машины — одна для создания сессии и другая для поиска и присоединения к сессии.

Для создания новой сессии вы используете метод NetworkSession.Create, получающий до пяти параметров:

Чтобы создать сессию вы определяете несколько закрытых переменных уровня класса и программируете новый метод CreateSession в вашем классе NetworkHelper:

private NetworkSession session = null; // Игровая сессия
private int maximumGamers = 2;         // Могут играть только двое
private int maximumLocalPlayers = 1;   // Нет разделенного экрана,
                                       // только удаленные игроки

public void CreateSession()
{
    if (session == null)
    {
        session = NetworkSession.Create(NetworkSessionType.SystemLink,
                                        maximumLocalPlayers,
                                        maximumGamers);
    }
}

Создать многопользовательскую игровую сессию в XNA проще простого: только одна команда и вы готовы идти дальше!

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

public void Update()
{
    if (session != null)
        session.Update();
}

Лучший способ вызывать этот метод в каждом игровом цикле — включить следующую строку в начало метода Game1.Update:

networkHelper.Update();

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

// Если главный узел отключается,
// новым главным узлом становится другая машина
session.AllowHostMigration = true;

// Игрокам разрешено подключаться в ходе игры
session.AllowJoinInProgress = true;

Вы можете также сконфигурировать ваш класс NetworkHelper для ответа на события сессии. Чтобы увидеть, как это делается создайте для вашего класса новое строковое и доступное только для чтения свойство Message, и запрограммируйте обработчики событий сессии для установки этого свойства соответствующим образом:

// Сообщение, зависящее от текущего состояния сессии
private String message = "Waiting for user command...";

public String Message
{
    get { return message; }
}

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

session.GamerJoined +=
      new EventHandler<GamerJoinedEventArgs>(session_GamerJoined);
session.GamerLeft +=
      new EventHandler<GamerLeftEventArgs>(session_GamerLeft);
session.GameStarted +=
      new EventHandler<GameStartedEventArgs>(session_GameStarted);
session.GameEnded +=
      new EventHandler<GameEndedEventArgs>(session_GameEnded);
session.SessionEnded +=
      new EventHandler<NetworkSessionEndedEventArgs>(session_SessionEnded);
session.HostChanged +=
      new EventHandler<HostChangedEventArgs>(session_HostChanged);

В показанном фрагменте кода вы информируете объект сессии, что будете обрабатывать каждое происходящее событие. Однако, следует помнить, что это не обязательно: вы должны запрограммировать только события, уместные для вашей игровой логики. Например, если значение свойства AllowHostMigration равно False, событие HostChanged никогда не произойдет.

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

Следующий листинг представляет фрагмент кода для установки свойства message при каждом событии, для которого вы создали ловушку:

void session_GamerJoined(object sender, GamerJoinedEventArgs e)
{
    if (e.Gamer.IsHost)
        message = "The Host started the session!";
    else
        message = "Gamer " + e.Gamer.Tag + " joined the session!";
}

void session_GamerLeft(object sender, GamerLeftEventArgs e)
{
    message = "Gamer " + e.Gamer.Tag + " left the session!";
}

void session_GameStarted(object sender, GameStartedEventArgs e)
{
    message = "Game Started";
}

void session_HostChanged(object sender, HostChangedEventArgs e)
{
    message = "Host changed from " + e.OldHost.Tag + " to " + e.NewHost.Tag;
}

void session_SessionEnded(object sender, NetworkSessionEndedEventArgs e)
{
    message = "The session has ended";
}

void session_GameEnded(object sender, GameEndedEventArgs e)
{
    message = "Game Over";
}

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

Чтобы завершить программирование создания сессии, вы должны только написать в методе Update класса Game1 код для запуска сессии (скажем, когда пользователь нажимает клавишу F2 на клавиатуре):

// Создание сессии
if (Keyboard.GetState().IsKeyDown(Keys.F2))
    networkHelper.CreateSession();

Ваша программа готова к работе, но вы хотите видеть сообщение с состоянием сессии, и, конечно, вам нужен код для этого. Щелкните правой кнопкой мыши по вашему проекту в Solution Explorer, выберите команду New Item в пункте Add, и добавьте в ваш проект новый SpriteFont (назовите его Arial). Добавьте следующую строку в начало класса Game1 для объявления SpriteFont:

SpriteFont Arial;

Затем загрузите файл, который вы только что включили в проект, добавив следующую строку в метод LoadContent класса Game1:

Arial = Content.Load<SpriteFont>("Arial");

Теперь вам нужно только воспользоваться SpriteBatch, любезно созданным для вас XNA Framework, чтобы нарисовать сообщение с помощью вашего SpriteFont в методе Draw класса Game1:

// Показываем текущее состояние сессии
spriteBatch.Begin();
spriteBatch.DrawString(Arial, "Game State: " + networkHelper.Message,
                       new Vector2(20, lineHeight), Color.Yellow);
spriteBatch.End();

Теперь запустите вашу программу и нажмите F1 (или кнопку Start на вашем игровом пульте), чтобы увидеть экран регистрации игрока. Зарегистрируйтесь на этом экране и закройте его, затем нажмите F2 для запуска новой сессии. Результат — не слишком впечатляющий — показан на рис. 5.9.


Рис. 5.9. Игровой экран с сообщением The Host started the session!

Рис. 5.9. Игровой экран с сообщением «The Host started the session!»


В следующем разделе вы запрограммируете процедуры клиентской стороны для поиска сессии и присоединения к ней.

Синхронный поиск и присоединение к сессии

Синхронное подключение к существующей сессии почти так же просто, как создание сессии и характеризуется прямолинейным кодом: вам необходимо искать доступные сессии, применив метод Find объекта NetworkSession, затем проверить, есть ли в сессии пустые слоты для вашего подключения и, в конце, вам надо присоединиться к найденной сессии.

Добавив следующий фрагмент кода в ваш класс NetworkHelper, вы добавите в ваш пример возможность поиска игровых сессий и присоединения к ним:

public void FindSession()
{
    // Все найденные сессии
    AvailableNetworkSessionCollection availableSessions;

    // Сессия для подключения
    AvailableNetworkSession availableSession = null;
    availableSessions = NetworkSession.Find(NetworkSessionType.SystemLink,
                                            maximumLocalPlayers, null);

    // Получаем сессию с доступными игровыми слотами
    foreach (AvailableNetworkSession curSession in availableSessions)
    {
        int TotalSessionSlots = curSession.OpenPublicGamerSlots +
                                curSession.OpenPrivateGamerSlots;
        if (TotalSessionSlots > curSession.CurrentGamerCount)
            availableSession = curSession;
    }

    // Если сессия найдена, подключаемся к ней
    if (availableSession != null)
    {
        message = "Found an available session at host " +
                  availableSession.HostGamertag;
        session = NetworkSession.Join(availableSession);
    }
    else
        message = "No sessions found!";
}

Давайте рассмотрим код шаг за шагом, чтобы понять его детали.

Во-первых, вы определяете две переменные, которые будут получать объекты, помогающие вам найти сессию и управлять ею: AvailableNetworkSessionCollection является коллекцией сессий, возвращаемой вам методом NetworkSession.Find, а AvailableNetworkSession — это элемент такой коллекции.

ПРИМЕЧАНИЕ
Объект AvailableNetworkSession отличается от объекта NetworkSession. Это только ссылка на доступную сессию со свойствами, описывающими сессию. Вы можете использовать ее для создания объекта NetworkSession через метод NetworkSession.Join.

После создания этих объектов вы используете метод NetworkSession.Find для получения коллекции доступных сессий. Этот метод получает три параметра: тип сетевых сессий, которые будут искаться (эти типы обсуждались в предыдущем разделе); максимальное количество игроков; и коллекция пользовательских свойств NetworkSessionProperties, которая должна соответствовать свойствам, использованным при создании сессии. В данном примере, поскольку вы создаете сессию без пользовательских свойств, вы можете просто передать в последнем аргументе null.

После получения доступных сессий, показанный выше код в цикле перебирает эти сессии и проверяет, есть ли в них пустые слоты для вашего подключения, сравнивая сумму доступных свойств сессии OpenPublicGamerSlots и OpenPrivateGamerSlots с общим количеством уже подключенных к сессии игроков, получаемым через свойство CurrentGamerCount.

И в конце вы устанавливаете свойство message класса NetworkHelper, записывая в него требуемое сообщение (сообщающее, найдена или нет сессия для подключения). Если найдена сессия с пустыми слотами, то вы подключаетесь к сессии, используя метод NetworkSession.Join, передавая ему в параметре найденную доступную сессию.

Чтобы завершить программирование поиска сессии, вам надо сейчас подстроить метод Update класса Game1, чтобы он вызывал ваш метод Find. Вы можете запускать поиск сессии, когда пользователь нажимает клавишу F3 на клавиатуре, с помощью следующего кода:

// Ищем сессию
if (Keyboard.GetState().IsKeyDown(Keys.F3))
    networkHelper.FindSession();

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

На втором компьютере запустите программу, нажмите клавишу F1, чтобы гарантировать, что игрок зарегистрирован (иначе поиск сессии провалится), а затем нажмите F3 для поиска сессии. Если оба компьютера находятся в одной и той же подсети, XNA сможет найти сессию и на экране будет показано сообщение «Found an available session at host XXX», где XXX — это имя игрока, зарегистрированного на машине главного узла (рис. 5.10).


Рис. 5.10. Игровой экран с сообщением Found an available session...

Рис. 5.10. Игровой экран с сообщением «Found an available session...»


 

СОВЕТ
У объекта AvailableNetworkSession есть свойство, QualityOfService, которое заполняется информацией о качестве соединения, после того, как XNA Framework соберет эти данные (проверьте свойство isAvailable класса, чтобы убедиться, собраны ли уже данные). В этом классе есть четыре свойства, представляющие минимальное и среднее время передачи и подтверждения приема сетевых пакетов, а также доступную полосу пропускания от главного узла к локальной машине и от локальной машины к главному узлу.

В следующем разделе вы увидите, как искать сессии асинхронно.

Асинхронный поиск и присоединение к сессии

Программирование асинхронного поиска это более часто используемый в играх подход, поскольку вы обычно не хотите замораживать игру и действия игрока, пока выполняется поиск доступных сессий. Основная идея поиска сессии и присоединения к ней остается той же, что и в предыдущем разделе. Однако здесь вы используете методы BeginFind и EndFind из NetworkSession, которые, соответственно, запускают поиск сессии, указывая функцию, которую следует вызвать, когда поиск закончен, и получают результаты поиска.

Следующий фрагмент кода, который следует включить в ваш класс NetworkHelper, определяет новую переменную, используемую для хранения и отслеживания состояния асинхронной операции, и метод, вызывающий BeginFind для начала поиска сессии:

IAsyncResult AsyncSessionFind = null;

public void AsyncFindSession()
{
    message = "Asynchronous search started!";
    if (AsyncSessionFind == null)
    {
        AsyncSessionFind = NetworkSession.BeginFind(
            NetworkSessionType.SystemLink, maximumLocalPlayers, null,
            new AsyncCallback(session_SessionFound), null);
    }
}

BeginFind получает те же параметры, что и метод Find, обсуждавшийся в предыдущем разделе (тип сессии, максимальное количество игроков и пользовательские свойства сессии), плюс адрес функции обратного вызова (которая вызывается, когда готовы результаты поиска). BeginFind также получает объект, используемый для хранения состояния асинхронной операции (не будем беспокоиться об этой последней вещи сейчас; прекрасно можно просто передавать значение null).

В предыдущем примере кода вы передали BeginFind в качестве функции обратного вызова session_SessionFound. Следующий фрагмент кода представляет код функции обратного вызова, который, как видите, очень похож на запрограммированный ранее метод FindSession:

public void session_SessionFound(IAsyncResult result)
{
    // Все найденные сессии
    AvailableNetworkSessionCollection availableSessions;

    // Сессия, к которой будем подключаться
    AvailableNetworkSession availableSession = null;

    if (AsyncSessionFind.IsCompleted)
    {
        availableSessions = NetworkSession.EndFind(result);

        // Ищем сессии с доступными слотами для игроков
        foreach (AvailableNetworkSession curSession in
                 availableSessions)
        {
            int TotalSessionSlots = curSession.OpenPublicGamerSlots +
                                    curSession.OpenPrivateGamerSlots;
            if (TotalSessionSlots > curSession.CurrentGamerCount)
                availableSession = curSession;
        }

        // Если сессия найдена, подключаемся к ней
        if (availableSession != null)
        {
            message = "Found an available session at host" +
                      availableSession.HostGamertag;
            session = NetworkSession.Join(availableSession);
        }
        else
            message = "No sessions found!";

        // Сбрасываем результаты поиска сессий
        AsyncSessionFind = null;
    }
}

Показанный фрагмент кода почти полностью идентичен вашему синхронному методу FindSession; фактически отличаются только три строки: проверка свойства AsyncSessionFind.IsCompleted, чтобы увидеть, доступны ли уже результаты; использование NetworkSession.EndFind (вместо NetworkSession.Find) для получения коллекции доступных сессий; и, наконец, последняя строка листинга, где вы просто сбрасываете переменную результата AsyncSessionFind. Итак, если вы поняли концепцию синхронного поиска сессии, у вас есть несколько новых вещей для изучения, когда дело доходит до асинхронного поиска.

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

// Асинхронный поиск сессии
if (Keyboard.GetState().IsKeyDown(Keys.F4))
    networkHelper.AsyncFindSession();

Вы можете протестировать новый код, снова повторив шаги, которые вы делали в предыдущем разделе для синхронного подключения к сессии, за исключением того, что вам нужно нажать клавишу F4, а не F3. На клиентской машине вы увидите сообщение «Asynchronous search started!», а затем, несколько секунд спустя, сообщение, говорящее о результатах поиска сессии.

Теперь у вас есть две машины с зарегистрированными игроками, первая создает сессию и действует как главный узел, а втроая присоединяется к созданной сессии, так что настало время проинформировать XNA, что мы готовы двигаться дальше, и запустить игру!

Запуск игры

Игровая сессия в XNA может находиться в одном из трех состояний, о чем сообщается через ее свойство SessionState:

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

Определение того, что все локальные игроки (один в Windows и до четырех в Xbox 360) готовы, легко осуществить через объект сессии, в котором есть коллекция со ссылками на все профили локальных игроков. Следующий фрагмент кода показывает новый метод для вашего класса NetworkHelper, выполняющий эту работу:

public void SetPlayerReady ()
{
    foreach (LocalNetworkGamer gamer in session.LocalGamers)
        gamer.IsReady = true;
}

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

void session_GamerJoined(object sender, GamerJoinedEventArgs e)
{
    if (e.Gamer.IsHost)
    {
        message = "The Host started the session!";
    }
    else
    {
        message = "Gamer " + e.Gamer.Tag + " joined the session!";

        // Другой игрок присоединился, начинаем игру!
        session.StartGame();
    }
}

Если вы сейчас запустите вашу программу на двух тестовых машинах, нажмете F2 на машине главного узла и нажмете F3 или F4 для поиска сессии на второй машине, главный узел автоматически запустит игру и покажет сообщение об этом (которое вы закодировали в обработчике события GameStarted объекта сессии в разделе «Создание сессии»).

К этому моменту у вас есть две машины, подсоединенные к одной и той же игре. Следуя общему руководству, представленному в этом разделе, вы легко можете расширить пример, написав код для завершения игры, путем вызова метода session.EndGame().

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

Обработка сообщений

Отправка и получение сообщений это просто вызов методов SendData и ReceiveData класса LocalNetworkGamer, представляющего локального игрока.

Оба метода могут работать с массивом байтов или составителем пакетов, являющимся потоковым источником двоичных данных. Он получает базовые типы данных и преобразует их в массивы байтов наиболее эффективным способом. Поскольку иметь дело с составителем пакетов проще, давайте будем работать с ним. Начнем с создания новой переменной уровня класса с именем packetWriter в вашем классе HetworkHelper:

PacketWriter packetWriter = new PacketWriter();

Теперь вы можете использовать этот составитель пакетов для формирования потока ваших сообщений одному или всем остальным удаленным игрокам, перебирая в цикле коллекцию LocalGamers вашей сессии и вызывая метод SendData, как показано ниже:

public void SendMessage(string key)
{
    foreach (LocalNetworkGamer localPlayer in session.LocalGamers)
    {
        packetWriter.Write(key);
        localPlayer.SendData(packetWriter, SendDataOptions.None);
        message = "Sending message: " + key;
    }
}

Метод SendData может обеспечивать гарантированную доставку и сохранение порядка сообщений, через его параметр SendDataOptions, который может принимать следующие значения: None (отправка пакетов не гарантируется), InOrder (пакеты отправляются в заданном порядке, но может происходить потеря пакетов), Reliable (пакеты всегда достигают места назначения, но их порядок может меняться) и ReliableInOrder (пакеты гарантированно доставляются в том порядке, в котором они были отправлены). Помните, что мы говорили в начале этой главы: выбирайте, какой вариант лучше подойдет для вашей игры.

Помимо этого, у метода SendData есть перегруженная версия, получающая дополнительный параметр NetworkGamer, которая позволяет вашей игре отправлять сообщения конкретному игроку. Если этот параметр не задан, сообщение доставляется всем зарегистрированным игрокам.

В методе SendMessage вы упаковываете только одну строку, но можно упаковывать и несколько переменных, в соответствии с логикой вашей игры. Например, если вы хотите отправлять всем другим игрокам состояние левого джойстика и обеих курков, формируйте ваш пакет, как показано в следующем фрагменте кода:

GamePadState GamePad1 = GamePad.GetState(PlayerIndex.One);

packetWriter.Write(GamePad1.Triggers.Left);
packetWriter.Write(GamePad1.Triggers.Right);
packetWriter.Write(GamePad1.ThumbSticks.Left);

Метод для получения сообщений также прост: вы в цикле перебираете коллекцию локальных игроков и проверяете, есть ли у них какие-либо доступные сообщения. Если да, вам надо вызвать метод ReceiveData объекта LocalNetworkGamer, пока вы не получите все доступные данные. ReceiveData возвращает массив байтов или packetReader (противоположность packetWriter, используемому для записи пакетов), а также объект NetworkGamer с данным удаленного игрока, который вы можете использовать чтобы решить, обрабатывать сообщение или нет, согласно логике игры.

Следующий фрагмент кода представляет простую реализацию процедуры, которая получает сообщения от других игроков:

PacketReader packetReader = new PacketReader();

public void ReceiveMessage()
{
    NetworkGamer remotePlayer; // Отправитель сообщения
    foreach (LocalNetworkGamer localPlayer in session.LocalGamers)
    {
        // Читаем, пока есть доступные данные
        while (localPlayer.IsDataAvailable)
        {
            localPlayer.ReceiveData(packetReader, out remotePlayer);

            // Игнорируем ввод от локального игрока
            if (!remotePlayer.IsLocal)
                message = "Received message: " +
                          packetReader.ReadString();
        }
    }
}

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

remoteThumbstick = packetReader.ReadVector2();
remoteLeftTrigger = packetReader.ReadSingle();
remoteRightTrigger = packetReader.ReadSingle();

Теперь, когда ваши процедуры отправки и получения заняли свое место, надо вызывать их из метода Update класса Game1 для проверки. Поскольку вы хотите отправлять и получать сообщения только когда игра запущена, создайте новое свойство для класса NetworkHelper, которое возвращает текущее состояние сессии:

public NetworkSessionState SessionState
{
    get
    {
        if (session == null)
            return NetworkSessionState.Ended;
        else
            return session.SessionState;
    }
}

Теперь добавим в метод Update вызовы для отправки и получения сообщений, когда сессия находится в состоянии Playing:

if (networkHelper.SessionState == NetworkSessionState.Playing)
{
    // Отправляем нажатые клавиши удаленному игроку
    foreach (Keys key in Keyboard.GetState().GetPressedKeys())
        networkHelper.SendMessage(key.ToString());

    // Получаем клавиши от удаленного игрока
    networkHelper.ReceiveMessage();
}

Чтобы проверить вашу программу, выполните тест из предыдущего раздела, чтобы две машины были соединены и игра запущена. Затем нажмите какую-нибудь клавишу и вы увидите сообщение «Sending message:» и нажатую клавишу на первой машине и сообщение «Received message:» и клавишу, нажатую на удаленной машине, на второй.

Заключительные штрихи

Пока в этой главе мы представляли различные концепции, было запрограммировано много специальных функций для различных клавиш. Для помощи в тестировании ваших программ давайте обновим метод Draw класса Game1, чтобы он отображал вспомогательные сообщения, объясняющие назначение каждой клавиши. Обновите метод согласно следующему фрагменту кода:

protected override void Draw(GameTime gameTime)
{
    graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

    // Показываем текущее состояние сессии
    spriteBatch.Begin();
    spriteBatch.DrawString(Arial, "Game State: " +
                           networkHelper.Message,
                           new Vector2(20, 20), Color.Yellow);
    spriteBatch.DrawString(Arial, "Press:", new Vector2(20, 100),
                           Color.Snow);
    spriteBatch.DrawString(Arial, " - F1 to sign in",
                           new Vector2(20, 120), Color.Snow);
    spriteBatch.DrawString(Arial, " - F2 to create a session",
                           new Vector2(20, 140), Color.Snow);
    spriteBatch.DrawString(Arial, " - F3 to find a session",
                           new Vector2(20, 160), Color.Snow);
    spriteBatch.DrawString(Arial,
                           " - F4 to asynchronously find a session",
                           new Vector2(20, 180), Color.Snow);
    spriteBatch.DrawString(Arial, "After the game starts, press other
                           keys to send messages",
                           new Vector2(20, 220), Color.Snow);
    spriteBatch.End();

    base.Draw(gameTime);
}

Теперь, запустив игру, вы получите краткую справку обо всех клавишах, имеющих специальное назначение, как показано на рис. 5.11.


Рис. 5.11. Игровой экран с вспомогательным сообщением

Рис. 5.11. Игровой экран с вспомогательным сообщением


Помните, что при тестировании этого приложения вы должны выполнять команды в определенном порядке: регистрация игрока, создание сессии, присоединение к сессии (только на второй машине), установка готовности игрока и начало отправки и получения сообщений. В примере, запрограммированном в этой главе, вы не беспокоились о таких вещах, но в игре вы должны гарантировать, например, что вы никогда не будете пытаться создать или найти сессию, если у вас нет зарегистрированного игрока.

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


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

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