Навигация
· XNA FAQ
· С чего начать
· Конкурсы
· Обратная связь
· XNA Блоги
Сейчас на сайте
· Гостей: 1

· Пользователей: 0

· Всего пользователей: 3,684
· Новый пользователь: headron
Последние фото
Эх, чуть не проспал закрытие.
Эх, чуть не проспал ...
Альбом: XNA Engine

GB
GB
Альбом: XNA Engine

South Park Coon & Friends
South Park Coon & Fr...
Альбом: XNA Games

Блоги
yavshoke
» XboxOne - интерес...
dampirik
» Push уведомления ...
dampirik
» Реклама,статистик...
Chort
» XNA и StartCoroutine
Chort
» Curve Class
dampirik
» Реклама, статисти...
dampirik
» Увеличение скорос...
dampirik
» Реклама, статисти...
general
» Распаковка DxtCom...
general
» Как работать с XN...
Поддержка
microsoft.com
1gb.ru - Дом для вашего сайта
Статистика посещений:

Простой менеджер игровых экранов (ScreenManager)
Данная статья предназначена для ознакомления и использования в личных целях, не претендует на уникальность и единственное решение поставленной задачи.
Статья рекомендована к ознакомлению новичкам, начинающим и продолжающим программирование игр на C# & XNA4.

Не секрет, что, начав осваивать новую платформу/среду/инструмент (не обязательно это XNA), поигравшись с ним, откомпилировав первый Hello, World!, многие начинают расширять свои навыки владения инструментом.
Для разработчиков на XNA первым и необходимым источником является AppHub.
Так как данная статья описывает реализацию Game Screens, или Игровых Экранов, то, найдя в упомянутом AppHub пример GameStateManagement многие новички впадают в ужас при виде всего того, что там "навалено".
Собственно, статья как раз и рассматривает создание более простого подхода к организации Игровых Экранов. И не только экранов. Поехали.

Запускаем студию, создаем проект. Название оставляем по умолчанию, WindowsGame1:

1

Основой всего происходящего в примере будет статический класс (да-да, даже не компонент), назовем его ScreenManager.
Для этого добавляем к пока пустому проекту папку ScreenManager:

2

и добавляем в эту папку класс ScreenManager.cs (прим.: расширение .cs в названии класса можно не указывать, само добавится):

3

4

Исходный код класса (здесь и далее исходник приведен полный, в комментариях думаю не нуждается):

ScreenManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using System.Collections.Generic;
using Microsoft.Xna.Framework;
 
