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

Совместное использование Silverlight и XNA

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

Цель работы: Научиться разрабатывать приложения с использованием шаблона Приложение Silverlight и XNA для Windows Phone

35.1. Особенности проекта Приложение Silverlight и XNA для Windows Phone

В некоторых из предыдущих проектов мы использовали отдельные библиотеки и объекты XNA в Silverlight-приложениях. Начиная с Windows Phone OS 7.1. при разработке приложений можно в полной мере пользоваться возможностями Silverlight и XNA в одном приложении. Например, можно создать сложный пользовательский интерфейс на Silverlight, и, в то же время, организовать вывод графики с помощью средств XNA. Фактически, такое приложение позволяет взять всё лучшее от обеих технологий.

Рассмотрим особенности таких приложений. Создадим новый проект, в окне создания проекта выберем шаблон Приложение Silverlight и XNA для Windows Phone, рис. 35.1.

(рис 35.1) Создание проекта

Посмотрим на Обозреватель решений этого проекта, рис. 35.2

(рис 35.2) Обозреватель решений для проекта P28_1

Если рассмотреть это решение в первом приближении, то окажется, что мы имеем Silverlight-проект, в котором есть механизмы для визуализации средствами XNA и страница GamePage.xaml, код которой (GamePage.xaml.cs) представляет собой реализацию игрового цикла XNA. Весь вывод данных на этой странице формируется средствами XNA.

