Среда, 11 Декабря 2019, 23:08

Приветствую Вас Гость

Меню сайта
Категории каталога
Создание игр [323]
Статьи об общих понятиях связанных с созданием игр.
Программирование [74]
Гайды по программированию на разных ЯП.
Движки и Гейммейкеры [126]
Статьи о программах для создания игр, уроки и описания.
Софт [28]
Различные программы, в том числе в помощь игроделам.
2D-графика [12]
Уроки по рисованию, растр, пиксель-арт, создание спрайтов и пр.
3D-графика [11]
Уроки по моделированию, ландшафт, модели, текстурирование и пр.
Моддинг игр [5]
Модификация компьютерных игр, создание дополнений, перевод, хакинг.
Игры [130]
Статьи об играх, в том числе и сделанных на гейммейкерах.
Разное [84]
Статьи, которые не вошли в определённые разделы.
Наш опрос
Вам хотелось бы стать модератором GcUp.ru?
Всего ответов: 2098
Главная » Статьи » Создание игр

Урок по созданию главного меню на XNA
Итак, будем идти от простого сложному, и для начала, просто создадим главное меню нашей программы. Очень надеюсь, что базовые знания по работе со спрайтами в 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. В итоге у нас:
Код
ScaleFone = (float)graphics.PreferredBackBufferHeight / TextFON.Height;

Для примера – 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();




Что ж, уже лучше. Теперь нужно определиться со сдвигом. Тут у нас есть две известных – почти smile неизменная (не учитывая масштаб) ширина картинки фона, и ширина экрана. Сдвиг находим как
Код
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). Первый отвечает за то, где рисуется спрайт. А второй, какая часть текстуры. Рискнём сделать и фон так. Добавляем три переменных:
Код
Rectangle RectWindFon; //область экрана для рисования спрайта
Rectangle RectImgFon; //область рисования текстуры

int DeltaXFon2 = 0; //сдвиг изображения фона от 0 влево.

В методе LoadContent() добавим определение:
Код
RectWindFon = new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);

Как видно, область рисования это весь экран от левого верхнего угла или начаола координат 0,0… до нижнего правого …graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight. Если возникли вопросы, почитайте о системе координат в XNA.
Теперь надо разобраться со вторым. Ведь если мы сейчас заменим функцию рисования на
Код
spriteBatch.Begin();
spriteBatch.Draw(TextFON, RectWindFon, null, Color.White);
spriteBatch.End();

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. Тут их надо и компенсировать.

Теперь переведём это в формулы и пропишем в коде:
Код
DeltaXFon2 = TextFON.Width - (int)((float)graphics.PreferredBackBufferWidth / graphics.PreferredBackBufferHeight * TextFON.Height);
//1920 - 800/600*1080 = 480
RectImgFon = new Rectangle((int)(DeltaXFon2 / 2), 0, (int)(TextFON.Width - DeltaXFon2), TextFON.Height);
//(240,0,1440,1080)


Вот теперь всё как надо. Вот полный код программы:
Код
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, мы выровним картинку по правому краю, а так по центру

  DeltaXFon2 = TextFON.Width - (int)((float)graphics.PreferredBackBufferWidth / graphics.PreferredBackBufferHeight * TextFON.Height);

  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);
  }

  protected override void Draw(GameTime gameTime)
  {
  GraphicsDevice.Clear(Color.CornflowerBlue);

  // 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 - рисоване на самом заднем плане

  //ВАРИАНТ 2
  //spriteBatch.Draw(TextFON, RectWindFon, RectImgFon, Color.White);
  spriteBatch.End();

  base.Draw(gameTime);
  }
  }
}


Пока с картинкой разобрались. далее работа с пунктами меню.
Категория: Создание игр | Добавил: VolkodavAD (30 Июня 2013) | Автор: Александр Дениссенко
Просмотров: 9450 | Комментарии: 7 | Рейтинг: 3.7/9 |
Теги: ширина, урок, разрешение, XNA Game Studio, интерфейс, Главного меню, Программирование, xna, меню
Дополнительные опции:
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:

Игровые объявления и предложения:
Если вас заинтересовал материал «Урок по созданию главного меню на XNA», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела. Предлагаются такие схожие материалы: Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.

Всего комментариев: 7
+1-
7 Stalker_Shooter   (17 Июля 2013 10:01)
Stalker_ShooterНаконец у меня появились последователи =). А вообще, если по поводу статьи, довольно неплохо. Правда, в принципе, первая часть статьи лишняя, т.к. работать с Ректанглом гораздо удобней и эффективней. Но, это не столько минус, сколько недочет.
Я вот, когда писал свои первые статьи по XNA был еще мал, глуп и неопытен. Сейчас нахожу в каждой кучу косяков, неточностей и других минусов, но исправлять становится влом. Может выпущу новую серию, если будет время). В принципе, возможно мое мнение не совпадет с большинством, но четверку ты заслужил по праву. happy

+-4-
5 GameDev2   (02 Июля 2013 18:44)
Круто! Есть ли такой же урок для Game Maker'а - на GML?

+3-
6 TLT   (06 Июля 2013 04:46)
TLTМожно очень просто сделать, было бы желание. Сделай титульник на основе объектов, привязанных к относительным координатам.

+0-
4 Undead   (01 Июля 2013 21:32)
UndeadУрок плохой, но лучше большинства того, что здесь уже есть.

+-3-
2 Amri   (01 Июля 2013 15:32)
AmriСпасибо кэп.

+-3-
3 Alastar   (01 Июля 2013 19:01)
AlastarСлишком толсто, аж жир потек.

+2-
1 Alastar   (01 Июля 2013 13:21)
AlastarКрасиво получилось

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск по сайту
10 случ. движков
  • EMG Studio
  • uranEngine
  • Atomic
  • Vizard
  • Blender
  • FIFE
  • Mokoi Gaming
  • Open RPG Maker
  • Sparrow
  • Cafu
  • Друзья сайта
    Игровой форум GFAQ.ru Перевод консольных игр
    Все права сохранены. GcUp.ru © 2008-2019 Рейтинг