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

Создание сцены сетевой игры

Теперь вы создадите сцену, которая позволит вам создавать сессию или присоединяться к сессии сетевой игры. Подобно тому, как вы поступали ранее, в главе 4, добавляем новый открытый класс, названный NetworkScene и наследуемый от GameScene (в пространстве имен RockRain.Core), чтобы у вас был новый класс сцены. Сперва добавим ссылки на пространства имен для поддержки сети:

using Microsoft.Xna.Framework.GamerServices;

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

// Состояния сцены
public enum NetworkGameState
{
    idle     = 1,
    joining  = 2,
    creating = 3
}

Как уже упоминалось, в этой сцене у вас есть меню, фоновая текстура и мерцающее сообщение. Объявим объекты, необходимые для сборки этой сцены:

// Разное
protected TextMenuComponent menu;
private readonly SpriteFont messageFont;
private Vector2 messagePosition,messageShadowPosition;
private string message;
protected TimeSpan elapsedTime = TimeSpan.Zero;

// SpriteBatch
protected SpriteBatch spriteBatch = null;

// Состояние сцены
private NetworkGameState state;

// Используется для мерцания сообщения
private bool showMessage = true;

В конструкторе только инициализируйте эти объекты, как вы делали во всех сценах в главе 4:

/// <summary>
/// Конструктор по умолчанию
/// </summary>
/// <param name="game">Главный объект игры</param>
/// <param name="smallFont">Шрифт для пунктов меню</param>
/// <param name="largeFont">Шрифт для выбранного пункта меню</param>
/// <param name="background">Текстура для фонового изображения</param>
public NetworkScene(Game game, SpriteFont smallFont, SpriteFont largeFont,
                    Texture2D background) : base(game)
{
    messageFont = largeFont;
    Components.Add(new ImageComponent(game, background,
                   ImageComponent.DrawMode.Stretch));

    // Создание компонента меню
    menu = new TextMenuComponent(game, smallFont, largeFont);
    Components.Add(menu);

    // Получение текущего пакета спрайтов
    spriteBatch = (SpriteBatch)Game.Services.GetService(
                                        typeof(SpriteBatch));
}

Состояние сцены, когда пользователь открывает ее, должно оставаться прежним:

/// <summary>
/// Показ сцены
/// </summary>
public override void Show()
{
    state = NetworkGameState.idle;

    base.Show();
}

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

/// <summary>
/// Позволяет игровому компоненту рисовать ваше содержимое
/// в игровом экране
/// </summary>
public override void Draw(GameTime gameTime)
{
    base.Draw(gameTime);

    if (!string.IsNullOrEmpty(message) && showMessage)
    {
        DrawMessage();
    }
}

/// <summary>
/// Вспомогательное рисование уведомляющих сообщений
/// перед вызовом блокирующих сетевых методов
/// </summary>
void DrawMessage()
{
    // Рисуем тень
    spriteBatch.DrawString(messageFont, message, messageShadowPosition,
                           Color.Black);

    // Рисуем сообщение
    spriteBatch.DrawString(messageFont, message, messagePosition,
                           Color.DarkOrange);
}

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

/// <summary>
/// Строка с текстом сообщения
/// </summary>
public string Message
{
    get { return message; }

    set
    {
        message = value;

        // Вычисляем местоположение сообщения
        messagePosition = new Vector2();
        messagePosition.X = (Game.Window.ClientBounds.Width -
                          messageFont.MeasureString(message).X)/2;
        messagePosition.Y = 130;

        // Вычисляем местоположение тени сообщения
        messageShadowPosition = messagePosition;
        messageShadowPosition.Y++;
        messageShadowPosition.X--;
    }
}

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

/// <summary>
/// Позволяет игровому компоненту обновлять себя
/// </summary>
/// <param name="gameTime">Предоставляет снимок значения таймера</param>
public override void Update(GameTime gameTime)
{
    elapsedTime += gameTime.ElapsedGameTime;

    if (elapsedTime > TimeSpan.FromSeconds(1))
    {
        elapsedTime -= TimeSpan.FromSeconds(1);
        showMessage = !showMessage;
    }

    // Устанавливаем меню для текущего состояния
    UpdateMenus();

    base.Update(gameTime);
}

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

