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

Сохранение и загрузка игр, сериализация, работа с файлами

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

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

19.1. О жизненном цикле приложений

Сохранение состояния игры и загрузка сохранённых состояний – это обычная, необходимая для многих игр процедура. Игры для Windows Phone могут использовать для хранения файлов так называемое изолированное хранилище (Isolated Storage), работа с которым осуществляется с помощью пространства имён System.IO.IsolatedStorage. Каждое приложение имеет собственное изолированное хранилище, таким образом, данные, хранимые приложениями, защищены от случайного или злонамеренного изменения другими программами. В свою очередь, приложение не может получить доступ к данным других приложений.

Можно организовать сохранение либо загрузку каких-либо данных, ориентируясь на произвольные события внутри программы, однако, часто эти операции выполняют при запуске (Launching), закрытии (Closing), активации (Activated), деактивации (Deactivated). Прежде чем рассматривать способы работы с изолированным хранилищем, поговорим о жизненном цикле XNA-приложения.

Жизненный цикл приложения начинается с запуска его пользователем (Launching). Приложение исполняется, после чего, например, по нажатию кнопки перехода на домашний экран, приложение деактивируется (Deactivated). Здесь возможны два варианта развития событий. При первом, когда мы не позаботились о том, чтобы сохранить состояние приложения, оно будет некоторое время находиться в памяти, при попытке возврата к нему (нажатием кнопки Назад), оно будет открыто в том же состоянии, в котором предстаёт перед пользователем сразу после запуска – активировано (Activated). При втором варианте можно выполнить так называемое захоронение (Tombstoning) приложения, сохранив важные данные и позаботившись о том, чтобы эти данные были восстановлены при активации приложения.

Если мы нажмём кнопку Назад во время исполнения приложения, оно будет закрыто (Closing), при этом возврат к приложению невозможен (система не будет сохранять его в памяти, пытаться сохранить промежуточные состояния, как в случае с деактивацией), однако, при возникновении этого события можно сохранить данные, которые будут использованы при следующем запуске того же приложения.

В XNA есть собственные механизмы для обработки состояний приложения. Так, вам уже известен метод Initialize, который выполняется при запуске игры и используется для инициализации игровых параметров. Базовый класс Game предоставляет и другие методы для организации реакции пользователя на изменение состояния игры. В частности, это методы OnActivation (происходит при активации игры), OnDeactivation (при деактивации), Exiting (при выходе из игры).

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

Как будет видно из примера, которым мы займёмся ниже, при запуске XNA-приложения, выполняются методы Initialize и OnActivation. При использовании обработчиков событий из PhoneApplicationService присутствует чёткое разделение при запуске приложения или при его повторной активации. Собственно говоря, это – одна из причин использования PhoneApplicationService в XNA-приложении.

19.2. О сериализации

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

Игра, предусматривающая возможность сохранения игрового состояния, должна предусматривать как минимум два режима работы. Первый – когда игра начинается сначала – в таком случае поля класса, предназначенного для сериализации, заполняются некоторыми заранее заданными значениями. В ходе игры поля класса меняются, отражая изменения, произошедшие в ходе игрового процесса. Далее, после того, как объект сериализован, игра может либо начинаться сначала, либо – восстанавливать свое состояние на момент сериализации. Второй режим – это загрузка ранее сохранённой игры.

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

При этом, есть возможность сохранять в хранилище любые файлы, работать с каталогами, читать файлы, то есть – пользоваться обычными возможностями системы управления файлами. Эта функциональность обеспечивается при помощи объектов System.IO.IsolatedStorage.IsolatedStorageFile (работа с хранилищем) System.IO.IsolatedStorage.IsolatedFileStream (работа с файлами в хранилище). Кроме того, в хранилище можно размещать данные в специальном словаре (содержащем ключи и значения). Эта структура данных называется изолированное хранилище настроек (Isolated Settings Storage), работа с хранилищем настроек осуществляется с помощью объекта System.IO.IsolatedStorage.IsolatedStorageSettings.

19.3. Работа с изолированным хранилищем и механизмами сериализации

