Pull to refresh

Microsoft XNA: Арканоид шаг за шагом

Reading time9 min
Views28K
Несколько дней назад, бороздя просторы великого и могучего Интернета, наткнулся на Microsoft XNA Studio. Не то чтобы услышал об этом фреймворке в первый раз, но все предыдущие разы как-то проходил мимо, времени разбираться не было совершенно.
В этот раз что-то меня дернуло покопаться поглубже. Справедливо рассудив что для знакомства с библиотекой лучшего метода чем реализовать что нибудь на нем нет, а также имея в распоряжении свободный вечер, решил написать что нибудь простенькое, например любимый мною с детства Arkanoid (Brick Out), не корысти ради, а ознакомления для.

Это моя первая статья на хабре, убедительно прошу ногами не пинать

Смысл данной статьи показать как легко можно начать создавать свои игры при помощи XNA и, собственно, дать стимул для дальнейшего изучения этой платформы. Посему, на полноценную игру не претендует, графика — никакая, как и художник из меня. Да и физика могла бы быть пореалистичнее, но это уже выходит за рамки данной статьи, уверен что интересующиеся разработкой игр найдут немало достойных (и не очень) материалов в сети.

Исходники можно скачать тут



Итак, что же такое Microsoft XNA?

Microsoft XNA это набор инструментов и библиотека для разработки мультиплатформенных 2D и 3D игр в управляемой среде Microsoft Managed Runtime Environment. Поддерживаются платформы Windows, Microsoft Xbox 360 и Microsoft Zune. Теоретически писать можно на любом .Net языке, в любой IDE, но оффициально поддерживаются только C# и XNA Game Studio Express и все версии Visual Studio 2005 и выше. XNA также дает возможность портировать игры на поддерживаемые платформы с минимальными изменениями.

Последняя версия на момент написания статьти – Microsoft XNA Game Studio 3.1 (73.2 MB)

Создание проекта



Создадим новый проект – XNA Game Studio 3.1 – Windows Game (3.1)

Photobucket

Мастер создаст скелет игры:



Самый большой интерес для нас представляет файл Game1.cs, в котором определен класс Game1, наследованный от Microsoft.Xna.Framework.Game, где мы и будем разрабатывать нашу игру.
В классе Game1 переопределены следующие методы Game:

void Initialize() – Вызывается единожды, для инициализации ресурсов до начала игры
void LoadContent() – Вызывается единожды, используется для загрузки контента (спрайты и т.д.)
void UnloadContent() – Вызывается единожды, используется для выгрузки контента
void Update(GameTime gameTime) – В этом методе реализуется собственно логика игры, обработка коллизий, обработка событий клавиатуры или джойстика, проигрывание аудио и т.д.
void Draw(GameTime gameTime) – Вызывается для прорисовки игрового поля.

На данный момент скомпилированная игра выглядит вот так



Добавление контента



Добавим игровые ресурсы, в данном случае картинки фона кирпича, ракетки и мячика – Content (Right Click) -> Add -> Existing Item…



Обратите внимание на свойство Asset Name, его мы используем для создания обьекта Texture2D необходимого для дальнейшей анимации.

Рисуем фон игрового поля



Загрузим изображение для фона игрового поля:

  1. private Rectangle _viewPortRectangle; // Границы игрового поля
  2. private Texture2D _background; // Фон игрового поля
  3.  
  4. protected override void LoadContent()
  5. {
  6.   <... skip ...>
  7.   // Границы игрового поля
  8.   _viewPortRectangle = new Rectangle(0, 0,
  9.   graphics.GraphicsDevice.Viewport.Width,
  10.   graphics.GraphicsDevice.Viewport.Height);
  11.  
  12.   _background = Content.Load<Texture2D>(@"background");
  13.   <... skip ...>
  14. }
* This source code was highlighted with Source Code Highlighter.


