Итак, будем идти от простого сложному, и для начала, просто создадим главное меню нашей программы. Очень надеюсь, что базовые знания по работе со спрайтами в XNA у Вас уже есть, хотя и постараюсь комментировать свой код поподробней. Итак, В главном меню у нас есть фоновый рисунок, и список пунктов меню, для примера это будет: заголовок "Меню:", пункты "НОВАЯ ИГРА", "ЗАГРУЗИТЬ", "НАСТРОЙКИ", "ВЫХОД". Что бы было трудней (а мы ведь трудностей не боимся) сделаем им выравнивание по левому краю экрана. Теперь надо решить один важный момент с фоновым изображением. Тут есть одна маленькая, но "противная" особенность. Так уж получилось ), но у производителей мониторов договориться о соотношении сторон коих договориться не получилось, и стандартного и универсального его нет. Чем это вызвано (особенностями производства, или тупо экономией на материалах - ведь при одинаковой диагонали, у не широкоформатного монитора площадь вроде больше), спорить не будем. По факту чаще всего встречаются: 9x16 (0,5625), 3x5(0.6), 5x8 (0,625), 2x3 (0.66), 3x4 (0.75), 4x5 (0.8). Как видно, разброс довольно большой, и к тому же, наиболее распространенные 9x16 и 3х4 в разных углах ринга. Вот и получается, угодишь одному - обидишь другого. Допустим, фоновый рисунок нам искажать никак нельзя. Так у нас персонажи, природа и т.д. Что делать? Есть несколько стандартных методов. 1. Черная полоса. Суть заключается в том, что мы ставим фоновый рисунок масштабируется с сохранением пропорций, так, что бы он по высоте и длине полностью влез в экран. При этом, если остаётся свободное пространство по бокам, или сверху или низу, оно остаётся черным. По сути, после очистки кадра в эту область памяти видеокарты просто не рисуют. Метод довольно устаревший, и ещё сейчас употребляется на мобильных платформах. 2. Универсальное изображение. Тут требуется хорошие руки у художника, так как надо создать фон, который будет хорошо смотреться на любом разрешении. и тут понятно, два варианта. • либо делать фоновое изображение в высоту (то есть для "квадратиков" 3х4) - основное смысловое изображение вмещается в формат 3х4, а всё, что больше, заполняется несмысловыми элементами - узорами, размытием или текстурой. • либо ориентироваться на ширину (анаморфированым 9х16) - основное смысловое изображение вмещается в формат 9х16, а всё, что больше - нижняя и верхняя граница, заполняется те ми же узорами, размытием или текстурой. Ну и как отдельный могу предложить ещё такой выход. В некоторых случаях фон можно заполнить просто узором ). Допустим, те же "обои на стене". А пункты меню как развешенные фотографии... просто, и со вкусом. Но тут главной, что бы даже при большом свободном месте у пользователей не начало рябить в глазах и не кидало трясти в эпилептическом приступе от ядовитой гаммы и гипнотических линий )! Мы сделаем второй вариант, а точнее будем подгонять по высоте. Тут же нам надо определиться, какого размера готовить рисунок фона. Можно ж и 800х600 попробовать поставить в современные 1920х1080, но не стоит ). Я взял как раз 1920х1080. Если что, сожмём. Так как это один спрайт, сильно вреда для fps не будет. Отдельным пунктом оговорю, что за размеры экрана я принимаю и размеры окна (ну или буфера) рисования, которое полной считается областью. Ведь если запускать приложение в оконном режиме, это разные вещи. Как фон я возьму мой старый рисунок, и попробую его на разных пропорциях экрана. Для удобства я сделан небольшую заготовку:
В неё и подогнал изображение для фона меню:
Для формата 3х4. Для формата 9х16.
То есть первое, это «урезанная» копия второго. Пункты меню остались неизменны относительно сторон. Что ж, с изображение готово, приступаем кодить.
Создаём пустой проект XNA 4. Экспортируем в него наш фон. Напомню, что ресурсы принято хранить в папке Content. Но в проекте XNA она представлена другой папкой - имя проекта + Content. Хотя у неё и Root Directory всё тот же «Content». Для экспорта просто щелкните правой кнопкой по вашей папке Content, и выберите Добавить->существующий элемент. Найдите свой рисунок фона, жмём ок, и вуаля, он в ресурсах.
Нам понадобятся переменные:
Код
Texture2D TextFON; //фоновое изображения для меню float ScaleFone = 1.0F; // масштаб фона. То есть отношение // высоты экрана к высоте рисунка. int DeltaXFon1 = 0; //сдвиг изображения фона от 0 влево. // так, что бы оно оказалось в центре
В функцию LoadContent() добавляем:
Код
//загрузка фонового изображения для меню TextFON = Content.Load<Texture2D>("ForGame");
В функцию Draw() добавим:
Код
spriteBatch.Begin(); spriteBatch.Draw(TextFON, Vector2.Zero, Color.White); // TextFON - наша текстура фона // Vector2.Zero - нулевой вектор (0,0) // Color.White - добавочный цвет. белый сто бы без искажения изображения spriteBatch.End();
этого достаточно, что бы приложение начало рисовать наш фон хоть как то ).
Начинаем доработку. Для начала добьемся что бы фон пропорционально масштабировался относительно экрана. Итак, высота рисунка нам известна и неизменна, это наши 600 pxs, или TextFON.Height. Берём за 100%, или точнее, за 1. Высота экрана определяется пользователем (хотя мы пока будем менять её вручную в коде). Для этого, в конструкторе Game1() добавим:
Код
graphics.PreferredBackBufferHeight = 600; //высота экрана graphics.PreferredBackBufferWidth = 800; //ширина экрана // параметр, отвечающий за полноэкранный режим //graphics.IsFullScreen = true; //пригодиться потом
Тогда, находим нашу пропорцию как высота экрана делённое на высоту изображения. И вроде бы код должен быть простой строчкой
Код
ScaleFone = graphics.GraphicsDevice.DisplayMode.Height / TextFON.Height; //graphics.PreferredBackBufferHeight высота экрана //TextFON.Height - высота рисунка фона
Но не тут то было. Дело в том, что оба операнда – целочисленные int. Поэтому по правилам Net Framework, результат то же должен быть целочисленным. То есть вычисляется то дробное, но оно округляется до целого. Потом он конечно приводиться опять в формат дробного float, но уже ж поздно. Исправляется это просто явным переводом одного из операндов во float. В итоге у нас:
Для примера – 800/1080 = 0.555555582. Это наш коэфицент, и наше изображение нужно будет немного сжать, умножив на это число. Тогда код рисования измениться на
Код
spriteBatch.Begin(); spriteBatch.Draw(TextFON, Vector2.Zero, null, Color.White, 0.0F, Vector2.Zero, ScaleFone, SpriteEffects.None, 1.0F); // null - что бы рисовать текстуру полностью, как она есть // 0.0F - градус поворота в радианах // ScaleFone - наш масштаб // SpriteEffects.None - никак не отзеркаливаем // 1.0F - рисоване на самом заднем плане spriteBatch.End();
Что ж, уже лучше. Теперь нужно определиться со сдвигом. Тут у нас есть две известных – почти неизменная (не учитывая масштаб) ширина картинки фона, и ширина экрана. Сдвиг находим как
Код
DeltaXFon1 = (graphics.PreferredBackBufferWidth - (int)(TextFON.Width * ScaleFone))/2; // на человеческом это значит // ширина экрана минус масштабированная ширина изображение фона пополам // если не делить на 2, мы выровним картинку по правому краю, а не по центру
А в Draw() изменим на:
Код
spriteBatch.Begin(); spriteBatch.Draw(TextFON, new Vector2(DeltaXFon1, 0), null, Color.White, 0.0F, Vector2.Zero, ScaleFone, SpriteEffects.None, 1.0F); // null - что бы рисовать текстуру полностью, как она есть // 0.0F - градус поворота в радианах // ScaleFone - наш масштаб // SpriteEffects.None - никак не отзеркаливаем // 1.0F - рисоване на самом заднем плане spriteBatch.End();
Итог, то что надо:
Можно применить и другой подход. В NXA есть метод рисования Draw() с двумя прямоугольниками (Rectangle). Первый отвечает за то, где рисуется спрайт. А второй, какая часть текстуры. Рискнём сделать и фон так. Добавляем три переменных:
int DeltaXFon2 = 0; //сдвиг изображения фона от 0 влево.
В методе LoadContent() добавим определение:
Код
RectWindFon = new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
Как видно, область рисования это весь экран от левого верхнего угла или начаола координат 0,0… до нижнего правого …graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight. Если возникли вопросы, почитайте о системе координат в XNA. Теперь надо разобраться со вторым. Ведь если мы сейчас заменим функцию рисования на
null – на месте второго аргумента означает что текстура рисуется с точки (0.0) до своего края (ширина.высота). Какое бы разрешение мы не ставили, картинка будет растягиваться на всю область, а пропорции исказятся. Как пример:
Что бы это исправить, мы начнем рисовать текстуру в спрайте не с самого левого края, а чуть дальше (конечно пропорционально), как бы обрезав часть изображения. Сколько нужно обрезать, сейчас вычислим. Допустим, у нас та же текстура 1920х1080, а экран 800х600. тогда 1080 высоты рисунка это 600 точек экрана. Из пропорции определим видимую ширины.
1080 – 600 Х – 800
Тогда Х – 1080*800/600 = 1440. Из 1920 pxs нам нужно 1440. 480 лишние. Отступ от 0 по х будет 480/2 = 240. А справа, - (240 + 240). Почему +240? Так ведь мы ж прибавили к левому краю 240. Тут их надо и компенсировать.
Вот теперь всё как надо. Вот полный код программы:
Код
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;
namespace ExampleMenu { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch;
Texture2D TextFON; //фоновое изображения для меню float ScaleFone = 1.0F; // масштаб фона. То есть отношение // высоты экрана к высоте рисунка. int DeltaXFon1 = 0; //сдвиг изображения фона от 0 влево. int DeltaXFon2 = 0; //сдвиг изображения фона от 0 влево. // так, что бы оно оказалось в центре Rectangle RectWindFon; //область экрана для рисования спрайта Rectangle RectImgFon; //область рисования текстуры
Vector2 PozitionFon;
public Game1() { graphics = new GraphicsDeviceManager(this); //высота экрана graphics.PreferredBackBufferHeight = 600; //ширина экрана graphics.PreferredBackBufferWidth = 800; //параметр, отвечающий за полноэкранный режим //graphics.IsFullScreen = true; Content.RootDirectory = "Content"; }
protected override void Initialize() { // TODO: Add your initialization logic here base.Initialize(); }
protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); //загрузка фонового изображения для меню TextFON = Content.Load<Texture2D>("ForGame");
ScaleFone = (float)graphics.PreferredBackBufferHeight / TextFON.Height; //graphics.PreferredBackBufferHeight высота экрана //TextFON.Height - высота риунка фона DeltaXFon1 = (graphics.PreferredBackBufferWidth - (int)(TextFON.Width * ScaleFone)) / 2; // на человеческом это значит // ширина экрана минус масштабированная ширина изображение фона пополам // если не делить на 2, мы выровним картинку по правому краю, а так по центру
RectWindFon = new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight); RectImgFon = new Rectangle((int)(DeltaXFon2 / 2), 0, (int)(TextFON.Width - DeltaXFon2), TextFON.Height); }
protected override void UnloadContent() { // TODO: Unload any non ContentManager content here }
protected override void Update(GameTime gameTime) { // Allows the game to exit if ((GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)||(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)) this.Exit(); // TODO: Add your update logic here base.Update(gameTime); }
// TODO: Add your drawing code here spriteBatch.Begin(); //ВАРИАНТ 1 spriteBatch.Draw(TextFON, new Vector2(DeltaXFon1, 0), null, Color.White, 0.0F, Vector2.Zero, ScaleFone, SpriteEffects.None, 1.0F); // null - что бы рисовать текстуру полностью, как она есть // 0.0F - градус поворота в радианах // ScaleFone - наш масштаб // SpriteEffects.None - никак не отзеркаливаем // 1.0F - рисоване на самом заднем плане
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:
Игровые объявления и предложения:
Если вас заинтересовал материал «Урок по созданию главного меню на XNA», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела.
Предлагаются такие схожие материалы:
Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.
Наконец у меня появились последователи =). А вообще, если по поводу статьи, довольно неплохо. Правда, в принципе, первая часть статьи лишняя, т.к. работать с Ректанглом гораздо удобней и эффективней. Но, это не столько минус, сколько недочет. Я вот, когда писал свои первые статьи по XNA был еще мал, глуп и неопытен. Сейчас нахожу в каждой кучу косяков, неточностей и других минусов, но исправлять становится влом. Может выпущу новую серию, если будет время). В принципе, возможно мое мнение не совпадет с большинством, но четверку ты заслужил по праву.