/// <summary>
/// Строим меню для каждого состояния сцены и сетевого статуса
/// </summary>
private void UpdateMenus()
{
    if (Gamer.SignedInGamers.Count == 0)
    {
        string[] items = {"Sign in", "Back"};
        menu.SetMenuItems(items);
    }
    else
    {
        if (state == NetworkGameState.idle)
        {
            string[] items = {"Join a System Link Game",
                              "Create a System Link Game",
                              "Sign out", "Back"};
            menu.SetMenuItems(items);
        }
        if (state == NetworkGameState.creating)
        {
            string[] items = {"Cancel"};
            menu.SetMenuItems(items);
        }
    }

    // Помещаем меню в центр экрана
    menu.Position = new Vector2((Game.Window.ClientBounds.Width -
                                           menu.Width) / 2, 330);
}

И, как вы всегда делали, предоставьте доступ к выбранному пункту меню, чтобы класс Game1 мог выполнить выбранный пользователем вариант. Также обеспечьте доступ к состоянию сцены, чтобы класс Game1 мог менять его, когда это требуется. Для этого добавьте следующий код к классу NetworkScene:

/// <summary>
/// Получаем выбранный вариант меню
/// </summary>
public int SelectedMenuIndex
{
    get { return menu.SelectedIndex; }
}

/// <summary>
/// Состояние сцены
/// </summary>
public NetworkGameState State
{
    get { return state; }

    set
    {
        state = value;
        menu.SelectedIndex = 0;
    }
}

Теперь вы можете использовать эту сцену в вашей игре. Начнем с добавления объявления объекта NetworkScene в классе Game1:

protected NetworkScene networkScene;

Затем добавляем фоновую текстуру для этой новой сцены.

protected Texture2D networkBackgroundTexture;

Вы найдете фоновые изображения для этого проекта в области Source Code/Download веб-сайта Apress http://www.apress.com. Добавьте эти изображения в папку Content и измените метод LoadContent, добавив следующие строки для загрузки фоновой текстуры и создания объекта сетевой сцены:

// Создание сетевой сцены
networkBackgroundTexture = Content.Load<Texture2D>("NetworkBackground");
networkScene = new NetworkScene(this,smallFont,largeFont,
                                networkBackgroundTexture);
Components.Add(networkScene);

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

case 2:
    ShowScene(networkScene);
    break;

Запустите программу. Выберите вариант Network Game и вы должны увидеть результат, показанный на рис. 6.2.


Рис. 6.2. Сцена сетевой игры

Рис. 6.2. Сцена сетевой игры


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

Обработка ввода в сцене

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

/// <summary>
/// Обработка меню сетевой сцены
/// </summary>
private void HandleNetworkSceneInput()
{
    if (CheckEnterA())
    {
        audioComponent.PlayCue("menu_select3");

        if (Gamer.SignedInGamers.Count == 0)
        {
            HandleNotSigned();
        }
        else
        {
            HandleSigned();
        }
    }
}

Метод HandleNotSigned содержит код для меню, которое показывает команды для неподключенного игрока, а метод HandleSigned содержит команды для подключенного пользователя.

Неподключенный пользователь может только подключиться к сети или вернуться к начальной сцене. Поэтому метод HandleNotSigned простой:

/// <summary>
/// Обработка меню сетевой сцены для неподключенного пользователя
/// </summary>
private void HandleNotSigned()
{
    switch (networkScene.SelectedMenuIndex)
    {
        case 0:
            if (!Guide.IsVisible)
            {
                Guide.ShowSignIn(1, false);
                break;
            }
            break;
        case 1:
            ShowScene(startScene);
            break;
    }
}

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

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

/// <summary>
/// Обработка меню сетевой сцены для подключенного пользователя
/// </summary>
private void HandleSigned()
{
    switch (networkScene.State)
    {
        case NetworkScene.NetworkGameState.idle:
            switch (networkScene.SelectedMenuIndex)
            {
                case 0:
                    // Присоединение к сетевой игре
                    JoinSession();
                    break;
                case 1:
                    // Создание сетевой игры
                    CreateSession();
                    break;
                case 2:
                    // Отображение мастера для
                    // смены пользователя
                    if (!Guide.IsVisible)
                    {
                        Guide.ShowSignIn(1, false);
                        break;
                    }
                    break;
                case 3:
                    // Возврат к начальной сцене
                    ShowScene(startScene);
                    break;
            }
            break;
        case NetworkScene.NetworkGameState.creating:
            // Закрытие созданной сессии
            CloseSession();

            // Ожидание новой команды
            networkScene.State = NetworkScene.NetworkGameState.idle;
            networkScene.Message = "";
            break;
    }
}

