Разработка компьютерных игр для Windows Phone 7 с использованием технологий Silverlight и XNA

Использование данных о местоположении, другие способы управления

Разбить на страницы
Показывать лекцию целиком

Цель работы: Научиться работать с данными о географическом положении, использовать жесты для управления программами

13.1. Географические координаты

Для управления играми, написанными для Windows Phone, можно пользоваться самыми разными датчиками. В частности, датчиком местоположения. В XNA-проекте мы можем получать данные о местоположении устройства, выраженные в географических координатах, от встроенного GPS-приемника, который дополняется системой A-GPS.

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

Создадим новый стандартный XNA-проект (P7_1). Для использования служб работы с местоположением нам понадобится подключить (в разделе Ссылки игрового проекта) библиотеку System.Device, пространство имён System.Device.Location.

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Device.Location;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace P7_1
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        //Строка для вывода
        string text="Please wait";
        //Для хранения позиции вывода текста 
        Vector2 textPosition = new Vector2(100, 100);
        //Для хранения шрифта
        SpriteFont MyFont;
        //Объект для работы с данными о местоположении
        GeoCoordinateWatcher GeoWatch;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            //Отображение игрового окна на полный экран, без строки состояния
            graphics.IsFullScreen = true;
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            //Новый объект для работы с местоположением
            GeoWatch = new GeoCoordinateWatcher();
            //Обработчик события, которое происходит при изменении местоположения
            GeoWatch.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(GeoWatch_PositionChanged);
            //Запуск сервиса
            GeoWatch.Start();

            base.Initialize();
        }

        void GeoWatch_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            //Записываем в переменную для вывода на экран следующие данные:
            //Широта
            //Долгота
            //Высота
            //Курс
            //Скорость
            //Отметка времени
            text = String.Format("Latitude: {0}\r\n" +
                                "Longitude: {1}\r\n" +
                                "Altitude: {2}\r\n" +
                                "Course: {3}\r\n"+
                                "Speed: {4}\r\n"+
                                "Current time: {5}",
                                e.Position.Location.Latitude,
                                e.Position.Location.Longitude,
                                e.Position.Location.Altitude,
                                e.Position.Location.Course,
                                e.Position.Location.Speed,
                                e.Position.Timestamp);
             }
        
        ///// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            // Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            //Загружаем шрифт
            MyFont = Content.Load<SpriteFont>("MyFont");

        }

        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
         
            base.Update(gameTime);
        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            //Выводим строку
            spriteBatch.DrawString(MyFont, text, textPosition, Color.Red,
                0, new Vector2(0, 0), 1.0f, SpriteEffects.None, 0.5f);
            spriteBatch.End();
            base.Draw(gameTime);
        }


    }
}

В данном проекте мы пользуемся классом GeoCoordinateWatcher(), создаем обработчик события PositionChanged, в коде которого записываем полученные от устройства данные в текстовую переменную для вывода на экран.

На рис. 13.1 вы можете видеть программу, запущенную в эмуляторе. Обратите внимание на то, что среди дополнительных инструментов эмулятора есть средство для эмуляции географических координат.

(рис 13.1) Запуск программы в эмуляторе

13.2. Обработка жестов, вибрация

На одном из предыдущих занятий мы рассматривали обработку события простого касания в XNA. Windows Phone, помимо обычных касаний, поддерживает большой набор жестов, распознавая которые, можно значительно расширить способы управления игрой.

Windows Phone поддерживает распознавание множества жестов. В частности, таких, как FreeDrag – то есть – свободное перетаскивание по экрану, DoubleTap – быстрое двойное касание экрана. Flick – касание с быстрым перемещением, Hold – касание и удержание пальца на панели, Pinch – сведение или разведение двух пальцев на панели, обычно используется для целей масштабирования объектов. Эти и другие жесты позволяют реализовать самые разные схемы управления объектами на экране. Например, объекты можно "раскладывать" по экрану в нужной последовательности, управлять скоростью и направлением их перемещения, масштабировать – здесь всё зависит от целей и фантазии разработчика. В нашем примере мы используем обработку двух жестов – FreeDrag и DoubleTap.

