Иван Андреев
andreev. *****@***com
XNA для начинающих. Практика. Перемещение объектов в 2D пространстве
Целью данной работы является получение базовых навыков работы с 2D графикой и игровым временем.
Задание на работу:
Создать XNA приложение, установить размеры окна. Нарисовать в левом верхнем углу экрана спрайт произвольного размера (лучше установить достаточно большой размер). При прохождении некоторого количества времени (либо при нажатии на кнопку на клавиатуре) спрайт должен переместиться в правый верхний угол, затем в правый нижний угол, левый нижний угол и, наконец, в центр экрана, причем центр спрайта должен совпасть с центром экрана. Далее, можно либо зациклить перемещение, либо оставить спрайт в центре экрана.
Теоретическая часть:
Основы 2Д графики
Перед тем как перейти к работе следует вспомнить теоретические основы. В двумерной графике каждый пиксель характеризуется координатами и цветом. На экране монитора (а также в любом компьютерном изображении, например, картинке, созданной в Paint) началом координат является левый верхний угол, ось X направлена слева направо, а ось Y сверху вниз. Координаты пикселя отсчитываются от 0, соответственно левый верхний угол экрана имеет координаты {0,0}, а правый нижний – {MaxX-1, MaxY-1}, где MaxX, MaxY – разрешение экрана.
Для того чтобы установить разрешение окна в 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.
Вся работа по отображению изображений на экран осуществляется в рамках блоков SpriteBatch. Begin – SpriteBatch. End. Для собственно отображения изображений на экран нужно использовать метод SpriteBatch. Draw. Существуют большое количество перегруженных вариантов вызова данного метода, однако в рамках данной работы будет рассмотрен наиболее простой.
public void Draw ( Texture2D texture, Vector2 position, Color color ) |
texture – изображение, которое необходимо нарисовать на экране.
position – позиция левого верхнего угла изображения на экране.
color – цвет, используемый для смешивания перед выводом изображения на экран. Чаще всего, этот параметр будет принимать значение Color. White. В таком случае изображение будет иметь цвета, которые были заданы при создании изображения. Вы можете самостоятельно поэксперементировать с данным параметром.
Загрузка содержимого (контента) игры
Перед тем как работать с изображениями (или другими типами игрового содержимого: моделями, звуками, шрифтами и т. д.) необходимо загрузить их в игру при помощи ContentManager.
Для загрузки игрового содержимого в каркасе приложения предусмотрен специальный метод LoadContent.
Загрузка определенного типа содержимого осуществляется вызовом метода Content. Load<ТипСодержимого>(“НазваниеФайлаСодержимого”).
Например, работы с друмерными изображениями в 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. Это означает, что игра работает медленнее, чем требуется. Если IsRunningSlowly равняется true, то стоит задуматься над оптимизацией |
TotalGameTime | Игровое время, прошедшее с запуска игры |
TotalRealTime | Реальное время, прошедшее с запуска игры |
Для нас наиболее важными свойствами являются ElapsedGameTime и TotalGameTime. Они возвращают объект типа TimeSpan, который имеет множество различных свойств. Рассмотрим лишь наиболее важные:
Days | Целое количество дней |
Hours | Целое количество часов |
Milliseconds | Целое количество миллисекунд |
Minutes | Целое количество минут |
Seconds | Целое количество секунд |
Ticks | Целое количество системных «тиков» |
TotalDays | Вещественное значение дней |
TotalHours | Вещественное значение часов |
TotalMilliseconds | Вещественное значение миллисекунд |
TotalMinutes | Вещественное значение минут |
TotalSeconds | Вещественное значение секунд |
Обратите внимание на то, что свойства, название которых не начинается с Total, возвращают целое число, то есть, например, Days будет иметь значение 0 до тех пор, пока не пройдет целый день. В то же время значение TotalDays будет постоянно увеличиваться и по-прошествие дня примет значение 1.
Реализация:
В следующем листинге приведена простая реализация без зацикливания и обработки пользовательского ввода:
using System;
using System. Collections. Generic;
using System. Linq;
using Microsoft. Xna. Framework;
using Microsoft. Xna. Framework. Audio;
using Microsoft. Xna. Framework. Content;
using Microsoft. Xna. Framework. GamerServices;
using Microsoft. Xna. Framework. Graphics;
using Microsoft. Xna. Framework. Input;
using Microsoft. Xna. Framework. Media;
using Microsoft. Xna. ;
using 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
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);
}
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);
}
}
}