Обратите внимание на методы CreateSession, JoinSession и CloseSession. Эти методы общие для всех сетевых игр, и фактически начинают и заканчивают все взаимодействие между игроками. Вы реализуете их попозже, а сперва давайте создадим класс, который поможет вам с сетевыми сервисами, необходимыми для Rock Rain Live.

Класс NetworkHelper

В предыдущей главе вы видели, что все сетевые сервисы в вашей XNA-игре централизованы в классе NetworkSession. С ним вы используете объекты из классов PacketWriter и PacketReader для записи и чтения сетевых данных. В целях организации вы создаете класс, который инкапсулирует всю необходимую функциональность транспортировки данных, использующую эти классы, чтобы у вас был только один объект, который можно использовать для отправки и чтения данных, как на стороне сервера, так и на стороне клиента. Этот класс простой — просто добавьте к проекту новый класс, названный NetworkHelper, и поместите в него следующий код:

using Microsoft.Xna.Framework.Net;

namespace RockRainLive
{
    /// <summary>
    /// Помощник для сетевых сервисов
    /// </summary>
    class NetworkHelper
    {
        // Вспомогательные ресурсы
        private NetworkSession networkSession;

        private readonly PacketWriter serverPacketWriter = new PacketWriter();
        private readonly PacketReader serverPacketReader = new PacketReader();

        private readonly PacketWriter clientPacketWriter = new PacketWriter();
        private readonly PacketReader clientPacketReader = new PacketReader();

        /// <summary>
        /// Активная сетевая сессия
        /// </summary>
        public NetworkSession NetworkGameSession
        {
            get { return networkSession; }

            set { networkSession = value; }
        }

        /// <summary>
        /// Писатель для данных сервера
        /// </summary>
        public PacketWriter ServerPacketWriter
        {
            get { return serverPacketWriter; }
        }

        /// <summary>
        /// Писатель для данных клиента
        /// </summary>
        public PacketWriter ClientPacketWriter
        {
            get { return clientPacketWriter; }
        }

        /// <summary>
        /// Читатель для данных клиента
        /// </summary>
        public PacketReader ClientPacketReader
        {
            get { return clientPacketReader; }
        }

        /// <summary>
        /// Читатель для данных сервера
        /// </summary>
        public PacketReader ServerPacketReader
        {
            get { return serverPacketReader; }
        }

        /// <summary>
        /// Отправляем все данные сервера
        /// </summary>
        public void SendServerData()
        {
            if (ServerPacketWriter.Length > 0)
            {
                // Отправляем объединенные данные каждому в сессии.
                LocalNetworkGamer server = (LocalNetworkGamer) networkSession.Host;
                server.SendData(ServerPacketWriter, SendDataOptions.InOrder);
            }
        }

        /// <summary>
        /// Чтение данных сервера
        /// </summary>
        public NetworkGamer ReadServerData(LocalNetworkGamer gamer)
        {
            NetworkGamer sender;

            // Читаем единственный пакет из сети.
            gamer.ReceiveData(ServerPacketReader, out sender);

            return sender;
        }

        /// <summary>
        /// Отправка всех данных клиента
        /// </summary>
        public void SendClientData()
        {
            if (ClientPacketWriter.Length > 0)
            {
                // Первый игрок всегда запущен на сервере...
                networkSession.LocalGamers[0].SendData(clientPacketWriter,
                                                  SendDataOptions.InOrder,
                                                  networkSession.Host);
            }
        }

        /// <summary>
        /// Чтение данных клиента
        /// </summary>
        public NetworkGamer ReadClientData(LocalNetworkGamer gamer)
        {
            NetworkGamer sender;

            // Читаем единственный пакет из сети.
            gamer.ReceiveData(ClientPacketReader, out sender);

            return sender;
        }
    }
}

Этот класс содержит ваш объект NetworkSession, а также методы для отправки и чтения пакетов данных через объекты PacketWriter и PacketReader, как для клиента так и для сервера. Вы используете этот класс для реализации вашего коммуникационного протокола в следующем разделе. Сейчас вы инициализируете объект NetworkSession этого класса, подобно тому, как вы делали в предыдущей главе, чтобы создать игровую сессию, присоединиться к существующей сессии или прервать сессию. Следовательно, вы реализуете методы CreateSession, JoinSession и CloseSession, о которых мы говорили ранее.


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

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