Кроме того, мы включили в пример работу с виброзвонком телефона. В определенный момент мы будем включать вибрацию. Этот приём часто используется в играх для того, чтобы добавить им реалистичности. Например, устройство может вибрировать при столкновении каких-то объектов, при падении и во многих других случаях.

Обработка жестов не требует каких-то дополнительных действий, всё, что нужно, уже подключено в стандартном проекте. Для работы с вибрацией нам понадобится подключить библиотеку Microsoft.Phone и пространство имен Microsoft.Devices

Кроме того, здесь мы, в учебных целях, рассмотрим удобный механизм отладки приложений с использованием сообщений, выводимых в окно отладчика. Для этого нам понадобится пространство имён System.Diagnostics.

Новый стандартный XNA-проект (P7_2) содержит игровой компонент, spriteComp.cs – это модифицированная версия использованного ранее компонента, один графический ресурс – rectangle.png – прямоугольник, который мы будем перемещать по экрану. Внешний вид окна Обозреватель решений вы можете видеть на рис. 13.2.

(рис 13.2) Игровой проект P7_2

В листинге 13.2 вы можете видеть код игрового компонента

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
//Подключим для отладочных целей
using System.Diagnostics;

namespace P7_2
{
    /// <summary>
    /// Это игровой компонент, реализующий интерфейс IUpdateable.
    /// </summary>
    public class spriteComp : Microsoft.Xna.Framework.DrawableGameComponent
    {
        //Изображение
        private Texture2D sprTexture;
        //Прямоугольник, ограничивающий спрайт
        private Rectangle sprRectangle;
        //Координата спрайта
        private Vector2 sprPosition;
        //Границы экрана
        private Rectangle scrBounds;
        //Направление движения спрайта
        private Vector2 sprMove = new Vector2(0, 0);
        public spriteComp(Game game, ref Texture2D newTexture,
            Rectangle newRectangle, Vector2 newPosition
)
            : base(game)
        {
            sprTexture = newTexture;
            sprRectangle = newRectangle;
            sprPosition = newPosition;
            //Работаем в портретном режиме, высота - 480, ширина - 800
            scrBounds = new Rectangle(0, 0,
                game.Window.ClientBounds.Height,
                game.Window.ClientBounds.Width
                );
        }

        /// <summary>
        /// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском. 
         Здесь можно запросить нужные службы и загрузить контент.
        /// 
        /// </summary>
        public override void Initialize()
        {
            // ЗАДАЧА: добавьте здесь код инициализации

            base.Initialize();
        }

        /// <summary>
        /// Позволяет игровому компоненту обновиться.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        public override void Update(GameTime gameTime)
        {
            //Изменение координат в соответствии с данными, имеющимися в sprMove
            sprPosition += sprMove;
            //Если вышли за пределы экрана - исправляем
            if (sprPosition.X < scrBounds.Left)
            {
                sprPosition.X = scrBounds.Left;
            }
            if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
            {
                sprPosition.X = scrBounds.Width - sprRectangle.Width;
            }
            if (sprPosition.Y < scrBounds.Top)
            {
                sprPosition.Y = scrBounds.Top;
            }
            if (sprPosition.Y > scrBounds.Height - sprRectangle.Height)
            {
                sprPosition.Y = scrBounds.Height - sprRectangle.Height;
            }

            base.Update(gameTime);
        }
        public override void Draw(GameTime gameTime)
        {
            SpriteBatch sprBatch =
                (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));

            sprBatch.Draw(sprTexture, sprPosition, sprRectangle, Color.White);

            base.Draw(gameTime);
        }
        //Метод для установки направления движения спрайта
        public void Move(Vector2 move)
        {
            //Зададим скорость и направление движени спрайта
             sprMove = move;
            //Выведем данные для целей отладки
            Debug.WriteLine(move.ToString());
        }
        //Получение позиции спрайта для внешних целей
        public Vector2 GetSprPos()
        {
            return sprPosition;
        }
        //Получение прямоугольника, ограничивающего спрайт, для врешних целей
        public Rectangle GetSprRectangle()
        {
            return sprRectangle;
        }
    }
}