Отрисовка фона

  1. protected override void Draw(GameTime gameTime)
  2. {
  3.   GraphicsDevice.Clear(Color.CornflowerBlue);
  4.  
  5.   spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
  6.  
  7.   // Рисуем фон
  8.   spriteBatch.Draw(_background, _viewPortRectangle, Color.White);
  9.  
  10.   spriteBatch.End();
  11.  
  12.   base.Draw(gameTime);
  13. }
* This source code was highlighted with Source Code Highlighter.


Метод SpriteBatch.Begin подготавливает графическое устройство к отрисовке спрайтов, SpriteBatch.End завершает процесс отрисовки и возвращает устройство к начальному состоянию. Все методы SpriteBatch.Draw должны быть заключены в SpriteBatch.Begin — SpriteBatch.End.

Создание игрового обьекта



Создадим класс GameObject инкапсулирующий любой из наших игровых обьектов:

  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Graphics;
  3.  
  4. namespace Arkanoid
  5. {
  6.   public class GameObject
  7.   {
  8.     public Texture2D Sprite { get; set; } // Спрайт
  9.  
  10.     public Vector2 Position; // Положение
  11.     public Vector2 Velocity; // Скорость
  12.     public int Width { get { return Sprite.Width; } } // Ширина
  13.     public int Height { get { return Sprite.Height; } } // Высота
  14.     public bool IsAlive { get; set; } // Жив ли обьект
  15.     public Rectangle Bounds // Границы обьекта
  16.     {
  17.       get
  18.       {
  19.         return new Rectangle((int)Position.X, (int)Position.Y, Width, Height);
  20.       }
  21.     }
  22.  
  23.     // Разворачивание движения по горизонтальной оси
  24.     public void ReflectHorizontal()
  25.     {
  26.       Velocity.Y = -Velocity.Y;
  27.     }
  28.  
  29.     // Разворачивание движения по вертикальной оси
  30.     public void ReflectVertical()
  31.     {
  32.       Velocity.X = -Velocity.X;
  33.     }
  34.     
  35.     public GameObject(Texture2D sprite)
  36.     {
  37.       Sprite = sprite;
  38.       IsAlive = true;
  39.       Position = Vector2.Zero;
  40.       Velocity = Vector2.Zero;
  41.     }
  42.   }
  43. }
* This source code was highlighted with Source Code Highlighter.


Отрисовка и анимация ракетки



Сначала создадим обьект представляющий ракетку и расположим его в середине игрового поля чуть повыше от его нижнего края

  1. private GameObject _paddle; // Ракетка
  2.  
  3. protected override void LoadContent()
  4. {
  5.   <... skip ...>
  6.  
  7.   // Создание ракетки, начальное положение в середине игрового поля, повыше нижнего края
  8.   _paddle = new GameObject(Content.Load<Texture2D>(@"paddle"));
  9.   _paddle.Position = new Vector2((_viewPortRectangle.Width - _paddle.Width) / 2,
  10.                   _viewPortRectangle.Height - _paddle.Height - 20);
  11.                  
  12.   <... skip ...>
  13. }
* This source code was highlighted with Source Code Highlighter.


Отрисовка ракетки на экране

  1. protected override void Draw(GameTime gameTime)
  2. {
  3.   <... skip ...>
  4.  
  5.   spriteBatch.Draw(_paddle.Sprite, _paddle.Position, Color.White);
  6.  
  7.   <... skip ...>
  8. }
* This source code was highlighted with Source Code Highlighter.


На данном этапе, если скомпилировать приложение, получим что-то вроде этого:



