Данная статья предназначена для ознакомления и использования в личных целях, не претендует на уникальность и единственное решение поставленной задачи.
Статья рекомендована к ознакомлению новичкам, начинающим и продолжающим программирование игр на C# & XNA4.
Не секрет, что, начав осваивать новую платформу/среду/инструмент (не обязательно это XNA), поигравшись с ним, откомпилировав первый Hello, World!, многие начинают расширять свои навыки владения инструментом.
Для разработчиков на XNA первым и необходимым источником является AppHub.
Так как данная статья описывает реализацию Game Screens, или Игровых Экранов, то, найдя в упомянутом AppHub пример GameStateManagement многие новички впадают в ужас при виде всего того, что там "навалено".
Собственно, статья как раз и рассматривает создание более простого подхода к организации Игровых Экранов. И не только экранов. Поехали.
Запускаем студию, создаем проект. Название оставляем по умолчанию, WindowsGame1:
Основой всего происходящего в примере будет статический класс (да-да, даже не компонент), назовем его ScreenManager.
Для этого добавляем к пока пустому проекту папку ScreenManager:
и добавляем в эту папку класс ScreenManager.cs (прим.: расширение .cs в названии класса можно не указывать, само добавится):
Исходный код класса (здесь и далее исходник приведен полный, в комментариях думаю не нуждается):
Код
using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace WindowsGame1
{
internal static class ScreenManager
{
static List screens = new List();
static bool isStarted = false;
static Screen prevScreen = null;
internal static Screen ActiveScreen = null;
internal static void AddScreen(Screen screen)
{
foreach (Screen scr in screens)
if (scr.Name == screen.Name)
return;
screens.Add(screen);
}
internal static int GetScreensCount()
{
return screens.Count;
}
if (ActiveScreen != null)
ActiveScreen.Initialize();
}
internal static void Update(GameTime gameTime)
{
if (!isStarted)
return;
if (ActiveScreen != null)
ActiveScreen.Update(gameTime);
}
internal static void Draw(GameTime gameTime)
{
if (!isStarted)
return;
if (ActiveScreen != null)
ActiveScreen.Draw(gameTime);
}
}
}
Наш менеджер игровых экранов оперирует неким объектом Screen, о котором пока не знает студия:
Так что же это за объект?
Это наш будущий базовый класс для всех игровых экранов. Он содержит минимум необходимых виртуальных методов, необходимых для отрисовки и обновления экрана-наследника, которые будем переопределять в наследуемых от него экранах.
Для начала создадим еще один класс Screen.cs в папке ScreenManager. Вот его полный исходный код:
Код
using Microsoft.Xna.Framework;
namespace WindowsGame1
{
internal class Screen
{
internal string Name { get; private set; }
Итак, наш менеджер экранов уже готов к работе, и настало время попробовать его использовать.
Но для начала создадим еще один статический класс-"хранилище" ссылок на общеигровые объекты нашей игры. Этот класс обеспечит нам доступ к необходимым объектам, так как ScreenManager является не GameComponent. Вот его исходный код:
Код
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
Как видно, мы убрали из Game1 экземпляр класса SpriteBatch, и "перенесли" его в Commons.
Также убрана очистка буфера экрана GraphicsDevice.Clear(Color.CornflowerBlue); в методе Draw(), так как отрисовка будет выполняться в наших игровых экранах.
Теперь добавим в нашу игру пару экранов: MainMenuScreen и TestScreen в новую папку Screens проекта. Для наглядной реализации в проекте сделаны лишь переходы между экранами. Этого вполне достаточно чтобы убедиться в работоспособности менеджера.
Забегая немного вперед отмечу, что в классах экранов мы опять увидим некий объект MenuItem. Это простой класс элемента меню, со своими полями и свойствами, облегчающими обработку состояния каждого элемента меню. Организация меню в целом и пунктов меню не совсем идеальная, но суть понятна из простой реализации. Вот его код:
Код
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace WindowsGame1
{
internal class MenuItem
{
internal string Text { get; private set; }
internal SpriteFont Font { get; private set; }
internal Vector2 Position { get; private set; }
internal Rectangle Rect { get; private set; }
internal Action Action { get; private set; }
internal MenuItem(string text, SpriteFont font, Vector2 position, Action action)
{
Text = text;
Font = font;
Vector2 measure = font.MeasureString(Text);
Position = new Vector2(
(int)(position.X - measure.X / 2),
(int)(position.Y - measure.Y / 2));
Rect = new Rectangle(
(int)Position.X,
(int)Position.Y,
(int)measure.X,
(int)measure.Y);
Здесь нам встретилось еще одно "существо" InputManager. Это такой же статический класс, выдающий нам по запросу состояния игровых контроллеров. В рамках статьи в нем реализовано лишь обновление клавиатуры и мыши:
Код
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
internal static Vector2 GetMousePositionToVector2()
{
return new Vector2(mouseState.X, mouseState.Y);
}
internal static Point GetMousePositionToPoint()
{
return new Point(mouseState.X, mouseState.Y);
}
}
}
Экран MainMenuScreen содержит лишь два пункта меню: "Играть" и "Выход":
Код
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace WindowsGame1
{
internal class MainMenuScreen : Screen
{
SpriteFont font;
List
Пункт "Играть" указывает нашему ScreenManager, чтобы тот "переключил" игровой экран на TestScreen. Клавишей Escape игра завершается.
Экран TestScreen содержит экземпляр MenuItem, у которого Action "просит" ScreenManager переключить экран на MainMenuScreen. То же самое происходит по нажатию клавиши Escape.
Также в центре этого экрана для наглядности крутится сфера с текстурой нашей планеты. Модель очень низкополигональная, ибо это не суть совсем, главное механизм в целом. Для интересующихся количеством полигонов скрин с блендера ниже по тексту.
Код
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace WindowsGame1
{
internal class TestScreen : Screen
{
SpriteFont font;
List menuItems = new List();
Color itemNormalColor = Color.White;
Color itemHoverColor = Color.Red;
Model sphere;
Matrix world;
Matrix view;
Matrix proj;
for (int i = 0; i < menuItems.Count; i++)
{
Commons.SpriteBatch.DrawString(
font,
menuItems.Text,
menuItems.Position,
menuItems.Hovered() ? itemHoverColor : itemNormalColor);
}
Commons.SpriteBatch.End();
sphere.Draw(world, view, proj);
base.Draw(gameTime);
}
}
}
Модель сферы в исходном виде в окне блендера:
Как видно, ничего сложного в реализации классов экранов нет, всё это Вы уже проделывали, упражняясь с программированием в XNA GS, только код был прямо в Game1. В подходе, описанном в статье, сохраняется эта простота, как если бы Вы писали код только для одного, главного экрана.