В целом этот код уже вам знаком по предыдущим примерам. Опишем его ключевые особенности. Метод Move принимает параметр move, который записывается в переменную sprMove.

В методе компонента Update эта переменная используется для модификации позиции спрайта. После модификации производится контроль на пересечение спрайтом границ экрана. В компоненте мы подключили пространство имён System.Diagnostics, в методе Move мы выводим в окно отладочных сообщений переданный в объект параметр move, на рис. 13.3 вы можете видеть это окно.

(рис 13.3) Отладочные сообщения

Отладочные сообщения – очень ценный инструмент для исследования программ, не следует забывать о нём при разработке игр.

Рассмотрим теперь код основной программы, листинг 13.3.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Devices;

namespace P7_2
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        spriteComp gameObject;
        //Для хранения ID касаний
        int gameObjectId;
        //Изображение для вывода
        Texture2D texture;
        //Следует ли перемещать объект
        bool gameObjectMove;
        
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.IsFullScreen = true;
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
            //Включить обработку жестов - FreeDarag и DoubleTap
            TouchPanel.EnabledGestures = GestureType.FreeDrag | GestureType.DoubleTap;
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            // ЗАДАЧА: добавьте здесь логику инициализации

            base.Initialize();
        }

        /// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            // Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Services.AddService(typeof(SpriteBatch), spriteBatch);
            texture = Content.Load<Texture2D>("rectangle");
            //Создаём игровой объект
            CreateNewObject();
        }

        protected void CreateNewObject()
        {
            gameObject = new spriteComp(this, ref texture,
                new Rectangle(0, 0, 150, 100),
                new Vector2(0, 0));
            Components.Add(gameObject);
        }


        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            //Получаем коллекцию объектов, содержащих информацию о касаниях экрана
            TouchCollection touchLocations = TouchPanel.GetState();
            //Перебираем коллекцию, присваивая объекту координаты касания
            foreach (TouchLocation touchLocation in touchLocations)
            {
                //Если обнаружено касание в области, соответствующей игровому объекту
                if (touchLocation.State == TouchLocationState.Pressed)
                {
                    if (touchLocation.Position.X > gameObject.GetSprPos().X 
                        touchLocation.Position.X < gameObject.GetSprPos().X + gameObject.GetSprRectangle().Width 
                        touchLocation.Position.Y > gameObject.GetSprPos().Y 
                        touchLocation.Position.Y < gameObject.GetSprPos().Y + gameObject.GetSprRectangle().Height)
                    {
                        //Разрешаем перемещать объект
                        gameObjectMove = true;
                        //Запоминаем идентификатор касания
                        gameObjectId = touchLocation.Id;
                        
                    }
                }

                //Если касание окончилось
                if (touchLocation.State == TouchLocationState.Released)
                {
                    //Проверяем ID
                    if (gameObjectId == touchLocation.Id)
                    {
                        //Запрещаем перемещение
                        gameObjectMove = false;
                    }
                }

            }
            //До тех пор, пока разрешена обработка жестов
            while (TouchPanel.IsGestureAvailable)
            {
                //прочитаем жест
                GestureSample gs = TouchPanel.ReadGesture();
                //Если жест - FreeDrag и разрешено перемещение 
                //то есть, пользователь касается объекта на экране
                if (gs.GestureType == GestureType.FreeDrag  gameObjectMove==true)
                {
                    //Начнем перемещать объект со скоростью, заданной параметром Delta
                    //Фактически - чем быстрее был выполнен жест - тем быстрее будет двигаться объект
                    //в направлении жеста
                    gameObject.Move(gs.Delta);
                    
                }
                //Если обнаружен жест двойного касания экрана
                if (gs.GestureType == GestureType.DoubleTap)
                {
                    //Остановим движение объекта
                    gameObject.Move(new Vector2());
                    //Включим вибраацию на 0,1 секунды
                    VibrateController.Default.Start(TimeSpan.FromMilliseconds(100));
                }
         
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();
            base.Draw(gameTime);
            spriteBatch.End();
        }
       
    }
}

