Невозможно пройти тесты, в окне с вопросами пусто |
2D-графика в XNA
Теперь выполним действия, необходимые для вывода нашего изображения на игровой экран. Для этого в раздел объявления свойств класса добавим две переменные (листинг 8.1.).
private Texture2D MySprite; private Vector2 position = new Vector2(10,10);Листинг 8.1. Переменные для работы с изображением
Первая переменная – MySprite – имеет тип Texture2D. Этот тип данных предназначен для хранения двумерных изображений, или текстур. Именно в эту переменную мы будем загружать изображение, которое ранее добавили в проект.
Вторая переменная - position – нужна для хранения позиции, в которую наша текстура будет выведена на экран. Мы сразу же инициализируем эту переменную значениями 10,10 – это координаты вывода изображения. Координаты привязаны к левому верхнему углу экрана. То есть – именно его левый верхний угол будет находиться в позиции 150,200 при выводе. Если бы мы инициализировали эту переменную значениями 0,0, изображение было бы выведено в начале координат – то есть – его верхний угол находился бы в позиции 0,0, а само оно располагалось бы в верхнем левом углу игрового поля.
Обратите внимание, без дополнительных настроек речь идет о ландшафтной ориентации экрана.
Теперь загрузим текстуру в переменную MySprite. Для этого модифицируем метод LoadContent следующим образом (листинге 8.2.).
protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); MySprite = Content.Load<Texture2D>("P3_1"); }Листинг 8.2. Загрузка изображения в переменную MySprite
Здесь, для наглядности, удалены стандартные комментарии. Итак, для загрузки мы пользуемся методом Load объекта Content. Причем, обратите внимание на то, что метод Load вызывается с указанием типа загружаемого игрового ресурса. В нашем случае это – двумерное изображение, поэтому тип ресурса указывается как Texture2D. Несложно заметить, что этот тип соответствует типу переменной MySprite.
Теперь настала очередь вывода изображения на игровой экран. Для этого нам понадобится модифицировать метод Draw. В листинге 8.3 приведен его код.
protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.Draw(MySprite, position, Color.White); spriteBatch.End(); base.Draw(gameTime); }Листинг 8.3. Вывод изображения
Метод Begin() объекта spriteBatch подготавливает графическое устройство к выводу изображения. Метод Draw того же объекта принимает в качестве параметров переменную типа Texture2D (MySprite), переменную типа Vector2 (position) и цвет, в соответствии с которым будет изменен оттенок изображения. В данном случае это белый цвет – это значит, что цвета изображения останутся неизменными. Метод End() завершает процедуру вывода – изображения выводятся на экран.
Метод Draw базового класса в данном случае не используется для вывода изображений. Мы рассмотрим его роль в выводе изображений ниже.
Запустив программу в эмуляторе, мы получим такое изображение (рис. 8.5.):
По умолчанию эмулятор расположен вертикально, так как XNA-программа по умолчанию работает с экраном, расположенным горизонтально, для того, чтобы приблизить внешний вид изображения к тому режиму, в котором будет использоваться устройство, окно эмулятора можно повернуть. Обратите внимание на кнопку поворота эмулятора против часовой стрелки, которая присутствует на панели инструментов, которая появляется при наведении на окно эмулятора указателя мыши. Нажатие на эту кнопку приводит к повороту окна эмулятора, рис. 8.6.
Похожим образом выглядит запуск нашей программы на устройстве, однако, здесь нужно учитывать то, что по умолчанию на экране отображается строка состояния. В итоге, мы работаем с тем же разрешением, но окно вывода занимает не весь экран, масштабируясь для сохранения пропорций, рис. 8.7. Строку состояния можно скрыть.
Обратите внимание на то, что экран отображается правильно (то есть – начало системы координат совпадает с левым верхним углом экрана устройства) при любом расположении устройства (то есть – и при повороте по часовой стрелки относительно вертикального расположения экрана, и при повороте против часовой стрелки) – таким образом устройство самостоятельно разворачивает экран в соответствии с настройками по умолчанию.
Мы рассмотрели простейший способ вывода изображения. Однако использование такого способа вывода изображений при разработке реальных игровых проектов не слишком удобно. Дело в том, что игровой объект, который стоит за каждым изображением, может иметь множество свойств. Например – скорость и направление движения. Так же количество объектов в игре может меняться. К тому же, не очень удобно хранить множество свойств различных игровых объектов, используя область объявления свойств основного игрового проекта. Удобнее было бы выделить данные об игровых объектах в отдельный класс и пользоваться объектами, сгенерированными на основе этого класса для вывода изображений и организации игровой логики. Собственно говоря, на практике так и поступают, создавая отдельные игровые компоненты (классы) для представления игровых объектов. Как правило, в реальных проектах создается целая система классов игровых объектов, связанных отношениями наследования.
Способ, описанный выше, может использоваться для вывода некоторых объектов, например – так можно вывести фоновый рисунок.
Рассмотрим подход, который позволяет упростить подход к выводу изображений методом, описанным выше.
8.3. Разработка класса для хранения графической информации
Создадим новый стандартный проект XNA-игры с именем P3_2. Добавим в проект изображение, которое мы создали выше (P3_1.png).
Теперь подумаем, каким должен быть класс >для представления игрового объекта. Сейчас нам нужен класс, объект которого сможет хранить текстуру спрайта и информацию о его позиции на экране. При необходимости этот список можно расширить – причем – как свойствами – так и методами. Но над подобным проектом мы будем работать на одном из следующих занятий. Ограничимся пока двумя вышеназванными свойствами.
Щелкнем по значку проекта P3_2 в панели Обозреватель проектов и выберем в появившемся меню пункт Добавить > Класс. Появится окно добавления нового элемента, в котором будет выделен шаблон пустого класса (рис. 8.8).
Зададим имя новому классу – пусть это будет spriteClass, и нажмем кнопку Добавить. В проект будет добавлен новый пустой класс. Вот как выглядит наш проект после всех перечисленных действий (рис. 8.9).
Модифицируем код класса spriteClass следующим образом.
Во-первых, в раздел подключения пространств имен добавим директивы using, содержащие те пространства имен, которые содержат объявления типов данных, необходимых для хранения текстуры и ее позиции.
Во-вторых – создадим необходимые свойства класса.
В-третьих – построим конструктор класса, который будет инициализировать эти свойства.
Вот как выглядит код класса после всех модификаций (листинг 8.4.):
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; namespace P3_2 { class spriteClass { public Texture2D spTexture; public Vector2 spPosition; public spriteClass(Texture2D newSpTexture, Vector2 newSpPosition) { spTexture = newSpTexture; spPosition = newSpPosition; } } }Листинг 8.4. Код класса spriteClass
Здесь мы подключили к нашему классу пространство имен Microsoft.Xna.Framework.Graphics – оно нужно нам, так как содержит объявление типа данных Texture2D. Пространство имен Microsoft.Xna.Framework содержит объявление типа данных Vector2.
В раздел объявления свойств класса мы добавили две переменных. Переменная spTexture типа Texture2D используется для хранения текстуры, переменная spPosition типа Vector2 – для хранения данных о положении объекта на экране. В конструкторе класса мы инициализируем эти переменные значениями соответствующих типов, переданными при создании объекта типа spriteClass в основном тексте программы.
Свойства класса объявлены с модификаторами доступности public. Это значит, что мы сможем модифицировать эти свойства извне. В данном случае такой подход оправдан. Однако надо отметить, что при конструировании классов рекомендуется защищать свойства от прямой модификации или чтения. Для организации доступа к таким свойствам можно создавать дополнительные свойства-методы, пользуясь директивами get/set.
Теперь внесем изменения в основной текст игры, который расположен в файле Game1.cs. В листинге 8.5 приведены эти изменения с комментариями, указывающими на их местонахождение.
/// Объект mySpriteObj для хранения данных об изображении для вывода на экран spriteClass mySpriteObj; /// Добавляем в метод LoadContent команду создания нового объекта типа spriteClass. Передаем в качестве текстуры изображения текстуру, загруженную с помощью команды Content.Load, в качестве позиции – новый объект типа Vector2 mySpriteObj = new spriteClass(Content.Load<Texture2D>("P3_1"), new Vector2(10f, 10f)); /// В метод Draw добавляем соответствующие вызовы методов объекта spriteBatch. Обратите внимание на то, что при вызове метода Draw мы пользуемся свойствами объекта mySpriteObj. spriteBatch.Begin(); spriteBatch.Draw(mySpriteObj.spTexture, mySpriteObj.spPosition, Color.White); spriteBatch.End(); /// В метод UnloadContent добавляем команды для освобождения системных ресурсов. mySpriteObj.spTexture.Dispose(); spriteBatch.Dispose();Пример 8.5. Изменения, внесенные в код файла Game1.cs
При запуске данного примера мы получим тот же графический вывод, который получили при проверке предыдущего.
Как видите, если сравнить предыдущий подход с хранением всех данных о выводимом изображении в классе Game1 и подход с хранением этих же данных в специально разработанном классе, можно заметить, что выделение отдельного класса для спрайта позволяет удобно организовывать все необходимые свойства и методы для работы с объектом.
Однако и этот подход не является единственно возможным. Еще более интересный способ вывода изображений и создания игровых объектов – это создание игровых объектов на базе игровых компонентов.