Напишем простое приложение, позволяющее отслеживать выполнение этих событий и реализующее механизм сохранения состояния приложения. Создадим новый проект P13_1, подключим библиотеки Microsoft.Phone, System.Windows, System.Xml.Serialization. Добавим в состав контента проекта шрифт для вывода сообщений, новый класс SavedParam.cs. В итоге окно Обозреватель решений для нашего проекта будет выглядеть так, как показано на рис. 19.1.

(рис 19.1) Обозреватель решений для P13_1

В листинге 19.1 приведен код класса SavedParams

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
//Класс для хранения данных игры, сериализуется
namespace P13_1
{
    public class SavedParam
    {
        const string filename = "SavedParam.xml";

        //Игровые данные
        //Количество касаний
        public int touchCount;
        //Позиция касания
        public Vector2 touchPos;
        public SavedParam()
        {
            //Установим в значения по умолчанию
            touchCount = 0;
            touchPos = new Vector2(10, 450);
        }
        //Процедура сохранения состояния объекта
        public void Save()
        {
            //Получим изолированное хранилище для приложения
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
            //Создадим файл для последующей работы
            IsolatedStorageFileStream stream = storage.CreateFile(filename);
            //Подготовим механизм сериализации для данного объекта
            XmlSerializer xml = new XmlSerializer(GetType());
            //Сериализуем текущий объект, направив вывод в ранее созданный файл
            xml.Serialize(stream, this);
            //Закроем файл
            stream.Close();
            //Освободим ресурсы
            stream.Dispose();
        }
        //Статический метод для восстановления состояния объекта (десериализации, загрузки игры)
        public static SavedParam Load()
        {
            //Получим изолированное хранилище
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
            //Для хранения ссылки на объект типа SavedParam
            SavedParam savedParam;
            //Если в хранилище существует файл с нужным именем
            if (storage.FileExists(filename))
            {
                //Откроем файл
                IsolatedStorageFileStream stream = storage.OpenFile("SavedParam.xml", FileMode.Open);
                //Подготовим механизм для сериализации
                XmlSerializer xml = new XmlSerializer(typeof(SavedParam));
                //Извлечем данные из файла
                savedParam = xml.Deserialize(stream) as SavedParam;
                stream.Close();
                stream.Dispose();
            }
            else
            {
                //Если такого файла нет (при первом запуске в нашем случае)
                //Запишем в savedParams ссылку на новый объект типа SavedParams
                savedParam = new SavedParam();
            }
            //Вернём в точку вызова либо результат десериализации, либо - новый объект.
            return savedParam;
        }
    }
}

Класс SavedParam имеет статический метод Load(). Мы вызываем этот метод при запуске или активации игры. В методе делается попытка прочесть файл SavedParam.xml, если файл найден, это означает, что ранее, при выходе из игры или при её деактивации мы сохранили в него результат сериализации объекта SavedParam. В таком случае мы десереализуем этот объект и возвращаем его в точку вызова. Если ранее объект сохранён не был, мы создаём новый объект и так же возвращаем его. При создании нового объекта (это происходит при самом первом запуске игры) переменные touchCount и touchPos устанавливаются в значения по умолчанию.

В методе Save() мы сохраняем объект в файл.

Обратите внимание на то, что класс, подлежащий сериализации, должен иметь атрибут [Serializable], в нашем случае это условие исполняется по умолчанию.

Пользуемся этим объектом мы в основном классе игры, его код приведен в листинге 19.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.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Phone.Shell;
using System.IO.IsolatedStorage;

