Разработка игр для Windows 8 с использованием MonoGame: поддержка сенсорных экранов

В предыдущей заметке было рассказано о основных моментах связанных с разработкой игр для Windows 8 и MonoGame. Несмотря на то что игра уже запускается, в ней не хватает важных функций которые необходимы при реализации приложения для Windows 8. Это поддержка прикосновений и обязательный экран privacy policy.

Реализация поддержки прикосновений

ОС Windows 8 может запускаться на самом различном спектре устройств, в том числе на планшетах у которых отсутствует клавиатура. Наша игра является классическим платформером, для управления персонажем в игре реализована только поддержка геймпада и клавиатуры. Для поддержки устройств без клавиатуры но с сенсорным экраном необходимы дополнения в коде.

Текущая реализация MonoGame для Windows 8 позволяет опрашивать состояние сенсора экрана с помощью класса TouchPanel из области видимости Xna.Framework.Input.Touch.Поддерживаются жесты, и комбинированное взаимодействие с мышью. В простейшем случае, для того чтобы выяснить, есть ли нажатие на экран достаточно вызвать методGetState() который вернет список точек к которым на данный момент осуществлено касание:

foreach (TouchLocation location in TouchPanel.GetState()) { switch (location.State) { case TouchLocationState.Moved: // location.Position.X; // location.Position.Y; // location.Pressure // location.Id break; case TouchLocationState.Pressed: break; case TouchLocationState.Released: break; case TouchLocationState.Invalid: break; } }

Возвращаемый обьект TouchLocation содержит данные о типе, координатах касания, его порядковом номере (стандартно поддерживается до пяти одновременных точек) и силе нажатия. 

Для поддержки касаний мы реализуем стандартный, принятый во многих играх подход – просто будем рисовать на экране изображение клавиш курсора и кнопку прыжка. Далее останется только вычислить координаты областей касаний, и опрашивать их на факт нажатия.

В самой игре CastleX обработка событий управления реализована двумя способами. Основное меню обратабывает события с помощью класса InputState в каждом методеUpdate() (перед вызовом Draw). При этом класс InputState помнит о предыдущем состоянии нажатия клавиш. Дополнительно, для управления игроком используется прямой опрос состояния клавиатуры. Эти стандартные обработчики требуется модифицировать.

Первое что нам нужно сделать это создать изображения курсорных клавиш и кнопки прыжка. Используя Paint.NET были созданы два PNG файла с прозрачностью:



Далее эти файлы нужно поместить в каталог Assets/GameContent. В принципе у нас даже нет необходимости создавать XNB файл, подсистема контента в MonoGame может прочитать файлы PNG напрямую.


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

Загружаем файл текстуры в методе LoadContent():

arrowkeys_texture = Content.Load<Texture2D>(@"transparent_arrow_keys");

И отображаем на экране в методе Draw()

SpriteBatch.Begin(); SpriteBatch.Draw(arrowkeys_texture, drawPos, new Color(0, 0, 0)); SpriteBatch.End();

При этом будет учтен альфаканал в PNG файле и в результате на экране отобразятся полупрозрачные изображения курсорных клавиш и кнопки:


Но пока это просто статичные изображения и необходимо обрабатывать события нажатия в этой области.

Для этого будет использоваться простой способ – просто зададим координаты области нажатия и будем проверять эту координату на состояние TouchLocationState.Moved

Так же хотелось бы сохранить текущую инфраструктуру обработки событий от клавиатуры и геймпада реализованную в классе InputState.

В специальном методе AddTouchKey(Keys key, Rectangle rect) будет задаваться координата ассоциированная с клавишей, и помимо опроса состояния клавиатуры так же будет вызван метод UpdateTouchKeys()

internal bool UpdateTouchPos(Dictionary<Keys, bool> key_states ) { bool touched = false; foreach (Keys key in _touchKeys.Keys) key_states.Add(key,false); foreach (TouchLocation location in TouchPanel.GetState()) { touched = true; foreach (var k in _touchKeys.Keys) { if (location.State == TouchLocationState.Moved) { if (_touchKeys[k].Contains((int)location.Position.X, (int)location.Position.Y)) { key_states[k] = true; } } } } return touched; }

Этот метод опрашивает все нажатые координаты, и если они входят в список областей нажатия ассоциированных с клавишами, эта кнопка вносится в список нажатых клавиш для дальнейшего использования в методах IsKeyDown() и IsKeyUp()

public bool IsKeyDown(Keys key) { if (touchKeys.ContainsKey(key) && isTouched) return touchKeys[key]; else return orig_key_state.IsKeyDown(key); }

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

Input.AddTouchKey(Keys.Down, hitPosDown); Input.AddTouchKey(Keys.Up, hitPosUp); Input.AddTouchKey(Keys.Left, hitPosLeft); Input.AddTouchKey(Keys.Right, hitPosRight); Input.Update();

Добавляем в игру соглашение о приватности

Одним из важных условий прохождения сертификации при публикации приложений является наличие соглашение о приватности (privacy policy). 


Для приложений на MonoGame такой экран реализуется простым способом, в методе Init основгого класса игры производится подписка на события вызова свойств приложения и задается обработчик:

protected override void Initialize() { base.Initialize(); SettingsPane.GetForCurrentView().CommandsRequested += PaneRequest; } void PaneRequest(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args) { UICommandInvokedHandler handler = new UICommandInvokedHandler(onPrivacyPolicyCommand); var privPolicyCmd = new SettingsCommand("PrivacyPolicyId", "Privacy Policy", handler); args.Request.ApplicationCommands.Add(privPolicyCmd); } void onPrivacyPolicyCommand(IUICommand command) { Launcher.LaunchUriAsync(new Uri("http://wincommunity.ru/baller/privacy.html")); }

Загрузить исходный проект этими изменениями вы можете по адресу aka.ms/cgt2fr

Автор статьи: Дмитрий Андреев.