Командой TouchPanel.EnabledGestures = GestureType.FreeDrag | GestureType.DoubleTap; мы включаем обработку жестов и указываем, с какими именно жестами собираемся работать. В нашем случае это FreeDrag и DoubleTap.

Мы хотим реализовать следующий функционал. Когда пользователь касается игровой объект на экране и проводит пальцем по экрану (жест FreeDrag), объект начинает перемещаться в направлении, в котором пользователь провёл пальцем, причем, скорость перемещения объекта задаётся скоростью перемещения пальца. Если пользователь коснётся точки, которая не принадлежит объекту, тот реагировать не будет – за это отвечает код, который, прежде чем разрешить движение объекта, проверяет точку касания.

Для этого используется обычная, уже знакомая вам, обработка касания. Объект в нашем примере напоминает лёгкую пластинку, которая плавает по поверхности прямоугольной ёмкости, наполненной жидкостью. Если пользователь приведет объект в движение – тот будет двигаться с заданными параметрами до тех пор, пока не упрётся в границу экрана. Для того, чтобы остановить движение объекта, мы применяем жест DoubleTap. Этот жест напоминает двойной щелчок мыши. Когда пользователь выполнит этот жест, объект останавливается и на 0,1 секунды включается вибрация телефона.

Мы используем здесь параметр жеста FreeDrag Delta для реализации управления объектом, если, например, использовать координаты касания (они постоянно обновляются при перемещении пальца по экрану), можно организовать управление объектом, подобное перемещению объекта по плоской поверхности, когда его можно "придавить" пальцем и переместить в нужную позицию.

На рис. 13.4 вы можете видеть игровой экран приложения

(рис 13.4) Игровой экран приложения

13.3. Другие возможности

В предыдущих практических работах мы разобрали различные способы управления игровым процессом с использованием сенсорной панели, акселерометра, GPS-приёмника. Этими устройствами не ограничиваются возможности по управлению игрой. Так, весьма полезен может быть гироскоп, который регистрирует углы поворота устройства. Можно пользоваться данными, которые поставляет электронный компас.

Кроме того, различные устройства можно комбинировать – именно так поступают разработчики многих современных игр. Например, если речь идёт о симуляторе автогонок Need For Speed Hot Pursuit, управление автомобилем осуществляется с помощью поворотов экрана, дополнительное ускорение можно включить, выполнив жест, судя по всему, являющийся жестом VerticalDrag (проведя пальцем вдоль правой части экрана в ландшафтной ориентации), а задействовать тормоза – коснувшись левой части экрана.

Обилие способов управления игровым процессом даёт огромный простор для творчества. Разрабатывая систему управления игрой, обратите внимание на Combined Motion API (пространство имён Microsoft.Device.Sensors), класс Motion. Это API позволяет получать данные от гироскопа, акселерометра и электронного компаса и предоставляет полезные инструменты для применения этих данных в управлении игровым процессом.

13.4. Выводы

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

13.5. Задание

Реализуйте распознавание различных жестов в XNA-программе, изучите особенности распознавания жестов, посмотрите, распознавание каких видов жестов может накладываться друг на друга, используя отладочные механизмы.

13.6. Дополнительные материалы

К данной лабораторной работе прилагается видеоурок

и демонстрационный проект.

Страницы:

Цель работы: Научиться работать с данными о географическом положении, использовать жесты для управления программами

13.1. Географические координаты

Для управления играми, написанными для Windows Phone, можно пользоваться самыми разными датчиками. В частности, датчиком местоположения. В XNA-проекте мы можем получать данные о местоположении устройства, выраженные в географических координатах, от встроенного GPS-приемника, который дополняется системой A-GPS.

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

