| 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 | < Назад | Оглавление | Далее > |