Иван Андреев andreev.ia@gmail.com XNA для начинающих. Практика. Перемещение объектов в 2D пространстве Целью данной работы является получение базовых навыков работы с 2D графикой и игровым временем. Задание на работу: Создать XNA приложение, установить размеры окна. Нарисовать в левом верхнем углу экрана спрайт произвольного размера (лучше установить достаточно большой размер). При прохождении некоторого количества времени (либо при нажатии на кнопку на клавиатуре) спрайт должен переместиться в правый верхний угол, затем в правый нижний угол, левый нижний угол и, наконец, в центр экрана, причем центр спрайта должен совпасть с центром экрана. Далее, можно либо зациклить перемещение, либо оставить спрайт в центре экрана. Теоретическая часть: Основы 2Д графики Перед тем как перейти к работе следует вспомнить теоретические основы. В двумерной графике каждый пиксель характеризуется координатами и цветом. На экране монитора (а также в любом компьютерном изображении, например, картинке, созданной в Paint) началом координат является левый верхний угол, ось X направлена слева направо, а ось Y сверху вниз. Координаты пикселя отсчитываются от 0, соответственно левый верхний угол экрана имеет координаты {0,0}, а правый нижний – {MaxX-1, MaxY-1}, где MaxX, MaxY – разрешение экрана. 1 Для того чтобы установить разрешение окна в XNA Framework необходимо написать следующий код: public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferWidth = 640; graphics.PreferredBackBufferHeight = 480; } В данном примере ширина устанавливается равной 640, а высота – 480. Цвет пикселя составляется из трех компонент: красной, синей и зеленой (также имеется альфа-канал, который отвечает за прозрачность данного пикселя для изображений, для пикселя монитора альфа составляющая не имеет смысла). Значения цвета по каждой из этих компонент лежат в диапазоне от 0 до 255 либо от 0 до 1. Так, если значения каждой из компонент равняются 255, получается белый цвет, а если значения равняются 0, то получается черный свет. Для работы с цветами в XNA существует класс Color, который поддерживает огромное количество стандартных цветов (например, Color.Red – красный, Color.Black - черный), а также позволяет создавать собственные цвета. SpriteBatch Основным объектом для работы с 2D графикой является SpriteBatch, более подробно работа со SpriteBatch будет рассмотрена в следующих практических занятиях, в рамках данного занятия необходимо знать лишь основные особенности работы со SpriteBatch. 2 Вся работа по отображению изображений на экран осуществляется в рамках блоков отображения SpriteBatch.Begin изображений на – SpriteBatch.End. экран нужно Для собственно использовать метод SpriteBatch.Draw. Существуют большое количество перегруженных вариантов вызова данного метода, однако в рамках данной работы будет рассмотрен наиболее простой. public void Draw ( Texture2D texture, Vector2 position, Color color ) texture – изображение, которое необходимо нарисовать на экране. position – позиция левого верхнего угла изображения на экране. color – цвет, используемый для смешивания перед выводом изображения на экран. Чаще всего, этот параметр будет принимать значение Color.White. В таком случае изображение будет иметь цвета, которые были заданы при создании изображения. Вы можете самостоятельно поэксперементировать с данным параметром. Загрузка содержимого (контента) игры Перед тем как работать с изображениями (или другими типами игрового содержимого: моделями, звуками, шрифтами и т.д.) необходимо загрузить их в игру при помощи ContentManager. Для загрузки игрового содержимого в каркасе приложения предусмотрен специальный метод LoadContent. Загрузка определенного типа содержимого осуществляется вызовом метода Content.Load<ТипСодержимого>(“НазваниеФайлаСодержимого”). 3 Например, работы с друмерными изображениями в XNA Frawework используется класс Texture2D. sprite = Content.Load<Texture2D>("filename"); Объект Texture2D содержит различные свойства, которые пригодятся нам для реализации данной работы. В частности, Width и Height возращают значения ширины и высоты исходного изображения. Игровое время Теперь вспомним основы работы с игровым временем в XNA Framework. Методы Update и Draw получают в качестве параметра единственный объект GameTime, который отвечает за игровое время. Рассмотрим основые свойства объекта GameTime: ElapsedGameTime Игровое время, предыдущего прошедшее вызова с метода Draw/Update ElapsedRealTime Реальное время, предыдущего прошедшее вызова с метода Draw/Update IsRunningSlowly Если это свойство имеет значение true, то время, проходящее между последовательными метода вызовами Update/Draw значения, больше указанного в TargetElapsedTime. Это означает, что игра 4 работает медленнее, чем требуется. Если IsRunningSlowly равняется true, то стоит задуматься над оптимизацией TotalGameTime Игровое время, прошедшее с запуска игры TotalRealTime Реальное время, прошедшее с запуска игры Для нас наиболее важными свойствами являются ElapsedGameTime и TotalGameTime. Они возвращают объект типа TimeSpan, который имеет множество различных свойств. Рассмотрим лишь наиболее важные: Days Целое количество дней Hours Целое количество часов Milliseconds Целое количество миллисекунд Minutes Целое количество минут Seconds Целое количество секунд Ticks Целое количество системных «тиков» TotalDays Вещественное значение дней TotalHours Вещественное значение часов TotalMilliseconds Вещественное значение миллисекунд TotalMinutes Вещественное значение минут TotalSeconds Вещественное значение секунд Обратите внимание на то, что свойства, название которых не начинается с Total, возвращают целое число, то есть, например, Days будет иметь значение 0 до тех пор, пока не пройдет целый день. В то же время 5 значение TotalDays будет постоянно увеличиваться и по-прошествие дня примет значение 1. Реализация: В следующем листинге приведена простая реализация без зацикливания и обработки пользовательского ввода: using using using using using using using using using using using using System; System.Collections.Generic; System.Linq; Microsoft.Xna.Framework; Microsoft.Xna.Framework.Audio; Microsoft.Xna.Framework.Content; Microsoft.Xna.Framework.GamerServices; Microsoft.Xna.Framework.Graphics; Microsoft.Xna.Framework.Input; Microsoft.Xna.Framework.Media; Microsoft.Xna.Framework.Net; Microsoft.Xna.Framework.Storage; namespace Lab1 { /// <summary> /// This is the main type for your game /// </summary> public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Texture2D sprite; int width; int height; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; width = graphics.PreferredBackBufferWidth = 800; height = graphics.PreferredBackBufferHeight = 600; } /// <summary> /// Allows the game to perform any initialization it needs to before starting to run. /// This is where it can query for any required services and load any non-graphic /// related content. Calling base.Initialize will enumerate through any components /// and initialize them as well. /// </summary> protected override void Initialize() { // TODO: Add your initialization logic here 6 base.Initialize(); } /// <summary> /// LoadContent will be called once per game and is the place to load /// all of your content. /// </summary> protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // TODO: use this.Content to load your game content here sprite = Content.Load<Texture2D>("hero"); } /// <summary> /// UnloadContent will be called once per game and is the place to unload /// all content. /// </summary> protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } /// <summary> /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // TODO: Add your update logic here base.Update(gameTime); } /// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here // Координаты левого верхнего угла экрана Vector2 pos = new Vector2(0, 0); if (gameTime.TotalGameTime.TotalSeconds > 1) { // Координаты правого верхнего угла экрана pos = new Vector2(width - sprite.Width, 0); 7 } if (gameTime.TotalGameTime.TotalSeconds > 2) { pos = new Vector2(width - sprite.Width, height sprite.Height); } if (gameTime.TotalGameTime.TotalSeconds > 3) { pos = new Vector2(0, height - sprite.Height); } if (gameTime.TotalGameTime.TotalSeconds > 4) { pos = new Vector2(width / 2 - sprite.Width / 2, height/2 sprite.Height/2); } spriteBatch.Begin(); spriteBatch.Draw(sprite, pos, Color.White); spriteBatch.End(); base.Draw(gameTime); } } } 8