Создадим новый стандартный XNA-проект (P7_1). Для использования служб работы с местоположением нам понадобится подключить (в разделе Ссылки игрового проекта) библиотеку System.Device, пространство имён System.Device.Location.

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Device.Location;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace P7_1
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        //Строка для вывода
        string text="Please wait";
        //Для хранения позиции вывода текста 
        Vector2 textPosition = new Vector2(100, 100);
        //Для хранения шрифта
        SpriteFont MyFont;
        //Объект для работы с данными о местоположении
        GeoCoordinateWatcher GeoWatch;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            //Отображение игрового окна на полный экран, без строки состояния
            graphics.IsFullScreen = true;
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            //Новый объект для работы с местоположением
            GeoWatch = new GeoCoordinateWatcher();
            //Обработчик события, которое происходит при изменении местоположения
            GeoWatch.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(GeoWatch_PositionChanged);
            //Запуск сервиса
            GeoWatch.Start();

            base.Initialize();
        }

        void GeoWatch_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            //Записываем в переменную для вывода на экран следующие данные:
            //Широта
            //Долгота
            //Высота
            //Курс
            //Скорость
            //Отметка времени
            text = String.Format("Latitude: {0}\r\n" +
                                "Longitude: {1}\r\n" +
                                "Altitude: {2}\r\n" +
                                "Course: {3}\r\n"+
                                "Speed: {4}\r\n"+
                                "Current time: {5}",
                                e.Position.Location.Latitude,
                                e.Position.Location.Longitude,
                                e.Position.Location.Altitude,
                                e.Position.Location.Course,
                                e.Position.Location.Speed,
                                e.Position.Timestamp);
             }
        
        ///// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            // Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            //Загружаем шрифт
            MyFont = Content.Load<SpriteFont>("MyFont");

        }

        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
         
            base.Update(gameTime);
        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            //Выводим строку
            spriteBatch.DrawString(MyFont, text, textPosition, Color.Red,
                0, new Vector2(0, 0), 1.0f, SpriteEffects.None, 0.5f);
            spriteBatch.End();
            base.Draw(gameTime);
        }


    }
}

В данном проекте мы пользуемся классом GeoCoordinateWatcher(), создаем обработчик события PositionChanged, в коде которого записываем полученные от устройства данные в текстовую переменную для вывода на экран.

На рис. 13.1 вы можете видеть программу, запущенную в эмуляторе. Обратите внимание на то, что среди дополнительных инструментов эмулятора есть средство для эмуляции географических координат.

(рис 13.1) Запуск программы в эмуляторе

13.2. Обработка жестов, вибрация

На одном из предыдущих занятий мы рассматривали обработку события простого касания в XNA. Windows Phone, помимо обычных касаний, поддерживает большой набор жестов, распознавая которые, можно значительно расширить способы управления игрой.

Windows Phone поддерживает распознавание множества жестов. В частности, таких, как FreeDrag – то есть – свободное перетаскивание по экрану, DoubleTap – быстрое двойное касание экрана. Flick – касание с быстрым перемещением, Hold – касание и удержание пальца на панели, Pinch – сведение или разведение двух пальцев на панели, обычно используется для целей масштабирования объектов. Эти и другие жесты позволяют реализовать самые разные схемы управления объектами на экране. Например, объекты можно "раскладывать" по экрану в нужной последовательности, управлять скоростью и направлением их перемещения, масштабировать – здесь всё зависит от целей и фантазии разработчика. В нашем примере мы используем обработку двух жестов – FreeDrag и DoubleTap.

Кроме того, мы включили в пример работу с виброзвонком телефона. В определенный момент мы будем включать вибрацию. Этот приём часто используется в играх для того, чтобы добавить им реалистичности. Например, устройство может вибрировать при столкновении каких-то объектов, при падении и во многих других случаях.

Обработка жестов не требует каких-то дополнительных действий, всё, что нужно, уже подключено в стандартном проекте. Для работы с вибрацией нам понадобится подключить библиотеку Microsoft.Phone и пространство имен Microsoft.Devices

