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. Сцена сетевой игры
Теперь вернемся назад к методу 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.
В предыдущей главе вы видели, что все сетевые сервисы в вашей 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 | < Назад | Оглавление | Далее > |