Иван Андреев andreev.ia@gmail.com XNA для начинающих. Практика. Основы 2Д. Работа со SpriteBatch. Масштабирование, повороты, отражения Целью данной работы является освоение основного объекта для работы с двумерной графикой в XNA – класса SpriteBatch. В ходе работы мы рассморим различные преобразования над двумерным изображением, которые позволяет выполнять SpriteBatch. Задание на работу: Создать в графическом редакторе растровое изображение в одном из форматов, используемом в XNA Game Studio. Отобразить нарисованное изображение в центре экрана с оригинальным размером так, чтобы центр изображения совпадал с центром экрана. Отобразить нарисованное изображение в центре экрана с масштабом 2:1, то есть размер должен быть увеличен в два раза. Реализовать анимацию изменения резмера изображения (увеличение до определенного размера, затем уменьшение. Коэффициенты масштабирования должны изменяться от 1/2 до 2) Реализовать эффект поворота изображения относительно: А. Левого верхнего угла изображения Б. Центра изображения В. Правого нижнего угла Реализовать отражение изображения в горизонтальном и вертикальном направлениях. Для того чтобы выполнить поставленную задачу, нужно сначала вспомнить основные этапы работы. Все рисование с использованием SpriteBatch осуществляется в «блоках», отделенных вызовами методов Begin и End. В рамках одного такого «блока» может производиться произвольное количество рисований. Стоит заметить, что, с точки зрения, производительности следует, по возможности, создавать как можно меньше отдельных «блоков». Метод Begin является перегруженным и может иметь ряд параметров. Для версии XNA Framework 3.1 имеются 4 возможных варианта вызова данного метода: 1 Рассмотрим роль каждого из параметров на примере вызова метода, принимающего максимальное количество параметров: public void Begin ( SpriteBlendMode blendMode, SpriteSortMode sortMode, SaveStateMode stateMode, Matrix transformMatrix ) blendMode – отвечает за режим блендинга (смешивания) цветов. Может принимать следующие значения: None, Additive, AlphaBlend. Более подробно о режимах смешивания далее. sortMode – задает режим сортировки спрайтов при отображении на экран. Может принимать следующие значения: BackToFront, Deferred, FrontToBack, Immediate, Texture. BackToFront Deferred (значени по умолчанию) FrontToBack Immediate Texture Аналогичен Deferred за тем исключением, что перед отрисовкой спрайты сортируются в порядке приближения к наблюдателю (то есть в порядке уменьшения глубины). В этом режиме спрайты не отображаются до того момента, когда будет вызван метод End. После этого все спрайты будут нарисованы в том порядке, в котором производились вызовы метода Draw Аналогичен Deferred за тем исключением, что перед отрисовкой спрайты сортируются в порядке отдаления от наблюдателю (то есть в порядке увеличения глубины). Каждый спрайт рисуется в момент вызова метода Draw. Режим Immediate работает быстрее, чем Deferred. Аналогичен Deferred, за тем исключением, что спрайты сортируются по текстурам (для увеличения быстродействия). Стоит применять в сценах с неперекрывающимися спрайтами stateMode – может принимать значения None или SaveState и отвечает за восстановление параметров графического устройства, установленных до вызова Begin. Если stateMode имеет значение SaveState, то значение этих параметров будет восстановлено после вызова метода End. Пока не стоит особенно беспокоиться об этом параметре, однако он имеет большое значение при работе с трехмерной графикой. Например, метод Begin по умолчанию отключает буфер глубины, что может очень плохо отразиться на итоговой картинке при работе с трехмерными объектами. С другой стороны, операции сохранения и восстановления значений параметров графического 2 устройства являются достаточно ресурсоемкими, так что, иногда может иметь смысл установить значение None для параметра stateMode, а затем самостоятельно выставить нужные значения для параметров графического устройства. transformMatrix – матрица трансформаций, которые должны быть применены ко всем объектам, обрабатываемым в рамках данного «блока». – матрица трансформаций, которые должны быть применены ко всем объектам, обрабатываемым в рамках данного «блока». К таким трансформациям относятся перенос, вращение, поворот. Теперь рассмотрим более подробно использование параметра blendMode. Как было упомянуто выше, он может принимать одно из следующих значений: None, Additive, AlphaBlend. Рассмотрим каждый из них на примере. Создадим два изображения с прозрачным фоном и сохраним их в формат PNG для того, чтобы информация о прозрачности сохранилась. Теперь создадим новое Windows Game приложение и загрузим в него подготовленные изображения. Texture2D image1; Texture2D image2; protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); image1 = Content.Load<Texture2D>("image1"); image2 = Content.Load<Texture2D>("image2"); // TODO: use this.Content to load your game content here } Теперь будем по очереди пробовать различные режимы смешивания и наблюдать за результатом. Будем рисовать спрайты так, чтобы они накладывались один на другой. Вариант 1 (SpriteBlendMode.None): protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); 3 // TODO: Add your drawing code here spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Deferred, SaveStateMode.None); spriteBatch.Draw(image1, new Rectangle(50, 20, 200, 200), Color.White); spriteBatch.Draw(image2, new Rectangle(100, 80, 250, 200), Color.White); spriteBatch.End(); base.Draw(gameTime); } Вариант 2 (SpriteBlendMode.AlphaBlend): protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.None); spriteBatch.Draw(image1, new Rectangle(50, 20, 200, 200), Color.White); spriteBatch.Draw(image2, new Rectangle(100, 80, 250, 200), Color.White); spriteBatch.End(); base.Draw(gameTime); } Вариант 2 (SpriteBlendMode.Additive): protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Deferred, SaveStateMode.None); spriteBatch.Draw(image1, new Rectangle(50, 20, 200, 200), Color.White); spriteBatch.Draw(image2, new Rectangle(100, 80, 250, 200), Color.White); spriteBatch.End(); base.Draw(gameTime); } 4 5 Не вдаваясь в подоробности, можно заметить, что при использовании режима None прозрачность не учитывается, а при использовании режима Additive цвета смешиваются без учета порядка вызовов метода Draw. Наиболее часто используемым является режим AlphaBlend, при котором учитывается порядок вывода спрайтов и прозрачность. Весь код примера приведен в следующем листинге: 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 BlendModeTest { /// <summary> /// This is the main type for your game /// </summary> public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Texture2D image1; Texture2D image2; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferWidth = 400; graphics.PreferredBackBufferHeight = 300; } /// <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> 6 /// 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); image1 = Content.Load<Texture2D>("image1"); image2 = Content.Load<Texture2D>("image2"); // TODO: use this.Content to load your game content here } /// <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 spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Deferred, SaveStateMode.None); spriteBatch.Draw(image1, new Rectangle(50, 20, 200, 200), Color.White); spriteBatch.Draw(image2, new Rectangle(100, 80, 250, 200), Color.White); spriteBatch.End(); base.Draw(gameTime); } 7 } } Рассмотрим теперь влияние параметра transformMatrix на итоговое изображение. Этот параметр позволяет задать метрицу трансформации для всех спрайтов, обрабытываемых внутру блока Begin End. Это может быть очень полезно. Несмотря на то, что матрицы в данном курсе будут обсуждаться значительно позже, стоит привести пример использования данного параметра. Для того чтобы уменьшить все спрайты в два раза можно использовать следующий код (есть и другой способ масштабирования спрайтов, который будет обсуждаться далее): float scale = 0.5f; spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None, Matrix.CreateScale(scale)); spriteBatch.Draw(image1, new Rectangle(50, 20, 200, 200), Color.White); spriteBatch.Draw(image2, new Rectangle(100, 80, 250, 200), Color.White); spriteBatch.End(); В данном примере переменная scale является коэффициентом масштабирования, а матрица трансформации создается при помощи статического метода Matrix.CreateScale, который принимает в качестве параметра коэффициент масштабирования и создает матрицу масштабирования. При помощи матрицы трансформации можно также масштабировать спрайты с различными коэффициентами вдоль каждой оси, например, следующий код сжимает изображение вдвое только по вертиками. float scaleX = 1; float scaleY = 0.5f; spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None, Matrix.CreateScale(scaleX, scaleY, 1)); spriteBatch.Draw(image1, new Rectangle(50, 20, 200, 200), Color.White); spriteBatch.Draw(image2, new Rectangle(100, 80, 250, 200), Color.White); //spriteBatch.Draw(image1, new Rectangle(50, 20, 200, 200), null, Color.White, 0, Vector2.Zero, SpriteEffects.None, 0); 8 //spriteBatch.Draw(image2, new Rectangle(100, 80, 250, 200), null, Color.White, 0, Vector2.Zero, SpriteEffects.None, 0.5f); //spriteBatch.Draw(image3, new Rectangle(100, 80, 250, 200), null, Color.White, 0, Vector2.Zero, SpriteEffects.None, 1); spriteBatch.End(); 9