Кроме того, здесь мы, в учебных целях, рассмотрим удобный механизм отладки приложений с использованием сообщений, выводимых в окно отладчика. Для этого нам понадобится пространство имён System.Diagnostics.

Новый стандартный XNA-проект (P7_2) содержит игровой компонент, spriteComp.cs – это модифицированная версия использованного ранее компонента, один графический ресурс – rectangle.png – прямоугольник, который мы будем перемещать по экрану. Внешний вид окна Обозреватель решений вы можете видеть на рис. 13.2.

(рис 13.2) Игровой проект P7_2

В листинге 13.2 вы можете видеть код игрового компонента

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
//Подключим для отладочных целей
using System.Diagnostics;

namespace P7_2
{
    /// <summary>
    /// Это игровой компонент, реализующий интерфейс IUpdateable.
    /// </summary>
    public class spriteComp : Microsoft.Xna.Framework.DrawableGameComponent
    {
        //Изображение
        private Texture2D sprTexture;
        //Прямоугольник, ограничивающий спрайт
        private Rectangle sprRectangle;
        //Координата спрайта
        private Vector2 sprPosition;
        //Границы экрана
        private Rectangle scrBounds;
        //Направление движения спрайта
        private Vector2 sprMove = new Vector2(0, 0);
        public spriteComp(Game game, ref Texture2D newTexture,
            Rectangle newRectangle, Vector2 newPosition
)
            : base(game)
        {
            sprTexture = newTexture;
            sprRectangle = newRectangle;
            sprPosition = newPosition;
            //Работаем в портретном режиме, высота - 480, ширина - 800
            scrBounds = new Rectangle(0, 0,
                game.Window.ClientBounds.Height,
                game.Window.ClientBounds.Width
                );
        }

        /// <summary>
        /// Позволяет игровому компоненту выполнить необходимую инициализацию перед\r\запуском. 
         Здесь можно запросить нужные службы и загрузить контент.
        /// 
        /// </summary>
        public override void Initialize()
        {
            // ЗАДАЧА: добавьте здесь код инициализации

            base.Initialize();
        }

        /// <summary>
        /// Позволяет игровому компоненту обновиться.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        public override void Update(GameTime gameTime)
        {
            //Изменение координат в соответствии с данными, имеющимися в sprMove
            sprPosition += sprMove;
            //Если вышли за пределы экрана - исправляем
            if (sprPosition.X < scrBounds.Left)
            {
                sprPosition.X = scrBounds.Left;
            }
            if (sprPosition.X > scrBounds.Width - sprRectangle.Width)
            {
                sprPosition.X = scrBounds.Width - sprRectangle.Width;
            }
            if (sprPosition.Y < scrBounds.Top)
            {
                sprPosition.Y = scrBounds.Top;
            }
            if (sprPosition.Y > scrBounds.Height - sprRectangle.Height)
            {
                sprPosition.Y = scrBounds.Height - sprRectangle.Height;
            }

            base.Update(gameTime);
        }
        public override void Draw(GameTime gameTime)
        {
            SpriteBatch sprBatch =
                (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));

            sprBatch.Draw(sprTexture, sprPosition, sprRectangle, Color.White);

            base.Draw(gameTime);
        }
        //Метод для установки направления движения спрайта
        public void Move(Vector2 move)
        {
            //Зададим скорость и направление движени спрайта
             sprMove = move;
            //Выведем данные для целей отладки
            Debug.WriteLine(move.ToString());
        }
        //Получение позиции спрайта для внешних целей
        public Vector2 GetSprPos()
        {
            return sprPosition;
        }
        //Получение прямоугольника, ограничивающего спрайт, для врешних целей
        public Rectangle GetSprRectangle()
        {
            return sprRectangle;
        }
    }
}

В целом этот код уже вам знаком по предыдущим примерам. Опишем его ключевые особенности. Метод Move принимает параметр move, который записывается в переменную sprMove.