Рассмотрим решение подробнее. Оно состоит из трёх проектов:

  • Основной проект приложения (P28_1). Это проект приложения, здесь содержатся страницы приложения. Причём, страница MainPage – это обычная Silverlight-страница, которая отображается после запуска приложения, на ней, по умолчанию, расположена кнопка для вызова страницы GamePage.
  • Проект библиотеки XNA (P28_1Lib). Этот проект напоминает проект XNA, разработкой которых мы занимались ранее в этом курсе. Он имеет ссылку на проект контента. Этот проект представляет собой нечто вроде моста между Silverlight-приложением и хранилищем контента.
  • Проект контента XNA (P281Lib_Content). Традиционный для XNA-приложений проект, содержащий контент – изображения, звуки, трехмерные модели.
  • Рассмотрим файлы, которые входят в решение. Начнём с файла App.xaml, листинге 35.1.

    <Application 
        x:Class="P28_1.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"       
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
        xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
         xmlns:xna="clr-namespace:Microsoft.Xna.Framework;assembly=Microsoft.Xna.Framework.Interop">
    
        <!--Ресурсы приложения-->
        <Application.Resources>
        </Application.Resources>
    
        <Application.ApplicationLifetimeObjects>
            <!--Обязательный объект, обрабатывающий события времени жизни приложения-->
            <shell:PhoneApplicationService 
                Launching="Application_Launching" Closing="Application_Closing" 
                Activated="Application_Activated" Deactivated="Application_Deactivated"/>
    
    
        <!--SharedGraphicsDeviceManager используется для отображения с помощью графических API XNA-->
        <xna:SharedGraphicsDeviceManager />
        </Application.ApplicationLifetimeObjects>
    
    </Application>

    Этот файл, как и в случае с обычным Silverlight-приложением, определяет обработчики события, касающиеся жизненного цикла приложения, и, кроме того, определяет xna:SharedGraphicsDeviceManager – объект, который используется для вывода XNA-графики.

    В листинге 35.2 приведен файл App.xaml.cs,

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using Microsoft.Phone.Controls;
    using Microsoft.Phone.Shell;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    
    namespace P28_1
    {
        public partial class App : Application
        {
            /// <summary>
            /// Обеспечивает быстрый доступ к корневому кадру приложения телефона.
            /// </summary>
            /// <returns>Корневой кадр приложения телефона.</returns>
            public PhoneApplicationFrame RootFrame { get; private set; }
    
            /// <summary>
            /// Предоставляет приложению доступ к ContentManager.
            /// </summary>
            public ContentManager Content { get; private set; }
    
            /// <summary>
            /// Предоставляет доступ к GameTimer, настроенному для передачи данных в FrameworkDispatcher.
            /// </summary>
            public GameTimer FrameworkDispatcherTimer { get; private set; }
    
            /// <summary>
            /// Предоставляет доступ к AppServiceProvider для приложения.
            /// </summary>
            public AppServiceProvider Services { get; private set; }
    
            /// <summary>
            /// Конструктор объекта приложения.
            /// </summary>
            public App()
            {
                // Глобальный обработчик неперехваченных исключений. 
                UnhandledException += Application_UnhandledException;
    
                // Стандартная инициализация Silverlight
                InitializeComponent();
    
                // Инициализация телефона
                InitializePhoneApplication();
    
                // Инициализация XNA
                InitializeXnaApplication();
    
                // Отображение сведений о профиле графики во время отладки.
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    // Отображение текущих счетчиков частоты смены кадров.
                    Application.Current.Host.Settings.EnableFrameRateCounter = true;
    
                    // Отображение областей приложения, перерисовываемых в каждом кадре.
                    //Application.Current.Host.Settings.EnableRedrawRegions = true;
    
                    // Включение режима визуализации анализа нерабочего кода 
                    // для отображения областей страницы, переданных в GPU, с цветным наложением.
                    //Application.Current.Host.Settings.EnableCacheVisualization = true;
    
                    // Отключите обнаружение простоя приложения, установив для свойства UserIdleDetectionMode
                    // объекта PhoneApplicationService приложения значение Disabled.
                    // Внимание! Используйте только в режиме отладки. Приложение, в котором отключено обнаружение 
                     бездействия пользователя, будет продолжать работать
                    // и потреблять энергию батареи, когда телефон не будет использоваться.
                    PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
                }
            }
    
            // Код для выполнения при запуске приложения (например, из меню "Пуск")
            // Этот код не будет выполняться при повторной активации приложения
            private void Application_Launching(object sender, LaunchingEventArgs e)
            {
            }
    
            // Код для выполнения при активации приложения (переводится в основной режим)
            // Этот код не будет выполняться при первом запуске приложения
            private void Application_Activated(object sender, ActivatedEventArgs e)
            {
            }
    
            // Код для выполнения при деактивации приложения (отправляется в фоновый режим)
            // Этот код не будет выполняться при закрытии приложения
            private void Application_Deactivated(object sender, DeactivatedEventArgs e)
            {
            }
    
            // Код для выполнения при закрытии приложения (например, при нажатии пользователем кнопки "Назад")
            // Этот код не будет выполняться при деактивации приложения
            private void Application_Closing(object sender, ClosingEventArgs e)
            {
            }
    
            // Код для выполнения в случае ошибки навигации
            private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
            {
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    // Ошибка навигации; перейти в отладчик
                    System.Diagnostics.Debugger.Break();
                }
            }
    
            // Код для выполнения на необработанных исключениях
            private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
            {
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    // Произошло необработанное исключение; перейти в отладчик
                    System.Diagnostics.Debugger.Break();
                }
            }
    
            #region Инициализация приложения телефона
    
            // Избегайте двойной инициализации
            private bool phoneApplicationInitialized = false;
    
            // Не добавляйте в этот метод дополнительный код
            private void InitializePhoneApplication()
            {
                if (phoneApplicationInitialized)
                    return;
    
                // Создайте кадр, но не задавайте для него значение RootVisual; это позволит
                // экрану-заставке оставаться активным, пока приложение не будет готово для визуализации.
                RootFrame = new PhoneApplicationFrame();
                RootFrame.Navigated += CompleteInitializePhoneApplication;
    
                // Обработка сбоев навигации
                RootFrame.NavigationFailed += RootFrame_NavigationFailed;
    
                // Убедитесь, что инициализация не выполняется повторно
                phoneApplicationInitialized = true;
            }
    
            // Не добавляйте в этот метод дополнительный код
            private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
            {
                // Задайте корневой визуальный элемент для визуализации приложения
                if (RootVisual != RootFrame)
                    RootVisual = RootFrame;
    
                // Удалите этот обработчик, т.к. он больше не нужен
                RootFrame.Navigated -= CompleteInitializePhoneApplication;
            }
    
            #endregion
    
            #region Инициализация приложения XNA
    
            // Выполняет инициализацию типов XNA, необходимых для приложения.
            private void InitializeXnaApplication()
            {
                // Создание поставщика услуг
                Services = new AppServiceProvider();
    
                // Добавить SharedGraphicsDeviceManager к службам в качестве IGraphicsDeviceService для приложения
                foreach (object obj in ApplicationLifetimeObjects)
                {
                    if (obj is IGraphicsDeviceService)
                        Services.AddService(typeof(IGraphicsDeviceService), obj);
                }
    
                // Создайте ContentManager для загрузки в приложение предварительно скомпилированных ресурсов
                Content = new ContentManager(Services, "Content");
    
                // Создайте GameTimer для получения XNA FrameworkDispatcher
                FrameworkDispatcherTimer = new GameTimer();
                FrameworkDispatcherTimer.FrameAction += FrameworkDispatcherFrameAction;
                FrameworkDispatcherTimer.Start();
            }
    
            // Обработчик событий передает FrameworkDispatcher во все кадры.
            // FrameworkDispatcher требуется для большинства событий XNA и
            // некоторых функций, таких как воспроизведение SoundEffect.
            private void FrameworkDispatcherFrameAction(object sender, EventArgs e)
            {
                FrameworkDispatcher.Update();
            }
    
            #endregion
        }
    }

    Это всё тот же App.xaml.cs, которым мы пользовались ранее, однако, здесь производится инициализация механизмов XNA, создается ConetntManager для работы с XNA-контентом, создаётся таймер уровня приложения для FrameworkDispatcher.Update().

    Страница MainPage.xaml – это обычная стартовая страница Silverlight-приложения, рис. 35.3.

    (рис 35.3) Страница MainPage.xaml

    Здесь располагается кнопка "Изменить на страницу игры". Правильнее будет подписать её как "Перейти на страницу игры", так как именно эту функцию она и выполняет. В листинге 35.3 приведен её код.

    <phone:PhoneApplicationPage 
        x:Class="P28_1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
        xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="Portrait" Orientation="Portrait"
        shell:SystemTray.IsVisible="False">
    
        <!--LayoutRoot представляет корневую сетку, где размещается все содержимое страницы-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!--TitlePanel содержит имя приложения и заголовок страницы-->
            <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
                <TextBlock x:Name="ApplicationTitle" Text="МОЕ ПРИЛОЖЕНИЕ" 
                Style="{StaticResource PhoneTextNormalStyle}"/>
                <TextBlock x:Name="PageTitle" Text="имя страницы" Margin="9,-7,0,0" 
                Style="{StaticResource PhoneTextTitle1Style}"/>
            </StackPanel>
    
            <!--ContentPanel — поместите здесь дополнительное содержимое-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                
                <!--Создание одной кнопки для перехода ко второй странице, визуализация которой выполнена XNA Framework-->
                <Button Height="100" Content="Изменить на страницу игры" Click="Button_Click" />
                
            </Grid>
        </Grid>
     
        <!--Пример кода, иллюстрирующий использование ApplicationBar-->
        <!--<phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Кнопка 1"/>
                <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Кнопка 2"/>
                <shell:ApplicationBar.MenuItems>
                    <shell:ApplicationBarMenuItem Text="Элемент меню 1"/>
                    <shell:ApplicationBarMenuItem Text="Элемент меню 2"/>
                </shell:ApplicationBar.MenuItems>
            </shell:ApplicationBar>
        </phone:PhoneApplicationPage.ApplicationBar>-->
    
    </phone:PhoneApplicationPage>

    Страница стандартна, она по умолчанию содержит настройку, скрывающую системные значки, что приводит к отображению страницы на полный экран. В её CS-файле, листинг 35.4., содержится обработчик нажатия на кнопку, открывающий страницу GamePage.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using Microsoft.Phone.Controls;
    
    namespace P28_1
    {
        public partial class MainPage : PhoneApplicationPage
        {
            // Конструктор
            public MainPage()
            {
                InitializeComponent();
            }
    
            // Простая кнопка Щелкните обработчик событий, чтобы перейти на вторую страницу
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                NavigationService.Navigate(new Uri("/GamePage.xaml", UriKind.Relative));
            }
        }
    }

    Собственно говоря, мы можем пользоваться всем арсеналом инструментов и приёмов Silverlight, работая со страницей MainPage и с другими Silverlight—страницами проекта.

    XAML-код страницы GamePage, листинг 35.5., задаёт лишь свойства PhoneApplicationPage. В частности, по умолчанию игровой экран будет выводиться в портретной ориентации.

    <phone:PhoneApplicationPage 
        x:Class="P28_1.GamePage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
        xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="Portrait" Orientation="Portrait"
        mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="480"
        shell:SystemTray.IsVisible="False">
        
        <!--Содержимое XAML не требуется, поскольку полная визуализация страницы выполнена XNA Framework-->
    
    </phone:PhoneApplicationPage>

    Другой нагрузки он, в шаблоне проекта, не несет, так как вся работа по созданию игры ведется в файле GamePage.xaml.cs. Если мы соберемся использовать Silverlight-элементы на XNA-странице, нам придется описывать их визуализацию в GamePage.xaml.cs. В этом файле нас ждёт самое интересное – реализация механизмов XNA.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using Microsoft.Phone.Controls;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    
    namespace P28_1
    {
        public partial class GamePage : PhoneApplicationPage
        {
            ContentManager contentManager;
            GameTimer timer;
            SpriteBatch spriteBatch;
    
            public GamePage()
            {
                InitializeComponent();
    
                // Получить диспетчер содержимого из приложения
                contentManager = (Application.Current as App).Content;
    
                // Создайте таймер для этой страницы
                timer = new GameTimer();
                timer.UpdateInterval = TimeSpan.FromTicks(333333);
                timer.Update += OnUpdate;
                timer.Draw += OnDraw;
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                // В графическом устройстве включите визуализацию XNA для режима совместного использования
                SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
    
                // Создание нового SpriteBatch, который может использоваться для рисования текстур.
                spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
    
                // TODO: загрузите сюда содержимое игры с помощью this.content
    
                // Запуск таймера
                timer.Start();
    
                base.OnNavigatedTo(e);
            }
    
            protected override void OnNavigatedFrom(NavigationEventArgs e)
            {
                // Остановка таймера
                timer.Stop();
    
                // В графическом устройстве выключите визуализацию XNA для режима совместного использования
                SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
    
                base.OnNavigatedFrom(e);
            }
    
            /// <summary>
            /// Позволяет странице выполнять логику, такую как обновление окружения,
            /// поиск конфликтов, сбор входных данных и воспроизведение звука.
            /// </summary>
            private void OnUpdate(object sender, GameTimerEventArgs e)
            {
                // TODO: добавьте здесь логику обновления
            }
    
            /// <summary>
            /// Разрешает автоматическую прорисовку страницы.
            /// </summary>
            private void OnDraw(object sender, GameTimerEventArgs e)
            {
                SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
    
                // TODO: добавьте здесь свой код прорисовки
            }
        }
    }

    В конструкторе (GamePage) мы получаем диспетчер содержимого, что позволяет нам работать с контентом, кроме того, в классе определено несколько переменных. Это – вышеупомянутый ContentManager, это timer – объект типа GameTimer, на основе которого на странице работают методы, аналогичные методам Update и Draw в обычных XNA-проектах. Это хорошо знакомый вам по XNA-проектам объект типа SpriteBatch, который используется для визуализации двумерных графических объектов.

    В конструкторе, кроме того, мы создаём игровой таймер, назначаем интервал срабатывания таймера, соответствующий времени обновления экрана (как в XNA-проектах, другими словами) и определяем обработчики для его событий Update и Draw – то есть – готовим всё для работы игрового цикла XNA.

    В методе OnNavigatedTo, выполняющемся при переходе на страницу, мы запускаем XNA-визуализацию, создаём новый SpriteBatch, и, кроме того, здесь мы будем загружать ресурсы игры. Здесь же мы запускаем таймер.

    Когда мы уходим со страницы, игровой таймер останавливается.

    Метод OnUpdate – это аналог метода Update из XNA. Здесь мы будем реализовывать игровую логику. Метод OnDraw – аналог метода Draw. Он предназначен для отрисовки игровых объектов. По умолчанию здесь присутствует лишь команда очистки экрана.

    Посмотрим теперь, как пользоваться вышеописанными механизмами на практике.

    35.2. Пример приложения

    Реализуем в новом окружении пример, аналогичный проекту P5_1, который мы рассматривали на пятом практическом занятии. Здесь игровой объект перемещался по экрану в позицию касания экрана.

    Создадим новый проект по шаблону Приложение Silverlight и XNA для Windows Phone, назовём проект P28_2.

    На странице MainPage.xaml изменим заголовок страницы на "ПЕРВАЯ ИГРА", поменяем подпись к кнопке на "Играть!". В листинге 35.7. приведен измененный код измененной страницы.

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="Portrait" Orientation="Portrait"
        shell:SystemTray.IsVisible="False">
    
        <!--LayoutRoot представляет корневую сетку, где размещается все содержимое страницы-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!--TitlePanel содержит имя приложения и заголовок страницы-->
            <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
                <TextBlock x:Name="ApplicationTitle" Text="МОЕ ПРИЛОЖЕНИЕ"
                 Style="{StaticResource PhoneTextNormalStyle}"/>
                <TextBlock x:Name="PageTitle" Text="первая игра" Margin="9,-7,0,0" 
                Style="{StaticResource PhoneTextTitle1Style}"/>
            </StackPanel>
    
            <!--ContentPanel — поместите здесь дополнительное содержимое-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                
                <!--Создание одной кнопки для перехода ко второй странице, визуализация которой выполнена XNA Framework-->
                <Button Height="100" Content="Играть!" Click="Button_Click" Margin="0,410,0,129" />
                
            </Grid>
        </Grid>
      
    </phone:PhoneApplicationPage>

    Теперь добавим в проект P28_2LibContent изображение для визуализации. Делается это командой контекстного меню проекта Добавить > Существующий элемент. В итоге наш проект будет выглядеть следующим образом, рис. 35.4.

    (рис 35.4) Игровой ресурс, добавленный в проект Content

    Вся остальная работа над игрой ведется в файле GamePage.xaml.cs. В листинге 35.8 приведен его полный код.

    using System;
    using System.Windows;
    using System.Windows.Navigation;
    using Microsoft.Phone.Controls;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input.Touch;
    
    namespace P28_2
    {
        public partial class GamePage : PhoneApplicationPage
        {
            // Менеджер контента
            ContentManager contentManager;
            // Таймер для организации игрового цикла
            GameTimer timer;
            // Объект для вывода 2D-графики
            SpriteBatch spriteBatch;
            // Текстура для хранения изображения
            private Texture2D mySprite;
            //Позиция вывода изображения
            private Vector2 position = new Vector2(0, 200);
    
            public GamePage()
            {
                InitializeComponent();
    
                // Получить диспетчер содержимого из приложения
                contentManager = (Application.Current as App).Content;
    
                // Создайте таймер для этой страницы
                timer = new GameTimer();
                timer.UpdateInterval = TimeSpan.FromTicks(333333);
                timer.Update += OnUpdate;
                timer.Draw += OnDraw;
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                // В графическом устройстве включите визуализацию XNA для режима совместного использования
                SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
    
                // Создание нового SpriteBatch, который может использоваться для рисования текстур.
                spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
    
                // Загружаем игровой контент
                mySprite = contentManager.Load<Texture2D>("ball");
    
                // Запуск таймера
                timer.Start();
    
                base.OnNavigatedTo(e);
            }
    
            protected override void OnNavigatedFrom(NavigationEventArgs e)
            {
                // Остановка таймера
                timer.Stop();
    
                // В графическом устройстве выключите визуализацию XNA для режима совместного использования
                SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
    
                base.OnNavigatedFrom(e);
            }
    
            /// <summary>
            /// Позволяет странице выполнять логику, такую как обновление окружения,
            /// поиск конфликтов, сбор входных данных и воспроизведение звука.
            /// </summary>
            private void OnUpdate(object sender, GameTimerEventArgs e)
            {
                //Получаем коллекцию объектов, содержащих информацию о касаниях экрана
                TouchCollection touchLocations = TouchPanel.GetState();
                //Перебираем коллекцию, присваивая объекту координаты касания
                foreach (TouchLocation touchLocation in touchLocations)
                {
                    if (touchLocation.State == TouchLocationState.Pressed)
                    {
                        position = touchLocation.Position;
                    }
                }
            }
    
            /// <summary>
            /// Разрешает автоматическую прорисовку страницы.
            /// </summary>
            private void OnDraw(object sender, GameTimerEventArgs e)
            {
                // Очищаем экран
                SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
                // Выводим изображение
                spriteBatch.Begin();
                    spriteBatch.Draw(mySprite, position, Color.White);
                spriteBatch.End();
            }
        }
    }

    Обратите внимание на то, что для работы с сенсорным вводом нам понадобилось подключить пространство имён Microsoft.Xna.Framework.Input.Touch.

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

    mySprite = contentManager.Load<Texture2D>("ball");

    В целом же наш простой пример в модификации практически не нуждался (если не учитывать необходимость располагать соответствующий код в методах, выполняющих ту же функциональность, что и в XNA, но с другими именами). В итоге мы получили следующее, рис. 35.5.

    (рис 35.5) Игровой экран проекта

    Обратите внимание на то, что начало координат расположено в левом верхнем углу экрана, экран по умолчанию расположен в портретной ориентации.

    35.3. Выводы

    В данной лабораторной работе мы рассмотрели шаблон Приложение Silverlignt и XNA для Windows Phone. Он позволяет объединять в одном приложении возможности XNA и Silverlight.

    35.4. Задание

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

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

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

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

    Страницы:

    Цель работы: Научиться разрабатывать приложения с использованием шаблона Приложение Silverlight и XNA для Windows Phone

    35.1. Особенности проекта Приложение Silverlight и XNA для Windows Phone

    В некоторых из предыдущих проектов мы использовали отдельные библиотеки и объекты XNA в Silverlight-приложениях. Начиная с Windows Phone OS 7.1. при разработке приложений можно в полной мере пользоваться возможностями Silverlight и XNA в одном приложении. Например, можно создать сложный пользовательский интерфейс на Silverlight, и, в то же время, организовать вывод графики с помощью средств XNA. Фактически, такое приложение позволяет взять всё лучшее от обеих технологий.

    Рассмотрим особенности таких приложений. Создадим новый проект, в окне создания проекта выберем шаблон Приложение Silverlight и XNA для Windows Phone, рис. 35.1.

    (рис 35.1) Создание проекта

    Посмотрим на Обозреватель решений этого проекта, рис. 35.2

    (рис 35.2) Обозреватель решений для проекта P28_1

    Если рассмотреть это решение в первом приближении, то окажется, что мы имеем Silverlight-проект, в котором есть механизмы для визуализации средствами XNA и страница GamePage.xaml, код которой (GamePage.xaml.cs) представляет собой реализацию игрового цикла XNA. Весь вывод данных на этой странице формируется средствами XNA.

    Рассмотрим решение подробнее. Оно состоит из трёх проектов:

  • Основной проект приложения (P28_1). Это проект приложения, здесь содержатся страницы приложения. Причём, страница MainPage – это обычная Silverlight-страница, которая отображается после запуска приложения, на ней, по умолчанию, расположена кнопка для вызова страницы GamePage.
  • Проект библиотеки XNA (P28_1Lib). Этот проект напоминает проект XNA, разработкой которых мы занимались ранее в этом курсе. Он имеет ссылку на проект контента. Этот проект представляет собой нечто вроде моста между Silverlight-приложением и хранилищем контента.
  • Проект контента XNA (P281Lib_Content). Традиционный для XNA-приложений проект, содержащий контент – изображения, звуки, трехмерные модели.
  • Рассмотрим файлы, которые входят в решение. Начнём с файла App.xaml, листинге 35.1.

    <Application 
        x:Class="P28_1.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"       
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
        xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
         xmlns:xna="clr-namespace:Microsoft.Xna.Framework;assembly=Microsoft.Xna.Framework.Interop">
    
        <!--Ресурсы приложения-->
        <Application.Resources>
        </Application.Resources>
    
        <Application.ApplicationLifetimeObjects>
            <!--Обязательный объект, обрабатывающий события времени жизни приложения-->
            <shell:PhoneApplicationService 
                Launching="Application_Launching" Closing="Application_Closing" 
                Activated="Application_Activated" Deactivated="Application_Deactivated"/>
    
    
        <!--SharedGraphicsDeviceManager используется для отображения с помощью графических API XNA-->
        <xna:SharedGraphicsDeviceManager />
        </Application.ApplicationLifetimeObjects>
    
    </Application>

    Этот файл, как и в случае с обычным Silverlight-приложением, определяет обработчики события, касающиеся жизненного цикла приложения, и, кроме того, определяет xna:SharedGraphicsDeviceManager – объект, который используется для вывода XNA-графики.

    В листинге 35.2 приведен файл App.xaml.cs,

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using Microsoft.Phone.Controls;
    using Microsoft.Phone.Shell;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    
    namespace P28_1
    {
        public partial class App : Application
        {
            /// <summary>
            /// Обеспечивает быстрый доступ к корневому кадру приложения телефона.
            /// </summary>
            /// <returns>Корневой кадр приложения телефона.</returns>
            public PhoneApplicationFrame RootFrame { get; private set; }
    
            /// <summary>
            /// Предоставляет приложению доступ к ContentManager.
            /// </summary>
            public ContentManager Content { get; private set; }
    
            /// <summary>
            /// Предоставляет доступ к GameTimer, настроенному для передачи данных в FrameworkDispatcher.
            /// </summary>
            public GameTimer FrameworkDispatcherTimer { get; private set; }
    
            /// <summary>
            /// Предоставляет доступ к AppServiceProvider для приложения.
            /// </summary>
            public AppServiceProvider Services { get; private set; }
    
            /// <summary>
            /// Конструктор объекта приложения.
            /// </summary>
            public App()
            {
                // Глобальный обработчик неперехваченных исключений. 
                UnhandledException += Application_UnhandledException;
    
                // Стандартная инициализация Silverlight
                InitializeComponent();
    
                // Инициализация телефона
                InitializePhoneApplication();
    
                // Инициализация XNA
                InitializeXnaApplication();
    
                // Отображение сведений о профиле графики во время отладки.
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    // Отображение текущих счетчиков частоты смены кадров.
                    Application.Current.Host.Settings.EnableFrameRateCounter = true;
    
                    // Отображение областей приложения, перерисовываемых в каждом кадре.
                    //Application.Current.Host.Settings.EnableRedrawRegions = true;
    
                    // Включение режима визуализации анализа нерабочего кода 
                    // для отображения областей страницы, переданных в GPU, с цветным наложением.
                    //Application.Current.Host.Settings.EnableCacheVisualization = true;
    
                    // Отключите обнаружение простоя приложения, установив для свойства UserIdleDetectionMode
                    // объекта PhoneApplicationService приложения значение Disabled.
                    // Внимание! Используйте только в режиме отладки. Приложение, в котором отключено обнаружение 
                     бездействия пользователя, будет продолжать работать
                    // и потреблять энергию батареи, когда телефон не будет использоваться.
                    PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
                }
            }
    
            // Код для выполнения при запуске приложения (например, из меню "Пуск")
            // Этот код не будет выполняться при повторной активации приложения
            private void Application_Launching(object sender, LaunchingEventArgs e)
            {
            }
    
            // Код для выполнения при активации приложения (переводится в основной режим)
            // Этот код не будет выполняться при первом запуске приложения
            private void Application_Activated(object sender, ActivatedEventArgs e)
            {
            }
    
            // Код для выполнения при деактивации приложения (отправляется в фоновый режим)
            // Этот код не будет выполняться при закрытии приложения
            private void Application_Deactivated(object sender, DeactivatedEventArgs e)
            {
            }
    
            // Код для выполнения при закрытии приложения (например, при нажатии пользователем кнопки "Назад")
            // Этот код не будет выполняться при деактивации приложения
            private void Application_Closing(object sender, ClosingEventArgs e)
            {
            }
    
            // Код для выполнения в случае ошибки навигации
            private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
            {
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    // Ошибка навигации; перейти в отладчик
                    System.Diagnostics.Debugger.Break();
                }
            }
    
            // Код для выполнения на необработанных исключениях
            private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
            {
                if (System.Diagnostics.Debugger.IsAttached)
                {
                    // Произошло необработанное исключение; перейти в отладчик
                    System.Diagnostics.Debugger.Break();
                }
            }
    
            #region Инициализация приложения телефона
    
            // Избегайте двойной инициализации
            private bool phoneApplicationInitialized = false;
    
            // Не добавляйте в этот метод дополнительный код
            private void InitializePhoneApplication()
            {
                if (phoneApplicationInitialized)
                    return;
    
                // Создайте кадр, но не задавайте для него значение RootVisual; это позволит
                // экрану-заставке оставаться активным, пока приложение не будет готово для визуализации.
                RootFrame = new PhoneApplicationFrame();
                RootFrame.Navigated += CompleteInitializePhoneApplication;
    
                // Обработка сбоев навигации
                RootFrame.NavigationFailed += RootFrame_NavigationFailed;
    
                // Убедитесь, что инициализация не выполняется повторно
                phoneApplicationInitialized = true;
            }
    
            // Не добавляйте в этот метод дополнительный код
            private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
            {
                // Задайте корневой визуальный элемент для визуализации приложения
                if (RootVisual != RootFrame)
                    RootVisual = RootFrame;
    
                // Удалите этот обработчик, т.к. он больше не нужен
                RootFrame.Navigated -= CompleteInitializePhoneApplication;
            }
    
            #endregion
    
            #region Инициализация приложения XNA
    
            // Выполняет инициализацию типов XNA, необходимых для приложения.
            private void InitializeXnaApplication()
            {
                // Создание поставщика услуг
                Services = new AppServiceProvider();
    
                // Добавить SharedGraphicsDeviceManager к службам в качестве IGraphicsDeviceService для приложения
                foreach (object obj in ApplicationLifetimeObjects)
                {
                    if (obj is IGraphicsDeviceService)
                        Services.AddService(typeof(IGraphicsDeviceService), obj);
                }
    
                // Создайте ContentManager для загрузки в приложение предварительно скомпилированных ресурсов
                Content = new ContentManager(Services, "Content");
    
                // Создайте GameTimer для получения XNA FrameworkDispatcher
                FrameworkDispatcherTimer = new GameTimer();
                FrameworkDispatcherTimer.FrameAction += FrameworkDispatcherFrameAction;
                FrameworkDispatcherTimer.Start();
            }
    
            // Обработчик событий передает FrameworkDispatcher во все кадры.
            // FrameworkDispatcher требуется для большинства событий XNA и
            // некоторых функций, таких как воспроизведение SoundEffect.
            private void FrameworkDispatcherFrameAction(object sender, EventArgs e)
            {
                FrameworkDispatcher.Update();
            }
    
            #endregion
        }
    }

    Это всё тот же App.xaml.cs, которым мы пользовались ранее, однако, здесь производится инициализация механизмов XNA, создается ConetntManager для работы с XNA-контентом, создаётся таймер уровня приложения для FrameworkDispatcher.Update().

    Страница MainPage.xaml – это обычная стартовая страница Silverlight-приложения, рис. 35.3.

    (рис 35.3) Страница MainPage.xaml

    Здесь располагается кнопка "Изменить на страницу игры". Правильнее будет подписать её как "Перейти на страницу игры", так как именно эту функцию она и выполняет. В листинге 35.3 приведен её код.

    <phone:PhoneApplicationPage 
        x:Class="P28_1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
        xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="Portrait" Orientation="Portrait"
        shell:SystemTray.IsVisible="False">
    
        <!--LayoutRoot представляет корневую сетку, где размещается все содержимое страницы-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!--TitlePanel содержит имя приложения и заголовок страницы-->
            <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
                <TextBlock x:Name="ApplicationTitle" Text="МОЕ ПРИЛОЖЕНИЕ" 
                Style="{StaticResource PhoneTextNormalStyle}"/>
                <TextBlock x:Name="PageTitle" Text="имя страницы" Margin="9,-7,0,0" 
                Style="{StaticResource PhoneTextTitle1Style}"/>
            </StackPanel>
    
            <!--ContentPanel — поместите здесь дополнительное содержимое-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                
                <!--Создание одной кнопки для перехода ко второй странице, визуализация которой выполнена XNA Framework-->
                <Button Height="100" Content="Изменить на страницу игры" Click="Button_Click" />
                
            </Grid>
        </Grid>
     
        <!--Пример кода, иллюстрирующий использование ApplicationBar-->
        <!--<phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Кнопка 1"/>
                <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Кнопка 2"/>
                <shell:ApplicationBar.MenuItems>
                    <shell:ApplicationBarMenuItem Text="Элемент меню 1"/>
                    <shell:ApplicationBarMenuItem Text="Элемент меню 2"/>
                </shell:ApplicationBar.MenuItems>
            </shell:ApplicationBar>
        </phone:PhoneApplicationPage.ApplicationBar>-->
    
    </phone:PhoneApplicationPage>

    Страница стандартна, она по умолчанию содержит настройку, скрывающую системные значки, что приводит к отображению страницы на полный экран. В её CS-файле, листинг 35.4., содержится обработчик нажатия на кнопку, открывающий страницу GamePage.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using Microsoft.Phone.Controls;
    
    namespace P28_1
    {
        public partial class MainPage : PhoneApplicationPage
        {
            // Конструктор
            public MainPage()
            {
                InitializeComponent();
            }
    
            // Простая кнопка Щелкните обработчик событий, чтобы перейти на вторую страницу
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                NavigationService.Navigate(new Uri("/GamePage.xaml", UriKind.Relative));
            }
        }
    }

    Собственно говоря, мы можем пользоваться всем арсеналом инструментов и приёмов Silverlight, работая со страницей MainPage и с другими Silverlight—страницами проекта.

    XAML-код страницы GamePage, листинг 35.5., задаёт лишь свойства PhoneApplicationPage. В частности, по умолчанию игровой экран будет выводиться в портретной ориентации.

    <phone:PhoneApplicationPage 
        x:Class="P28_1.GamePage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
        xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="Portrait" Orientation="Portrait"
        mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="480"
        shell:SystemTray.IsVisible="False">
        
        <!--Содержимое XAML не требуется, поскольку полная визуализация страницы выполнена XNA Framework-->
    
    </phone:PhoneApplicationPage>

    Другой нагрузки он, в шаблоне проекта, не несет, так как вся работа по созданию игры ведется в файле GamePage.xaml.cs. Если мы соберемся использовать Silverlight-элементы на XNA-странице, нам придется описывать их визуализацию в GamePage.xaml.cs. В этом файле нас ждёт самое интересное – реализация механизмов XNA.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using Microsoft.Phone.Controls;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    
    namespace P28_1
    {
        public partial class GamePage : PhoneApplicationPage
        {
            ContentManager contentManager;
            GameTimer timer;
            SpriteBatch spriteBatch;
    
            public GamePage()
            {
                InitializeComponent();
    
                // Получить диспетчер содержимого из приложения
                contentManager = (Application.Current as App).Content;
    
                // Создайте таймер для этой страницы
                timer = new GameTimer();
                timer.UpdateInterval = TimeSpan.FromTicks(333333);
                timer.Update += OnUpdate;
                timer.Draw += OnDraw;
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                // В графическом устройстве включите визуализацию XNA для режима совместного использования
                SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
    
                // Создание нового SpriteBatch, который может использоваться для рисования текстур.
                spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
    
                // TODO: загрузите сюда содержимое игры с помощью this.content
    
                // Запуск таймера
                timer.Start();
    
                base.OnNavigatedTo(e);
            }
    
            protected override void OnNavigatedFrom(NavigationEventArgs e)
            {
                // Остановка таймера
                timer.Stop();
    
                // В графическом устройстве выключите визуализацию XNA для режима совместного использования
                SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
    
                base.OnNavigatedFrom(e);
            }
    
            /// <summary>
            /// Позволяет странице выполнять логику, такую как обновление окружения,
            /// поиск конфликтов, сбор входных данных и воспроизведение звука.
            /// </summary>
            private void OnUpdate(object sender, GameTimerEventArgs e)
            {
                // TODO: добавьте здесь логику обновления
            }
    
            /// <summary>
            /// Разрешает автоматическую прорисовку страницы.
            /// </summary>
            private void OnDraw(object sender, GameTimerEventArgs e)
            {
                SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
    
                // TODO: добавьте здесь свой код прорисовки
            }
        }
    }

    В конструкторе (GamePage) мы получаем диспетчер содержимого, что позволяет нам работать с контентом, кроме того, в классе определено несколько переменных. Это – вышеупомянутый ContentManager, это timer – объект типа GameTimer, на основе которого на странице работают методы, аналогичные методам Update и Draw в обычных XNA-проектах. Это хорошо знакомый вам по XNA-проектам объект типа SpriteBatch, который используется для визуализации двумерных графических объектов.

    В конструкторе, кроме того, мы создаём игровой таймер, назначаем интервал срабатывания таймера, соответствующий времени обновления экрана (как в XNA-проектах, другими словами) и определяем обработчики для его событий Update и Draw – то есть – готовим всё для работы игрового цикла XNA.

    В методе OnNavigatedTo, выполняющемся при переходе на страницу, мы запускаем XNA-визуализацию, создаём новый SpriteBatch, и, кроме того, здесь мы будем загружать ресурсы игры. Здесь же мы запускаем таймер.

    Когда мы уходим со страницы, игровой таймер останавливается.

    Метод OnUpdate – это аналог метода Update из XNA. Здесь мы будем реализовывать игровую логику. Метод OnDraw – аналог метода Draw. Он предназначен для отрисовки игровых объектов. По умолчанию здесь присутствует лишь команда очистки экрана.

    Посмотрим теперь, как пользоваться вышеописанными механизмами на практике.

    35.2. Пример приложения

    Реализуем в новом окружении пример, аналогичный проекту P5_1, который мы рассматривали на пятом практическом занятии. Здесь игровой объект перемещался по экрану в позицию касания экрана.

    Создадим новый проект по шаблону Приложение Silverlight и XNA для Windows Phone, назовём проект P28_2.

    На странице MainPage.xaml изменим заголовок страницы на "ПЕРВАЯ ИГРА", поменяем подпись к кнопке на "Играть!". В листинге 35.7. приведен измененный код измененной страницы.

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="Portrait" Orientation="Portrait"
        shell:SystemTray.IsVisible="False">
    
        <!--LayoutRoot представляет корневую сетку, где размещается все содержимое страницы-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!--TitlePanel содержит имя приложения и заголовок страницы-->
            <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
                <TextBlock x:Name="ApplicationTitle" Text="МОЕ ПРИЛОЖЕНИЕ"
                 Style="{StaticResource PhoneTextNormalStyle}"/>
                <TextBlock x:Name="PageTitle" Text="первая игра" Margin="9,-7,0,0" 
                Style="{StaticResource PhoneTextTitle1Style}"/>
            </StackPanel>
    
            <!--ContentPanel — поместите здесь дополнительное содержимое-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                
                <!--Создание одной кнопки для перехода ко второй странице, визуализация которой выполнена XNA Framework-->
                <Button Height="100" Content="Играть!" Click="Button_Click" Margin="0,410,0,129" />
                
            </Grid>
        </Grid>
      
    </phone:PhoneApplicationPage>

    Теперь добавим в проект P28_2LibContent изображение для визуализации. Делается это командой контекстного меню проекта Добавить > Существующий элемент. В итоге наш проект будет выглядеть следующим образом, рис. 35.4.

    (рис 35.4) Игровой ресурс, добавленный в проект Content

    Вся остальная работа над игрой ведется в файле GamePage.xaml.cs. В листинге 35.8 приведен его полный код.

    using System;
    using System.Windows;
    using System.Windows.Navigation;
    using Microsoft.Phone.Controls;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input.Touch;
    
    namespace P28_2
    {
        public partial class GamePage : PhoneApplicationPage
        {
            // Менеджер контента
            ContentManager contentManager;
            // Таймер для организации игрового цикла
            GameTimer timer;
            // Объект для вывода 2D-графики
            SpriteBatch spriteBatch;
            // Текстура для хранения изображения
            private Texture2D mySprite;
            //Позиция вывода изображения
            private Vector2 position = new Vector2(0, 200);
    
            public GamePage()
            {
                InitializeComponent();
    
                // Получить диспетчер содержимого из приложения
                contentManager = (Application.Current as App).Content;
    
                // Создайте таймер для этой страницы
                timer = new GameTimer();
                timer.UpdateInterval = TimeSpan.FromTicks(333333);
                timer.Update += OnUpdate;
                timer.Draw += OnDraw;
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                // В графическом устройстве включите визуализацию XNA для режима совместного использования
                SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
    
                // Создание нового SpriteBatch, который может использоваться для рисования текстур.
                spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
    
                // Загружаем игровой контент
                mySprite = contentManager.Load<Texture2D>("ball");
    
                // Запуск таймера
                timer.Start();
    
                base.OnNavigatedTo(e);
            }
    
            protected override void OnNavigatedFrom(NavigationEventArgs e)
            {
                // Остановка таймера
                timer.Stop();
    
                // В графическом устройстве выключите визуализацию XNA для режима совместного использования
                SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
    
                base.OnNavigatedFrom(e);
            }
    
            /// <summary>
            /// Позволяет странице выполнять логику, такую как обновление окружения,
            /// поиск конфликтов, сбор входных данных и воспроизведение звука.
            /// </summary>
            private void OnUpdate(object sender, GameTimerEventArgs e)
            {
                //Получаем коллекцию объектов, содержащих информацию о касаниях экрана
                TouchCollection touchLocations = TouchPanel.GetState();
                //Перебираем коллекцию, присваивая объекту координаты касания
                foreach (TouchLocation touchLocation in touchLocations)
                {
                    if (touchLocation.State == TouchLocationState.Pressed)
                    {
                        position = touchLocation.Position;
                    }
                }
            }
    
            /// <summary>
            /// Разрешает автоматическую прорисовку страницы.
            /// </summary>
            private void OnDraw(object sender, GameTimerEventArgs e)
            {
                // Очищаем экран
                SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
                // Выводим изображение
                spriteBatch.Begin();
                    spriteBatch.Draw(mySprite, position, Color.White);
                spriteBatch.End();
            }
        }
    }

    Обратите внимание на то, что для работы с сенсорным вводом нам понадобилось подключить пространство имён Microsoft.Xna.Framework.Input.Touch.

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

    mySprite = contentManager.Load<Texture2D>("ball");

    В целом же наш простой пример в модификации практически не нуждался (если не учитывать необходимость располагать соответствующий код в методах, выполняющих ту же функциональность, что и в XNA, но с другими именами). В итоге мы получили следующее, рис. 35.5.

    (рис 35.5) Игровой экран проекта

    Обратите внимание на то, что начало координат расположено в левом верхнем углу экрана, экран по умолчанию расположен в портретной ориентации.

    35.3. Выводы

    В данной лабораторной работе мы рассмотрели шаблон Приложение Silverlignt и XNA для Windows Phone. Он позволяет объединять в одном приложении возможности XNA и Silverlight.

    35.4. Задание

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

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

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

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

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