namespace WindowsGame1
{
internal static class ScreenManager
{
static List<Screen> screens = new List<Screen>();
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;
}
 
internal static Screen GetScreenByIndex(int index)
{
return screens[index];
}
 
internal static Screen GetScreenByName(string name)
{
foreach (Screen scr in screens)
if (scr.Name == name)
return scr;
 
return null;
}
 
internal static void ActivateScreen(Screen screen)
{
prevScreen = ActiveScreen;
 
if (ActiveScreen != null)
ActiveScreen.Remove();
 
ActiveScreen = screen;
 
if (isStarted)
ActiveScreen.Initialize();
}
 
internal static void ActivateScreenByIndex(int index)
{
prevScreen = ActiveScreen;
 
if (ActiveScreen != null)
ActiveScreen.Remove();
 
ActiveScreen = GetScreenByIndex(index);
 
if (isStarted)
ActiveScreen.Initialize();
}
 
internal static void ActivateScreenByName(string name)
{
prevScreen = ActiveScreen;
 
ActiveScreen = GetScreenByName(name);
 
if (isStarted)
ActiveScreen.Initialize();
}
 
internal static void ActivatePreviousScreen()
{
if (prevScreen != null)
ActivateScreenByName(prevScreen.Name);
}
 
internal static void Initialize()
{
isStarted = true;
 
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, о котором пока не знает студия:

5

Так что же это за объект?
Это наш будущий базовый класс для всех игровых экранов. Он содержит минимум необходимых виртуальных методов, необходимых для отрисовки и обновления экрана-наследника, которые будем переопределять в наследуемых от него экранах.
Для начала создадим еще один класс Screen.cs в папке ScreenManager. Вот его полный исходный код:

Screen.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using Microsoft.Xna.Framework;
 
namespace WindowsGame1
{
internal class Screen
{
internal string Name { get; private set; }
 
internal Screen(string name)
{
Name = name;
}
 
internal virtual bool Initialize() { return true; }
 
internal virtual void Remove() { }
 
internal virtual void Update(GameTime gameTime) { }
 
internal virtual void Draw(GameTime gameTime) { }
 
}
}

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

Но для начала создадим еще один статический класс-"хранилище" ссылок на общеигровые объекты нашей игры. Этот класс обеспечит нам доступ к необходимым объектам, так как ScreenManager является не GameComponent. Вот его исходный код:
Commons.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
 
namespace WindowsGame1
{
internal static class Commons
{
internal static Game Game;
internal static ContentManager Content;
internal static GraphicsDevice GraphicsDevice;
internal static SpriteBatch SpriteBatch;
}
}

Определим поля класса Commons и проинициализируем наш менеджер в "главном" классе Game1:

Game1.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
 
namespace WindowsGame1
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
 
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
 
IsMouseVisible = true;
}
 
protected override void Initialize()
{
base.Initialize();
}
 
protected override void LoadContent()
{
Commons.Game = this;
Commons.Content = Content;
Commons.GraphicsDevice = graphics.GraphicsDevice;
Commons.SpriteBatch = new SpriteBatch(graphics.GraphicsDevice);
 
ScreenManager.AddScreen(new MainMenuScreen());
ScreenManager.AddScreen(new TestScreen());
ScreenManager.ActivateScreenByName("MainMenuScreen");
ScreenManager.Initialize();
}
 
protected override void UnloadContent()
{
}
 
protected override void Update(GameTime gameTime)
{
ScreenManager.Update(gameTime);
InputManager.Update(gameTime);
 
base.Update(gameTime);
}
 
protected override void Draw(GameTime gameTime)
{
ScreenManager.Draw(gameTime);
 
base.Draw(gameTime);
}
 
}
}

Как видно, мы убрали из Game1 экземпляр класса SpriteBatch, и "перенесли" его в Commons.

Также убрана очистка буфера экрана GraphicsDevice.Clear(Color.CornflowerBlue); в методе Draw(), так как отрисовка будет выполняться в наших игровых экранах.

Теперь добавим в нашу игру пару экранов: MainMenuScreen и TestScreen в новую папку Screens проекта. Для наглядной реализации в проекте сделаны лишь переходы между экранами. Этого вполне достаточно чтобы убедиться в работоспособности менеджера.
Забегая немного вперед отмечу, что в классах экранов мы опять увидим некий объект MenuItem. Это простой класс элемента меню, со своими полями и свойствами, облегчающими обработку состояния каждого элемента меню. Организация меню в целом и пунктов меню не совсем идеальная, но суть понятна из простой реализации. Вот его код:
MenuItem.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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);
 
Action = action;
}
internal bool Hovered()
{
return Rect.Contains(InputManager.GetMousePositionToPoint());
}
 
internal bool Clicked()
{
return Hovered() && InputManager.IsMouseLeftClick();
}
 
}
}

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

InputManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
 