В методе компонента Update эта переменная используется для модификации позиции спрайта. После модификации производится контроль на пересечение спрайтом границ экрана. В компоненте мы подключили пространство имён System.Diagnostics, в методе Move мы выводим в окно отладочных сообщений переданный в объект параметр move, на рис. 13.3 вы можете видеть это окно.

(рис 13.3) Отладочные сообщения

Отладочные сообщения – очень ценный инструмент для исследования программ, не следует забывать о нём при разработке игр.

Рассмотрим теперь код основной программы, листинг 13.3.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Devices;

namespace P7_2
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        spriteComp gameObject;
        //Для хранения ID касаний
        int gameObjectId;
        //Изображение для вывода
        Texture2D texture;
        //Следует ли перемещать объект
        bool gameObjectMove;
        
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.IsFullScreen = true;
            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
            //Включить обработку жестов - FreeDarag и DoubleTap
            TouchPanel.EnabledGestures = GestureType.FreeDrag | GestureType.DoubleTap;
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            // ЗАДАЧА: добавьте здесь логику инициализации

            base.Initialize();
        }

        /// <summary>
        /// LoadContent будет вызываться в игре один раз; здесь загружается
        /// весь контент.
        /// </summary>
        protected override void LoadContent()
        {
            // Создайте новый SpriteBatch, который можно использовать для отрисовки текстур.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            Services.AddService(typeof(SpriteBatch), spriteBatch);
            texture = Content.Load<Texture2D>("rectangle");
            //Создаём игровой объект
            CreateNewObject();
        }

        protected void CreateNewObject()
        {
            gameObject = new spriteComp(this, ref texture,
                new Rectangle(0, 0, 150, 100),
                new Vector2(0, 0));
            Components.Add(gameObject);
        }


        /// <summary>
        /// UnloadContent будет вызываться в игре один раз; здесь выгружается
        /// весь контент.
        /// </summary>
        protected override void UnloadContent()
        {
            // ЗАДАЧА: выгрузите здесь весь контент, не относящийся к ContentManager
        }

        /// <summary>
        /// Позволяет игре запускать логику обновления мира,
        /// проверки столкновений, получения ввода и воспроизведения звуков.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Update(GameTime gameTime)
        {
            // Позволяет выйти из игры
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            //Получаем коллекцию объектов, содержащих информацию о касаниях экрана
            TouchCollection touchLocations = TouchPanel.GetState();
            //Перебираем коллекцию, присваивая объекту координаты касания
            foreach (TouchLocation touchLocation in touchLocations)
            {
                //Если обнаружено касание в области, соответствующей игровому объекту
                if (touchLocation.State == TouchLocationState.Pressed)
                {
                    if (touchLocation.Position.X > gameObject.GetSprPos().X 
                        touchLocation.Position.X < gameObject.GetSprPos().X + gameObject.GetSprRectangle().Width 
                        touchLocation.Position.Y > gameObject.GetSprPos().Y 
                        touchLocation.Position.Y < gameObject.GetSprPos().Y + gameObject.GetSprRectangle().Height)
                    {
                        //Разрешаем перемещать объект
                        gameObjectMove = true;
                        //Запоминаем идентификатор касания
                        gameObjectId = touchLocation.Id;
                        
                    }
                }

                //Если касание окончилось
                if (touchLocation.State == TouchLocationState.Released)
                {
                    //Проверяем ID
                    if (gameObjectId == touchLocation.Id)
                    {
                        //Запрещаем перемещение
                        gameObjectMove = false;
                    }
                }

            }
            //До тех пор, пока разрешена обработка жестов
            while (TouchPanel.IsGestureAvailable)
            {
                //прочитаем жест
                GestureSample gs = TouchPanel.ReadGesture();
                //Если жест - FreeDrag и разрешено перемещение 
                //то есть, пользователь касается объекта на экране
                if (gs.GestureType == GestureType.FreeDrag  gameObjectMove==true)
                {
                    //Начнем перемещать объект со скоростью, заданной параметром Delta
                    //Фактически - чем быстрее был выполнен жест - тем быстрее будет двигаться объект
                    //в направлении жеста
                    gameObject.Move(gs.Delta);
                    
                }
                //Если обнаружен жест двойного касания экрана
                if (gs.GestureType == GestureType.DoubleTap)
                {
                    //Остановим движение объекта
                    gameObject.Move(new Vector2());
                    //Включим вибраацию на 0,1 секунды
                    VibrateController.Default.Start(TimeSpan.FromMilliseconds(100));
                }
         
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// Вызывается, когда игра отрисовывается.
        /// </summary>
        /// <param name="gameTime">Предоставляет моментальный снимок значений времени.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();
            base.Draw(gameTime);
            spriteBatch.End();
        }
       
    }
}