namespace P13_1
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont myFont;
        String text = "";
        //Объект, хранящий параметры игры
        SavedParam parameters = new SavedParam();
        //Счётчик количества запусков
        int launchCount = 0;
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);

            //Получаем объект PhoneApplicationService, связанный с текущим приложением
            PhoneApplicationService appSrv = PhoneApplicationService.Current;
            //Назначаем обработчики событий
            appSrv.Launching += new EventHandler<LaunchingEventArgs>(appSrv_Launching);
            appSrv.Activated += new EventHandler<ActivatedEventArgs>(appSrv_Activated);
            appSrv.Deactivated += new EventHandler<DeactivatedEventArgs>(appSrv_Deactivated);
            appSrv.Closing += new EventHandler<ClosingEventArgs>(appSrv_Closing);
            //Счётчик количества запусков приложения
            //Проверим, есть ли данные в хранилище настроек
            if (IsolatedStorageSettings.ApplicationSettings.Contains("launchCount") == true)
            {
                //Если есть - прочитаем
                launchCount = (int)IsolatedStorageSettings.ApplicationSettings["launchCount"];
            }
            //Увеличим на 1 количество запусков
            launchCount++;
            //Запишем в хранилище (если ранее данных с этим ключом не было, он будет создан)
            IsolatedStorageSettings.ApplicationSettings["launchCount"] = launchCount;
            //Сохраним данные
            IsolatedStorageSettings.ApplicationSettings.Save();
        }
        //При прекращении работы приложения
        void appSrv_Closing(object sender, ClosingEventArgs e)
        {
            parameters.Save();
        }
        //При деактивации приложения
        void appSrv_Deactivated(object sender, DeactivatedEventArgs e)
        {
            parameters.Save();
        }
        //При активации приложения
        void appSrv_Activated(object sender, ActivatedEventArgs e)
        {
            text = text + " srv_Activation";
            parameters = SavedParam.Load();
        }
        //При запуске приложения
        void appSrv_Launching(object sender, LaunchingEventArgs e)
        {
            text = text + " srv_Launching";
            parameters = SavedParam.Load();
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            text = text + " XNA_Initialize";
            base.Initialize();
        }
        //Метод базового класса Game, выполняется при деактивации приложения
        protected override void OnDeactivated(object sender, EventArgs args)
        {
            text = "";
            base.OnDeactivated(sender, args);

        }
        //Метод базового класса Game, выполняется при активации приложения
        protected override void OnActivated(object sender, EventArgs args)
        {
            text = text + " XNA_OnActivated";
            base.OnActivated(sender, args);
        }


        /// <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();
            //Обработакм касание
            TouchCollection touchLocations = TouchPanel.GetState();
            foreach (TouchLocation touchLocation in touchLocations)
            {
                if (touchLocation.State == TouchLocationState.Pressed)
                {
                    //Сохраним позицию касания
                    parameters.touchPos = touchLocation.Position;
                    //Увеличим количество касаний
                    parameters.touchCount++;
                }
            }

            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, new Vector2(0, 0), Color.Red);
            //Выведем сообщение о количестве касаний в позиции последнего касания
            spriteBatch.DrawString(myFont, parameters.touchCount.ToString(), parameters.touchPos, Color.White);
            //Выведем данные о количестве запусков программы
            spriteBatch.DrawString(myFont, "Launch count = " + launchCount.ToString(), new Vector2(0, 240), Color.Black);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Логика работы данного проекта разъясняется в комментариях, рассмотрим его основные моменты.

В конструкторе класса мы подсчитываем количество запусков игры. Здесь мы работаем с изолированным хранилищем настроек. Сначала проверяем, есть ли в хранилище данные по ключу launchCount, если данные есть, приводим их к типу int и записываем в переменную launchCount, после чего увеличиваем переменную на 1 (по умолчанию она равна 0) и записываем значение переменной в хранилище настроек с ключом launchCount. При этом, если такого ключа в хранилище пока нет (при первом запуске) он будет создан. Записав данные, сохраняем состояние хранилища настроек.

В конструкторе мы подключаем обработчики событий Launching, Activated, Deactivated, Closing класса PhoneApplicationService. Эти обработчики мы будем использовать для сохранения состояния игры (Deactivated и Closing) и загрузки (Activated, Launching). Кроме того, в классе мы переопределили методы базового класса OnActivated и OnDeactivated.

В данном случае это сделано для того, чтобы показать, что при активации приложения после деактивации (то есть, например, в ситуации, когда, работая в приложении мы нажимаем на кнопку вызова домашнего экрана, после чего, воспользовавшись кнопкой Назад возвращаемся в приложение) происходит вызов методов Initialize и OnActivated.

В то же время, при активации после деактивации вызывается лишь обработчик события Activated. При первом запуске приложения происходит вызов Initialize и OnActivated, относящихся к классу Game и Launching из PhoneApplicationService. Мы формируем строку text в этих событиях, она выводится на экран. Для самостоятельного исследования данных методов и особенностей жизненного цикла приложения полезно будет воспользоваться инструментами отладки.