namespace WindowsGame1
{
internal static class InputManager
{
static KeyboardState keyState;
static KeyboardState keyOldState;
static MouseState mouseState;
static MouseState mouseOldState;
 
static InputManager()
{
}
 
internal static void Update(GameTime gameTime)
{
keyOldState = keyState;
mouseOldState = mouseState;
 
keyState = Keyboard.GetState();
mouseState = Mouse.GetState();
}
 
internal static bool IsKeyDown(Keys key)
{
return keyState.IsKeyDown(key);
}
 
internal static bool IsKeyPress(Keys key)
{
return IsKeyDown(key) && keyOldState.IsKeyUp(key);
}
 
internal static bool IsMouseLeftDown()
{
return mouseState.LeftButton == ButtonState.Pressed;
}
 
internal static bool IsMouseLeftClick()
{
return IsMouseLeftDown() && mouseOldState.LeftButton == ButtonState.Released;
}
 
internal static bool IsMouseRightDown()
{
return mouseState.RightButton == ButtonState.Pressed;
}
 
internal static bool IsMouseRightClick()
{
return IsMouseRightDown() && mouseOldState.RightButton == ButtonState.Released;
}
 
internal static bool IsMouseMiddleDown()
{
return mouseState.MiddleButton == ButtonState.Pressed;
}
 
internal static bool IsMouseMiddleClick()
{
return IsMouseMiddleDown() && mouseOldState.MiddleButton == ButtonState.Released;
}
 
internal static bool IsMouseWheelUp()
{
return mouseState.ScrollWheelValue > mouseOldState.ScrollWheelValue;
}
 
internal static bool IsMouseWheelDown()
{
return mouseState.ScrollWheelValue < mouseOldState.ScrollWheelValue;
}
 
internal static Vector2 GetMousePositionToVector2()
{
return new Vector2(mouseState.X, mouseState.Y);
}
 
internal static Point GetMousePositionToPoint()
{
return new Point(mouseState.X, mouseState.Y);
}
 
}
}

Экран MainMenuScreen содержит лишь два пункта меню: "Играть" и "Выход":

6

MainMenuScreen.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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<MenuItem> menuItems = new List<MenuItem>();
Color itemNormalColor = Color.White;
Color itemHoverColor = Color.Red;
 
internal MainMenuScreen()
: base("MainMenuScreen")
{
}
 
internal override bool Initialize()
{
font = Commons.Content.Load<SpriteFont>("Fonts/menuFont");
 
menuItems.AddRange(new MenuItem[]
{
new MenuItem(
"Play / Играть",
font,
new Vector2(
Commons.GraphicsDevice.Viewport.Width / 2,
Commons.GraphicsDevice.Viewport.Height * 4 / 6),
new Action(() => { ScreenManager.ActivateScreenByName("TestScreen"); } )),
new MenuItem(
"Exit / Выход",
font,
new Vector2(
Commons.GraphicsDevice.Viewport.Width / 2,
Commons.GraphicsDevice.Viewport.Height * 5 / 6),
new Action(() => { Commons.Game.Exit(); } )),
});
 
return base.Initialize();
}
 
internal override void Remove()
{
base.Remove();
}
 
internal override void Update(GameTime gameTime)
{
if (InputManager.IsKeyPress(Keys.Escape))
Commons.Game.Exit();
 
for (int i = 0; i < menuItems.Count; i++)
{
if (menuItems[i].Clicked())
{
menuItems[i].Action.Invoke();
break;
}
}
 
base.Update(gameTime);
}
 
internal override void Draw(GameTime gameTime)
{
Commons.GraphicsDevice.Clear(Color.Gray);
 
Commons.SpriteBatch.Begin();
 
for (int i = 0; i < menuItems.Count; i++)
{
Commons.SpriteBatch.DrawString(
font,
menuItems[i].Text,
menuItems[i].Position,
menuItems[i].Hovered() ? itemHoverColor : itemNormalColor);
}
 
Commons.SpriteBatch.End();
 
base.Draw(gameTime);
}
 
}
}

Пункт "Играть" указывает нашему ScreenManager, чтобы тот "переключил" игровой экран на TestScreen. Клавишей Escape игра завершается.