Командой TouchPanel.EnabledGestures = GestureType.FreeDrag | GestureType.DoubleTap; мы включаем обработку жестов и указываем, с какими именно жестами собираемся работать. В нашем случае это FreeDrag и DoubleTap.

Мы хотим реализовать следующий функционал. Когда пользователь касается игровой объект на экране и проводит пальцем по экрану (жест FreeDrag), объект начинает перемещаться в направлении, в котором пользователь провёл пальцем, причем, скорость перемещения объекта задаётся скоростью перемещения пальца. Если пользователь коснётся точки, которая не принадлежит объекту, тот реагировать не будет – за это отвечает код, который, прежде чем разрешить движение объекта, проверяет точку касания.

Для этого используется обычная, уже знакомая вам, обработка касания. Объект в нашем примере напоминает лёгкую пластинку, которая плавает по поверхности прямоугольной ёмкости, наполненной жидкостью. Если пользователь приведет объект в движение – тот будет двигаться с заданными параметрами до тех пор, пока не упрётся в границу экрана. Для того, чтобы остановить движение объекта, мы применяем жест DoubleTap. Этот жест напоминает двойной щелчок мыши. Когда пользователь выполнит этот жест, объект останавливается и на 0,1 секунды включается вибрация телефона.

Мы используем здесь параметр жеста FreeDrag Delta для реализации управления объектом, если, например, использовать координаты касания (они постоянно обновляются при перемещении пальца по экрану), можно организовать управление объектом, подобное перемещению объекта по плоской поверхности, когда его можно "придавить" пальцем и переместить в нужную позицию.

На рис. 13.4 вы можете видеть игровой экран приложения

(рис 13.4) Игровой экран приложения

13.3. Другие возможности

В предыдущих практических работах мы разобрали различные способы управления игровым процессом с использованием сенсорной панели, акселерометра, GPS-приёмника. Этими устройствами не ограничиваются возможности по управлению игрой. Так, весьма полезен может быть гироскоп, который регистрирует углы поворота устройства. Можно пользоваться данными, которые поставляет электронный компас.

Кроме того, различные устройства можно комбинировать – именно так поступают разработчики многих современных игр. Например, если речь идёт о симуляторе автогонок Need For Speed Hot Pursuit, управление автомобилем осуществляется с помощью поворотов экрана, дополнительное ускорение можно включить, выполнив жест, судя по всему, являющийся жестом VerticalDrag (проведя пальцем вдоль правой части экрана в ландшафтной ориентации), а задействовать тормоза – коснувшись левой части экрана.

Обилие способов управления игровым процессом даёт огромный простор для творчества. Разрабатывая систему управления игрой, обратите внимание на Combined Motion API (пространство имён Microsoft.Device.Sensors), класс Motion. Это API позволяет получать данные от гироскопа, акселерометра и электронного компаса и предоставляет полезные инструменты для применения этих данных в управлении игровым процессом.

13.4. Выводы

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

13.5. Задание

Реализуйте распознавание различных жестов в XNA-программе, изучите особенности распознавания жестов, посмотрите, распознавание каких видов жестов может накладываться друг на друга, используя отладочные механизмы.

13.6. Дополнительные материалы

К данной лабораторной работе прилагается видеоурок

и демонстрационный проект.

Вернуться к учебному плану