В методе Update мы наблюдаем за касаниями экрана, если касание зафиксировано, записываем в открытую переменную touchPos объекта типа SavedParam координату касания и увеличиваем на 1 значение переменной touchCount из этого же объекта для подсчёта числа касаний.

В методе Draw мы выводим строку, сформированную при исполнении методов, отражающих изменение жизненного цикла приложения, выводим сведения о количестве касаний в позицию последнего зафиксированного касания, пользуясь данными объекта parameters типа SavedParam, и выводим сведения о количестве запусков программы из переменной launchCount, которая ранее (начиная со второго запуска программы) была установлена с использованием параметра, сохранённого в изолированном хранилище параметров.

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

(рис 19.2) Программа в эмуляторе

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

19.4. Работа с изолированным хранилищем с ПК разработчика

Вместе с инструментами разработки для Windows Phone поставляется утилита командной строки ISETool.exe (Isolated Storage Explorer Tool). В нашем случае она располагается по следующему адресу: C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Tools\IsolatedStorageExplorerTool. Утилита позволяет выполнять множество действий с изолированным хранилищем конкретной программы как на эмуляторе, так и на устройстве.

Для использования инструмента программа должна быть установлена, но не запущена. Прежде чем им пользоваться, нужно выяснить идентификатор продукта (ProductID). Для этого нужно обратиться к файлу WPAppManifest.xml (он расположен в папке Properties в Обозревателе решений, и найти атрибут ProductID элемента App, на рис. 19.3 приведен фрагмент данного файла для нашего приложения.

(рис 19.3) Фрагмент файла WMAppManifest.xml

Идентификатор, в нашем случае, выглядит как 69ebc0c5-68eb-49a7-b2ca-20beeb988232 Запустим интерпретатор командной строки командой cmd в поле поиска меню Пуск, перейдем в папку, где хранится ISETool.exe (в нашем случае это команда CD C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Tools\IsolatedStorageExplorerTool и выполним следующую команду, предварительно установив программу на устройство, выйдя из программы, но оставив устройство включённым:

isetool.exe dir de 69ebc0c5-68eb-49a7-b2ca-20beeb988232

В ответ на эту команду мы получим содержимое изолированного хранилища для нашей программы:

<DIR>  Shared
255  SavedParam.xml
 338  __ApplicationSettings

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

(рис 19.4) Результат выполнения команды просмотра хранилища

В данном случае команда dir задаёт действие, которое мы хотим выполнить (просмотреть хранилище), команда de указывает на то, что просматривать мы хотим хранилище программы, которая установлена на смартфоне, подключённом к компьютеру, далее следует идентификатор программы.

Для просмотра хранилища программы, установленной на эмуляторе, можно воспользоваться командой xd вместо de. Файлы можно копировать в хранилище с ПК, в обратном направлении, заменять файлы в хранилище. Например, для того, чтобы скопировать файлы из хранилища программы, установленной в эмуляторе, в папку C:\WP, нужно воспользоваться такой командой:

isetool ts xd 69ebc0c5-68eb-49a7-b2ca-20beeb988232 "C:\WP"

При успешном выполнении операции можно увидеть следующие сообщения:

Download Started ... Into Folder: C:\WP

Download Successful Into Folder: C:\WP

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

(рис 19.5) Выполнение команды копирования файлов на ПК

На рис. 19.6 представлено содержимое подпапки IsolatedStorage, которая была создана в папке WP после выполнения команды.

(рис 19.6) Локальная копия IsolatedStorage

Получить подсказку по использованию утилиты можно, запустив её без параметров, дополнительные материалы по использованию ISETool можно на http://msdn.microsoft.com/en-us/library/hh286408%28v=vs.92%29.aspx

19.4. Выводы

В данной лабораторной работе мы поговорили о жизненном цикле приложений, о сериализации как способе сохранения состояния объектов, о работе с изолированным хранилищем для хранения файлов.

19.5. Задание

Организуйте систему сохранения и восстановления состояния игры, разработкой которой вы занимаетесь. Освойте работу с ISETool для контроля за правильностью работы механизмов хранения игровых файлов.

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

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

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

Страницы:

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

19.1. О жизненном цикле приложений

Сохранение состояния игры и загрузка сохранённых состояний – это обычная, необходимая для многих игр процедура. Игры для Windows Phone могут использовать для хранения файлов так называемое изолированное хранилище (Isolated Storage), работа с которым осуществляется с помощью пространства имён System.IO.IsolatedStorage. Каждое приложение имеет собственное изолированное хранилище, таким образом, данные, хранимые приложениями, защищены от случайного или злонамеренного изменения другими программами. В свою очередь, приложение не может получить доступ к данным других приложений.

Можно организовать сохранение либо загрузку каких-либо данных, ориентируясь на произвольные события внутри программы, однако, часто эти операции выполняют при запуске (Launching), закрытии (Closing), активации (Activated), деактивации (Deactivated). Прежде чем рассматривать способы работы с изолированным хранилищем, поговорим о жизненном цикле XNA-приложения.

Жизненный цикл приложения начинается с запуска его пользователем (Launching). Приложение исполняется, после чего, например, по нажатию кнопки перехода на домашний экран, приложение деактивируется (Deactivated). Здесь возможны два варианта развития событий. При первом, когда мы не позаботились о том, чтобы сохранить состояние приложения, оно будет некоторое время находиться в памяти, при попытке возврата к нему (нажатием кнопки Назад), оно будет открыто в том же состоянии, в котором предстаёт перед пользователем сразу после запуска – активировано (Activated). При втором варианте можно выполнить так называемое захоронение (Tombstoning) приложения, сохранив важные данные и позаботившись о том, чтобы эти данные были восстановлены при активации приложения.

Если мы нажмём кнопку Назад во время исполнения приложения, оно будет закрыто (Closing), при этом возврат к приложению невозможен (система не будет сохранять его в памяти, пытаться сохранить промежуточные состояния, как в случае с деактивацией), однако, при возникновении этого события можно сохранить данные, которые будут использованы при следующем запуске того же приложения.

В XNA есть собственные механизмы для обработки состояний приложения. Так, вам уже известен метод Initialize, который выполняется при запуске игры и используется для инициализации игровых параметров. Базовый класс Game предоставляет и другие методы для организации реакции пользователя на изменение состояния игры. В частности, это методы OnActivation (происходит при активации игры), OnDeactivation (при деактивации), Exiting (при выходе из игры).

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

Как будет видно из примера, которым мы займёмся ниже, при запуске XNA-приложения, выполняются методы Initialize и OnActivation. При использовании обработчиков событий из PhoneApplicationService присутствует чёткое разделение при запуске приложения или при его повторной активации. Собственно говоря, это – одна из причин использования PhoneApplicationService в XNA-приложении.

19.2. О сериализации

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

Игра, предусматривающая возможность сохранения игрового состояния, должна предусматривать как минимум два режима работы. Первый – когда игра начинается сначала – в таком случае поля класса, предназначенного для сериализации, заполняются некоторыми заранее заданными значениями. В ходе игры поля класса меняются, отражая изменения, произошедшие в ходе игрового процесса. Далее, после того, как объект сериализован, игра может либо начинаться сначала, либо – восстанавливать свое состояние на момент сериализации. Второй режим – это загрузка ранее сохранённой игры.

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

При этом, есть возможность сохранять в хранилище любые файлы, работать с каталогами, читать файлы, то есть – пользоваться обычными возможностями системы управления файлами. Эта функциональность обеспечивается при помощи объектов System.IO.IsolatedStorage.IsolatedStorageFile (работа с хранилищем) System.IO.IsolatedStorage.IsolatedFileStream (работа с файлами в хранилище). Кроме того, в хранилище можно размещать данные в специальном словаре (содержащем ключи и значения). Эта структура данных называется изолированное хранилище настроек (Isolated Settings Storage), работа с хранилищем настроек осуществляется с помощью объекта System.IO.IsolatedStorage.IsolatedStorageSettings.

19.3. Работа с изолированным хранилищем и механизмами сериализации

Напишем простое приложение, позволяющее отслеживать выполнение этих событий и реализующее механизм сохранения состояния приложения. Создадим новый проект P13_1, подключим библиотеки Microsoft.Phone, System.Windows, System.Xml.Serialization. Добавим в состав контента проекта шрифт для вывода сообщений, новый класс SavedParam.cs. В итоге окно Обозреватель решений для нашего проекта будет выглядеть так, как показано на рис. 19.1.

(рис 19.1) Обозреватель решений для P13_1

В листинге 19.1 приведен код класса SavedParams

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
//Класс для хранения данных игры, сериализуется
namespace P13_1
{
    public class SavedParam
    {
        const string filename = "SavedParam.xml";

        //Игровые данные
        //Количество касаний
        public int touchCount;
        //Позиция касания
        public Vector2 touchPos;
        public SavedParam()
        {
            //Установим в значения по умолчанию
            touchCount = 0;
            touchPos = new Vector2(10, 450);
        }
        //Процедура сохранения состояния объекта
        public void Save()
        {
            //Получим изолированное хранилище для приложения
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
            //Создадим файл для последующей работы
            IsolatedStorageFileStream stream = storage.CreateFile(filename);
            //Подготовим механизм сериализации для данного объекта
            XmlSerializer xml = new XmlSerializer(GetType());
            //Сериализуем текущий объект, направив вывод в ранее созданный файл
            xml.Serialize(stream, this);
            //Закроем файл
            stream.Close();
            //Освободим ресурсы
            stream.Dispose();
        }
        //Статический метод для восстановления состояния объекта (десериализации, загрузки игры)
        public static SavedParam Load()
        {
            //Получим изолированное хранилище
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
            //Для хранения ссылки на объект типа SavedParam
            SavedParam savedParam;
            //Если в хранилище существует файл с нужным именем
            if (storage.FileExists(filename))
            {
                //Откроем файл
                IsolatedStorageFileStream stream = storage.OpenFile("SavedParam.xml", FileMode.Open);
                //Подготовим механизм для сериализации
                XmlSerializer xml = new XmlSerializer(typeof(SavedParam));
                //Извлечем данные из файла
                savedParam = xml.Deserialize(stream) as SavedParam;
                stream.Close();
                stream.Dispose();
            }
            else
            {
                //Если такого файла нет (при первом запуске в нашем случае)
                //Запишем в savedParams ссылку на новый объект типа SavedParams
                savedParam = new SavedParam();
            }
            //Вернём в точку вызова либо результат десериализации, либо - новый объект.
            return savedParam;
        }
    }
}

Класс SavedParam имеет статический метод Load(). Мы вызываем этот метод при запуске или активации игры. В методе делается попытка прочесть файл SavedParam.xml, если файл найден, это означает, что ранее, при выходе из игры или при её деактивации мы сохранили в него результат сериализации объекта SavedParam. В таком случае мы десереализуем этот объект и возвращаем его в точку вызова. Если ранее объект сохранён не был, мы создаём новый объект и так же возвращаем его. При создании нового объекта (это происходит при самом первом запуске игры) переменные touchCount и touchPos устанавливаются в значения по умолчанию.

В методе Save() мы сохраняем объект в файл.

Обратите внимание на то, что класс, подлежащий сериализации, должен иметь атрибут [Serializable], в нашем случае это условие исполняется по умолчанию.

Пользуемся этим объектом мы в основном классе игры, его код приведен в листинге 19.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.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Phone.Shell;
using System.IO.IsolatedStorage;

namespace P13_1
{
    /// <summary>
    /// Это главный тип игры
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont myFont;
        String text = "";
        //Объект, хранящий параметры игры
        SavedParam parameters = new SavedParam();
        //Счётчик количества запусков
        int launchCount = 0;
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Частота кадра на Windows Phone по умолчанию — 30 кадров в секунду.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Дополнительный заряд аккумулятора заблокирован.
            InactiveSleepTime = TimeSpan.FromSeconds(1);

            //Получаем объект PhoneApplicationService, связанный с текущим приложением
            PhoneApplicationService appSrv = PhoneApplicationService.Current;
            //Назначаем обработчики событий
            appSrv.Launching += new EventHandler<LaunchingEventArgs>(appSrv_Launching);
            appSrv.Activated += new EventHandler<ActivatedEventArgs>(appSrv_Activated);
            appSrv.Deactivated += new EventHandler<DeactivatedEventArgs>(appSrv_Deactivated);
            appSrv.Closing += new EventHandler<ClosingEventArgs>(appSrv_Closing);
            //Счётчик количества запусков приложения
            //Проверим, есть ли данные в хранилище настроек
            if (IsolatedStorageSettings.ApplicationSettings.Contains("launchCount") == true)
            {
                //Если есть - прочитаем
                launchCount = (int)IsolatedStorageSettings.ApplicationSettings["launchCount"];
            }
            //Увеличим на 1 количество запусков
            launchCount++;
            //Запишем в хранилище (если ранее данных с этим ключом не было, он будет создан)
            IsolatedStorageSettings.ApplicationSettings["launchCount"] = launchCount;
            //Сохраним данные
            IsolatedStorageSettings.ApplicationSettings.Save();
        }
        //При прекращении работы приложения
        void appSrv_Closing(object sender, ClosingEventArgs e)
        {
            parameters.Save();
        }
        //При деактивации приложения
        void appSrv_Deactivated(object sender, DeactivatedEventArgs e)
        {
            parameters.Save();
        }
        //При активации приложения
        void appSrv_Activated(object sender, ActivatedEventArgs e)
        {
            text = text + " srv_Activation";
            parameters = SavedParam.Load();
        }
        //При запуске приложения
        void appSrv_Launching(object sender, LaunchingEventArgs e)
        {
            text = text + " srv_Launching";
            parameters = SavedParam.Load();
        }

        /// <summary>
        /// Позволяет игре выполнить инициализацию, необходимую перед запуском.
        /// Здесь можно запросить нужные службы и загрузить неграфический
        /// контент.  Вызов base.Initialize приведет к перебору всех компонентов и
        /// их инициализации.
        /// </summary>
        protected override void Initialize()
        {
            text = text + " XNA_Initialize";
            base.Initialize();
        }
        //Метод базового класса Game, выполняется при деактивации приложения
        protected override void OnDeactivated(object sender, EventArgs args)
        {
            text = "";
            base.OnDeactivated(sender, args);

        }
        //Метод базового класса Game, выполняется при активации приложения
        protected override void OnActivated(object sender, EventArgs args)
        {
            text = text + " XNA_OnActivated";
            base.OnActivated(sender, args);
        }


        /// <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();
            //Обработакм касание
            TouchCollection touchLocations = TouchPanel.GetState();
            foreach (TouchLocation touchLocation in touchLocations)
            {
                if (touchLocation.State == TouchLocationState.Pressed)
                {
                    //Сохраним позицию касания
                    parameters.touchPos = touchLocation.Position;
                    //Увеличим количество касаний
                    parameters.touchCount++;
                }
            }

            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, new Vector2(0, 0), Color.Red);
            //Выведем сообщение о количестве касаний в позиции последнего касания
            spriteBatch.DrawString(myFont, parameters.touchCount.ToString(), parameters.touchPos, Color.White);
            //Выведем данные о количестве запусков программы
            spriteBatch.DrawString(myFont, "Launch count = " + launchCount.ToString(), new Vector2(0, 240), Color.Black);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Логика работы данного проекта разъясняется в комментариях, рассмотрим его основные моменты.

В конструкторе класса мы подсчитываем количество запусков игры. Здесь мы работаем с изолированным хранилищем настроек. Сначала проверяем, есть ли в хранилище данные по ключу launchCount, если данные есть, приводим их к типу int и записываем в переменную launchCount, после чего увеличиваем переменную на 1 (по умолчанию она равна 0) и записываем значение переменной в хранилище настроек с ключом launchCount. При этом, если такого ключа в хранилище пока нет (при первом запуске) он будет создан. Записав данные, сохраняем состояние хранилища настроек.

В конструкторе мы подключаем обработчики событий Launching, Activated, Deactivated, Closing класса PhoneApplicationService. Эти обработчики мы будем использовать для сохранения состояния игры (Deactivated и Closing) и загрузки (Activated, Launching). Кроме того, в классе мы переопределили методы базового класса OnActivated и OnDeactivated.

В данном случае это сделано для того, чтобы показать, что при активации приложения после деактивации (то есть, например, в ситуации, когда, работая в приложении мы нажимаем на кнопку вызова домашнего экрана, после чего, воспользовавшись кнопкой Назад возвращаемся в приложение) происходит вызов методов Initialize и OnActivated.

В то же время, при активации после деактивации вызывается лишь обработчик события Activated. При первом запуске приложения происходит вызов Initialize и OnActivated, относящихся к классу Game и Launching из PhoneApplicationService. Мы формируем строку text в этих событиях, она выводится на экран. Для самостоятельного исследования данных методов и особенностей жизненного цикла приложения полезно будет воспользоваться инструментами отладки.

В методе Update мы наблюдаем за касаниями экрана, если касание зафиксировано, записываем в открытую переменную touchPos объекта типа SavedParam координату касания и увеличиваем на 1 значение переменной touchCount из этого же объекта для подсчёта числа касаний.

В методе Draw мы выводим строку, сформированную при исполнении методов, отражающих изменение жизненного цикла приложения, выводим сведения о количестве касаний в позицию последнего зафиксированного касания, пользуясь данными объекта parameters типа SavedParam, и выводим сведения о количестве запусков программы из переменной launchCount, которая ранее (начиная со второго запуска программы) была установлена с использованием параметра, сохранённого в изолированном хранилище параметров.

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

(рис 19.2) Программа в эмуляторе

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

19.4. Работа с изолированным хранилищем с ПК разработчика

Вместе с инструментами разработки для Windows Phone поставляется утилита командной строки ISETool.exe (Isolated Storage Explorer Tool). В нашем случае она располагается по следующему адресу: C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Tools\IsolatedStorageExplorerTool. Утилита позволяет выполнять множество действий с изолированным хранилищем конкретной программы как на эмуляторе, так и на устройстве.

Для использования инструмента программа должна быть установлена, но не запущена. Прежде чем им пользоваться, нужно выяснить идентификатор продукта (ProductID). Для этого нужно обратиться к файлу WPAppManifest.xml (он расположен в папке Properties в Обозревателе решений, и найти атрибут ProductID элемента App, на рис. 19.3 приведен фрагмент данного файла для нашего приложения.

(рис 19.3) Фрагмент файла WMAppManifest.xml

Идентификатор, в нашем случае, выглядит как 69ebc0c5-68eb-49a7-b2ca-20beeb988232 Запустим интерпретатор командной строки командой cmd в поле поиска меню Пуск, перейдем в папку, где хранится ISETool.exe (в нашем случае это команда CD C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Tools\IsolatedStorageExplorerTool и выполним следующую команду, предварительно установив программу на устройство, выйдя из программы, но оставив устройство включённым:

isetool.exe dir de 69ebc0c5-68eb-49a7-b2ca-20beeb988232

В ответ на эту команду мы получим содержимое изолированного хранилища для нашей программы:

<DIR>  Shared
255  SavedParam.xml
 338  __ApplicationSettings

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

(рис 19.4) Результат выполнения команды просмотра хранилища

В данном случае команда dir задаёт действие, которое мы хотим выполнить (просмотреть хранилище), команда de указывает на то, что просматривать мы хотим хранилище программы, которая установлена на смартфоне, подключённом к компьютеру, далее следует идентификатор программы.

Для просмотра хранилища программы, установленной на эмуляторе, можно воспользоваться командой xd вместо de. Файлы можно копировать в хранилище с ПК, в обратном направлении, заменять файлы в хранилище. Например, для того, чтобы скопировать файлы из хранилища программы, установленной в эмуляторе, в папку C:\WP, нужно воспользоваться такой командой:

isetool ts xd 69ebc0c5-68eb-49a7-b2ca-20beeb988232 "C:\WP"

При успешном выполнении операции можно увидеть следующие сообщения:

Download Started ... Into Folder: C:\WP

Download Successful Into Folder: C:\WP

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

(рис 19.5) Выполнение команды копирования файлов на ПК

На рис. 19.6 представлено содержимое подпапки IsolatedStorage, которая была создана в папке WP после выполнения команды.

(рис 19.6) Локальная копия IsolatedStorage

Получить подсказку по использованию утилиты можно, запустив её без параметров, дополнительные материалы по использованию ISETool можно на http://msdn.microsoft.com/en-us/library/hh286408%28v=vs.92%29.aspx

19.4. Выводы

В данной лабораторной работе мы поговорили о жизненном цикле приложений, о сериализации как способе сохранения состояния объектов, о работе с изолированным хранилищем для хранения файлов.

19.5. Задание

Организуйте систему сохранения и восстановления состояния игры, разработкой которой вы занимаетесь. Освойте работу с ISETool для контроля за правильностью работы механизмов хранения игровых файлов.

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

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

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

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