Экран TestScreen содержит экземпляр MenuItem, у которого Action "просит" ScreenManager переключить экран на MainMenuScreen. То же самое происходит по нажатию клавиши Escape.
Также в центре этого экрана для наглядности крутится сфера с текстурой нашей планеты. Модель очень низкополигональная, ибо это не суть совсем, главное механизм в целом. Для интересующихся количеством полигонов скрин с блендера ниже по тексту.
7

TestScreen.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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<MenuItem> menuItems = new List<MenuItem>();
Color itemNormalColor = Color.White;
Color itemHoverColor = Color.Red;
 
Model sphere;
Matrix world;
Matrix view;
Matrix proj;
 
internal TestScreen()
: base("TestScreen")
{
world = Matrix.Identity;
view = Matrix.CreateLookAt(
new Vector3(5f, 1f, 0f),
Vector3.Zero,
Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4,
Commons.GraphicsDevice.Viewport.AspectRatio,
0.1f, 10f);
}
 
internal override bool Initialize()
{
font = Commons.Content.Load<SpriteFont>("Fonts/font1");
sphere = Commons.Content.Load<Model>("Models/sphere");
 
menuItems.AddRange(new MenuItem[]
{
new MenuItem(
"Меню",
font,
new Vector2(
Commons.GraphicsDevice.Viewport.Width - font.MeasureString("Меню").X,
font.MeasureString("Меню").Y / 2),
new Action(() => { ScreenManager.ActivateScreenByName("MainMenuScreen"); } )),
});
 
return base.Initialize();
}
 
internal override void Remove()
{
base.Remove();
}
 
internal override void Update(GameTime gameTime)
{
if (InputManager.IsKeyPress(Keys.Escape))
ScreenManager.ActivateScreenByName("MainMenuScreen");
 
for (int i = 0; i < menuItems.Count; i++)
{
if (menuItems[i].Clicked())
{
menuItems[i].Action.Invoke();
break;
}
}
 
world *= Matrix.CreateRotationY((float)gameTime.ElapsedGameTime.TotalSeconds);
 
base.Update(gameTime);
}
 
internal override void Draw(GameTime gameTime)
{
Commons.GraphicsDevice.Clear(Color.Transparent);
 
Commons.SpriteBatch.Begin();
 
for (int i = 0; i < menuItems.Count; i++)
{
Commons.SpriteBatch.DrawString(
font,
menuItems[i].Text,
menuItems[i].Position,
menuItems[i].Hovered() ? itemHoverColor : itemNormalColor);
}
 
Commons.SpriteBatch.End();
 
sphere.Draw(world, view, proj);
 
base.Draw(gameTime);
}
 
}
}

Модель сферы в исходном виде в окне блендера:

8

Как видно, ничего сложного в реализации классов экранов нет, всё это Вы уже проделывали, упражняясь с программированием в XNA GS, только код был прямо в Game1. В подходе, описанном в статье, сохраняется эта простота, как если бы Вы писали код только для одного, главного экрана.

P.S.
Статья, как уже говорилось в начале, расчитана на новичков. Однако не вижу никаких причин не использовать данный подход в сложных проектах. Код классов экранов ничем не задушен и не отягощен и позволяет творить с ними всё что в голову придёт.

P.P.S.
Весь рассмотренный в статье код проекта работает без каких-либо правок как на эмуляторе Windows Phone 7, так и на реальном телефоне. Единственное отличие (а как же!) - в классе InputManager. В него придется добавить свой функционал обработки device-only управления.

P.P.P.S.
Как говорится - почувствуй разницу (с AppHub-овским GameStateManagement).

Критика, пожелания, замечания и тосты, конечно же, приветствуются. Равно как и расширение функционала рассмотренного метода :)

Исходный код проекта: Скачать.
Комментарии
#1 | Chort 04.10.2011 17:16:48
два замечания по поводу кода (ну не нравится мне)
- много internal
- много static
а так вариант имеет право на жизнь, имеет свои плюсы, +1