Неплхо было бы заставить реагировать ракетку на нажатие клавиш, для этого добавим в метод Update следующий код

  1. protected override void Update(GameTime gameTime)
  2. {
  3.   <... skip ...>
  4.  
  5.   KeyboardState keyboardState = Keyboard.GetState();
  6.  
  7.   // Двигаем ракетку вправо
  8.   if (keyboardState.IsKeyDown(Keys.Right))
  9.    _paddle.Position.X += 6f;
  10.  
  11.   // Двигаем ракетку влево
  12.   if (keyboardState.IsKeyDown(Keys.Left))
  13.    _paddle.Position.X -= 6f;
  14.  
  15.   // Ограничиваем движение ракетки игровым полем
  16.   _paddle.Position.X = MathHelper.Clamp(_paddle.Position.X, 0, _viewPortRectangle.Width - _paddle.Width);
  17.  
  18.   <... skip ...>
  19. }
* This source code was highlighted with Source Code Highlighter.


Отрисовка кирпичей



Создадим массив GameObject представляющий кирпичи которые собственно и будем разбивать

  1. private int _brickPaneWidth = 10; // Сколько кипричей рисовать в ширину
  2. private int _brickPaneHeight = 5; // Сколько кипричей рисовать в высоту
  3. private Texture2D _brickSprite; // Спрайт кирпича
  4. private GameObject[,] _bricks; // Массив кирпичей
* This source code was highlighted with Source Code Highlighter.


Добавим следующий код в метод LoadContent()

  1. protected override void LoadContent()
  2. {
  3.   <... skip ...>
  4.  
  5.   // Создание массива кирпичей
  6.   _brickSprite = Content.Load<Texture2D>(@"brick");
  7.   _bricks = new GameObject[_brickPaneWidth,_brickPaneHeight];
  8.  
  9.   for (int i = 0; i < _brickPaneWidth; i++)
  10.   {
  11.    for (int j = 0; j < _brickPaneHeight; j++)
  12.    {
  13.      _bricks[i, j] = new GameObject(_brickSprite)
  14.      {
  15.       Position = new Vector2(i * 55 + 120, j * 25 + 100)
  16.      };
  17.    }
  18.   }
  19.  
  20.   <... skip ...>
  21. }
* This source code was highlighted with Source Code Highlighter.


Отрисовка массива кирпичей, отисовка производится если кирпич “жив”, т.е. не разбит мячем

  1. protected override void Draw(GameTime gameTime)
  2. {
  3.   <... skip ...>
  4.  
  5.   // Рисуем кирпичи
  6.   foreach (var brick in _bricks)
  7.    if (brick.IsAlive)
  8.      spriteBatch.Draw(brick.Sprite, brick.Position, Color.White);
  9.  
  10.   <... skip ...>
  11. }
* This source code was highlighted with Source Code Highlighter.


На данном этапе игровое поле выглядит следующим образом



Отрисовка мячика



Создаем обьект мячика

  1. private GameObject _ball; // Мячик
  2.  
  3. protected override void LoadContent()
  4. {
  5.   <... skip ...>
  6.  
  7.   // Создание мячика, начальное положение в середине на ракетке,
  8.   // начальное направление - вправо, вверх
  9.   _ball = new GameObject(Content.Load<Texture2D>(@"ball"));
  10.   _ball.Position = new Vector2((_viewPortRectangle.Width - _ball.Width) / 2,
  11.             _viewPortRectangle.Height - _paddle.Height - _ball.Height - 20);
  12.   _ball.Velocity = new Vector2(3,-3);
  13.   <... skip ...>
  14. }
* This source code was highlighted with Source Code Highlighter.


Для анимации мячика добавим новый метод UpdateBall(), и его вызов в в метод Update(). Данный метод нам понадобится в дальнейшем для обработки столкновений мячика с кирпичами и ракеткой

  1. private void UpdateBall()
  2. {
  3.   _ball.Position += _ball.Velocity;
  4. }
  5.  
  6. protected override void Update(GameTime gameTime)
  7. {
  8.   <... skip ...>
  9.  
  10.   // Двигаем мячик
  11.   UpdateBall();
  12.  
  13.   <... skip ...>
  14. }
* This source code was highlighted with Source Code Highlighter.