P.S. В своем варианте что ScreenManager, что сам Screen наследовал от DrawableGameComponent
#2 | beaver 04.10.2011 17:37:47
Принято Smile
По поводу internal: и согласен и нет. Оно не мешает, public ведь тоже везде. А вообще я просто взял да переименовал везде группой Smile
По поводу static: если класс статический - куда денешься? А статикой контролировать удобней.
И, в целом, код очень легок для понимания, при этом функционал на уровне.
#3 | Jam 04.10.2011 19:03:46
Русская студия - жестьGrin
#4 | beaver 04.10.2011 20:48:13
Я вот вообще не обращаю внимания на язык студии, я не фанатик. А смотреть в русские хинты все-таки приятнее...
#5 | ArtFeel 05.10.2011 21:40:44
А чем плох АррХабовский GameStateManagement?
#6 | beaver 05.10.2011 22:24:31
ArtFeel, хотя бы количеством букаф Smile
#7 | gforcer18 07.10.2011 20:33:29
осталась статья про кинект, и можно считать год продуктивным
#8 | za5 08.10.2011 15:54:42
Jam, русская студия очень хороша тем, что там вся докуменация по всем методам на русском. В английской вообще смысла не вижу.
#9 | Jam 10.10.2011 08:20:02
С русской студией проблемы начинаются когда лезут исключения. Попробуйте сделать поиск по этим исключениямGrin Да и перевод оставляет желать лучшего.
#10 | YOYOMAN 17.01.2012 16:17:12
А для студии 3,1 этот прример подойдёт?
Добавить комментарий
Пожалуйста, залогиньтесь для добавления комментария.
Рейтинги
Рейтинг доступен только для пользователей.

Пожалуйста, залогиньтесь или зарегистрируйтесь для голосования.

Отлично! Отлично! 89% [8 Голоса]
Очень хорошо Очень хорошо 0% [Нет голосов]
Хорошо Хорошо 0% [Нет голосов]
Удовлетворительно Удовлетворительно 11% [1 Голос]
Плохо Плохо 0% [Нет голосов]
Авторизация
Логин

Пароль



Вы не зарегистрированы?
Нажмите здесь для регистрации.

Забыли пароль?
Запросите новый здесь.
Мини-чат
Вы должны авторизироваться, чтобы добавить сообщение.

27.08.2014
Я умею немного на asp.net + html и css

22.08.2014
на ASP mvc 3 есть пару проектов. Могу помочь, если нужно. Обидно, если закроется Frown

21.08.2014
я тоже ноль

21.08.2014
Я в вебе только с php занимался да и то на уровне чтоб работало.

21.08.2014
Я в вебе полный ноль…

21.08.2014
Переводить его надо, хоть на ту же азуру. И двиг менять на что-то современное. Если есть веб-разрабы - можем скооперироваться. Один делать не буду.

21.08.2014
не знаю всех нюансов по оплате и все хорошее когда нибудь заканчивается

21.08.2014
А что случилось?

21.08.2014
похоже сайт будет работать до 28го числа

09.08.2014
Апи пока не видел. Но есть приложение в магазине Live Lock Screen BETA, так что думаю скоро будет

08.08.2014
Я про API для Update1. На нем работает это

08.08.2014
А что именно нужно? Чтото и сейчас открыто http://msdn.micro.
...105).aspx

06.08.2014
Кто-нибудь слышал об открытии доступа к Lock Screen Api?

31.07.2014
VPDExpress на базе MVS 2012, ни в какую не ловит исключения. Даже если их сам создаешь. И всех так?

25.07.2014
С днем системного администратора причастных к этой профессии! По случаю - тортик от жены

RSS каналы сайта
XNA - Новости
XNA - Статьи
XNA - Форум
XNA - Галерея
XNA - Файлы
Время загрузки: 0,08 секунд 8,709,594 уникальных посетителей