Для отрисовки мячика добавим следующий код в метод Draw()

  1. protected override void Draw(GameTime gameTime)
  2. {
  3.   <... skip ...>
  4.  
  5.   // Рисуем мячик
  6.   spriteBatch.Draw(_ball.Sprite, _ball.Position, Color.White);
  7.  
  8.   <... skip ...>
  9. }
* This source code was highlighted with Source Code Highlighter.


На данный момент, мы имеем почти полностью готовое игровое поле, но без обработки столкновений мячик сразу же вылетает за пределы игрового поля. Добавим обработку столкновений мячика с игровым полем, кирпичами и ракеткой

Обработка столкновений



Создадим новый метод определяющий место столкновения обьектов и меняющий направление полета мяча.

  1. // Определение стороны столкновения и отражение направления полета мячика
  2. public void Collide(GameObject gameObject, Rectangle rect2)
  3. {
  4.  
  5.   // Обьект столкнулся сверху или снизу, отражаем направление полета по горизонтали
  6.   if (rect2.Left <= gameObject.Bounds.Center.X && gameObject.Bounds.Center.X <= rect2.Right)
  7.    gameObject.ReflectHorizontal();
  8.    
  9.   // Обьект столкнулся слева или справа, отражаем направление полета по вертикали
  10.   else if (rect2.Top <= gameObject.Bounds.Center.Y && gameObject.Bounds.Center.Y <= rect2.Bottom)
  11.    gameObject.ReflectVertical();
  12. }
* This source code was highlighted with Source Code Highlighter.


Добавим следующий код в метод UpdateBall()

  1. private void UpdateBall()
  2. {
  3.   // Будущее положение мяча, нужно для предотвращения "залипания" мяча на поверхности обьекта
  4.   Rectangle nextRect = new Rectangle((int)(_ball.Position.X + _ball.Velocity.X),
  5.     (int)(_ball.Position.Y + _ball.Velocity.Y),
  6.     _ball.Width, _ball.Height);
  7.  
  8.   // Столкновение с верхним краем игрового поля
  9.   if (nextRect.Y <= 0)
  10.     _ball.ReflectHorizontal();
  11.  
  12.   // При сталкивании мяча с нижним краем игрового поля, мячик "умирает"
  13.   if (nextRect.Y >= _viewPortRectangle.Height - nextRect.Height)
  14.   {
  15.     _ball.IsAlive = false;
  16.   }
  17.  
  18.   // Столкновение мячика с левым или правым краем игрового поля
  19.   if ((nextRect.X >= _viewPortRectangle.Width - nextRect.Width) || nextRect.X <= 0)
  20.   {
  21.     _ball.ReflectVertical();
  22.   }
  23.  
  24.   // Столкновение мячика с ракеткой
  25.   if (nextRect.Intersects(_paddle.Bounds))
  26.     Collide(_ball, _paddle.Bounds);
  27.  
  28.   // Столкновение мячика с кирпичами
  29.   foreach (var brick in _bricks)
  30.   {
  31.     if (nextRect.Intersects(brick.Bounds) && brick.IsAlive)
  32.     {
  33.       brick.IsAlive = false;
  34.       Collide(_ball, brick.Bounds);
  35.     }
  36.   }
  37.  
  38.   _ball.Position += _ball.Velocity;
  39. }
* This source code was highlighted with Source Code Highlighter.


Итак, что получилось



Конечно физика в игре, мягко говоря, никакая, мячик иногда залипает при столкновении с движущейся ракеткой. Но, повторюсь, смысл данной статьи знакомство со средой Microsoft XNA Game Studio, и, надо сказать, она отлично справляется с рутиной, освобождая время разработчика для фокусировки внимания на логике игры.

Исходники можно скачать тут

Дополнительные ресурсы:
creators.xna.com
XNAWiki

PS. Большое спасибо ivv за приглашение.
PPS. Спасибо за карму, перенес в XNA

Текст подготовлен в ХабраРедакторе
Tags:
Hubs:
Total votes 112: ↑91 and ↓21+70
Comments53

Articles