В этом небольшом уроке я покажу вам, как просто создавать игры на XNA Game Studio, итак приступим (Исходники можно скачать тут). Итак, что же такое 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 также дает возможность портировать игры на поддерживаемые платформы с минимальными изменениями.
Создание проекта
Создадим новый проект – XNA Game Studio 3.1 – Windows Game (3.1)
Мастер создаст скелет игры:
Самый большой интерес для нас представляет файл 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 необходимого для дальнейшей анимации.
Рисуем фон игрового поля
Загрузим изображение для фона игрового поля:
Code
private Rectangle _viewPortRectangle; // Границы игрового поля
private Texture2D _background; // Фон игрового поля
protected override void LoadContent()
{
<... skip ...>
// Границы игрового поля
_viewPortRectangle = new Rectangle(0, 0,
graphics.GraphicsDevice.Viewport.Width,
graphics.GraphicsDevice.Viewport.Height);
_background = Content.Load<Texture2D>(@"background");
<... skip ...>
}
Отрисовка фона
Code
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
// Рисуем фон
spriteBatch.Draw(_background, _viewPortRectangle, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
Метод SpriteBatch.Begin подготавливает графическое устройство к отрисовке спрайтов, SpriteBatch.End завершает процесс отрисовки и возвращает устройство к начальному состоянию. Все методы SpriteBatch.Draw должны быть заключены в SpriteBatch.Begin — SpriteBatch.End. Создание игрового обьекта
Создадим класс GameObject инкапсулирующий любой из наших игровых обьектов:
Code
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Arkanoid
{
public class GameObject
{
public Texture2D Sprite { get; set; } // Спрайт
public Vector2 Position; // Положение
public Vector2 Velocity; // Скорость
public int Width { get { return Sprite.Width; } } // Ширина
public int Height { get { return Sprite.Height; } } // Высота
public bool IsAlive { get; set; } // Жив ли обьект
public Rectangle Bounds // Границы обьекта
{
get
{
return new Rectangle((int)Position.X, (int)Position.Y, Width, Height);
}
}
// Разворачивание движения по горизонтальной оси
public void ReflectHorizontal()
{
Velocity.Y = -Velocity.Y;
}
// Разворачивание движения по вертикальной оси
public void ReflectVertical()
{
Velocity.X = -Velocity.X;
}
public GameObject(Texture2D sprite)
{
Sprite = sprite;
IsAlive = true;
Position = Vector2.Zero;
Velocity = Vector2.Zero;
}
}
}
Отрисовка и анимация ракетки Сначала создадим обьект представляющий ракетку и расположим его в середине игрового поля чуть повыше от его нижнего края
Code
private GameObject _paddle; // Ракетка
protected override void LoadContent()
{
<... skip ...>
// Создание ракетки, начальное положение в середине игрового поля, повыше нижнего края
_paddle = new GameObject(Content.Load<Texture2D>(@"paddle"));
_paddle.Position = new Vector2((_viewPortRectangle.Width - _paddle.Width) / 2,
_viewPortRectangle.Height - _paddle.Height - 20);
<... skip ...>
}
Отрисовка ракетки на экране
Code
protected override void Draw(GameTime gameTime)
{
<... skip ...>
spriteBatch.Draw(_paddle.Sprite, _paddle.Position, Color.White);
<... skip ...>
}
На данном этапе, если скомпилировать приложение, получим что-то вроде этого:
Неплхо было бы заставить реагировать ракетку на нажатие клавиш, для этого добавим в метод Update следующий код
Code
protected override void Update(GameTime gameTime)
{
<... skip ...>
KeyboardState keyboardState = Keyboard.GetState();
// Двигаем ракетку вправо
if (keyboardState.IsKeyDown(Keys.Right))
_paddle.Position.X += 6f;
// Двигаем ракетку влево
if (keyboardState.IsKeyDown(Keys.Left))
_paddle.Position.X -= 6f;
// Ограничиваем движение ракетки игровым полем
_paddle.Position.X = MathHelper.Clamp(_paddle.Position.X, 0, _viewPortRectangle.Width - _paddle.Width);
<... skip ...>
}
Отрисовка кирпичей Создадим массив GameObject представляющий кирпичи которые собственно и будем разбивать
Code
private int _brickPaneWidth = 10; // Сколько кипричей рисовать в ширину
private int _brickPaneHeight = 5; // Сколько кипричей рисовать в высоту
private Texture2D _brickSprite; // Спрайт кирпича
private GameObject[,] _bricks; // Массив кирпичей
Добавим следующий код в метод LoadContent()
Code
protected override void LoadContent()
{
<... skip ...>
// Создание массива кирпичей
_brickSprite = Content.Load<Texture2D>(@"brick");
_bricks = new GameObject[_brickPaneWidth,_brickPaneHeight];
for (int i = 0; i < _brickPaneWidth; i++)
{
for (int j = 0; j < _brickPaneHeight; j++)
{
_bricks[i, j] = new GameObject(_brickSprite)
{
Position = new Vector2(i * 55 + 120, j * 25 + 100)
};
}
}
<... skip ...>
}
Отрисовка массива кирпичей, отисовка производится если кирпич “жив”, т.е. не разбит мячем
Code
protected override void Draw(GameTime gameTime)
{
<... skip ...>
// Рисуем кирпичи
foreach (var brick in _bricks)
if (brick.IsAlive)
spriteBatch.Draw(brick.Sprite, brick.Position, Color.White);
<... skip ...>
}
На данном этапе игровое поле выглядит следующим образом
Отрисовка мячика Создаем обьект мячика
Code
private GameObject _ball; // Мячик
protected override void LoadContent()
{
<... skip ...>
// Создание мячика, начальное положение в середине на ракетке,
// начальное направление - вправо, вверх
_ball = new GameObject(Content.Load<Texture2D>(@"ball"));
_ball.Position = new Vector2((_viewPortRectangle.Width - _ball.Width) / 2,
_viewPortRectangle.Height - _paddle.Height - _ball.Height - 20);
_ball.Velocity = new Vector2(3,-3);
<... skip ...>
}
Для анимации мячика добавим новый метод UpdateBall(), и его вызов в в метод Update(). Данный метод нам понадобится в дальнейшем для обработки столкновений мячика с кирпичами и ракеткой
Code
private void UpdateBall()
{
_ball.Position += _ball.Velocity;
}
protected override void Update(GameTime gameTime)
{
<... skip ...>
// Двигаем мячик
UpdateBall();
<... skip ...>
}
Для отрисовки мячика добавим следующий код в метод Draw()
Code
protected override void Draw(GameTime gameTime)
{
<... skip ...>
// Рисуем мячик
spriteBatch.Draw(_ball.Sprite, _ball.Position, Color.White);
<... skip ...>
}
На данный момент, мы имеем почти полностью готовое игровое поле, но без обработки столкновений мячик сразу же вылетает за пределы игрового поля. Добавим обработку столкновений мячика с игровым полем, кирпичами и ракеткой Обработка столкновений
Создадим новый метод определяющий место столкновения обьектов и меняющий направление полета мяча.
Code
// Определение стороны столкновения и отражение направления полета мячика
public void Collide(GameObject gameObject, Rectangle rect2)
{
// Обьект столкнулся сверху или снизу, отражаем направление полета по горизонтали
if (rect2.Left <= gameObject.Bounds.Center.X && gameObject.Bounds.Center.X <= rect2.Right)
gameObject.ReflectHorizontal();
// Обьект столкнулся слева или справа, отражаем направление полета по вертикали
else if (rect2.Top <= gameObject.Bounds.Center.Y && gameObject.Bounds.Center.Y <= rect2.Bottom)
gameObject.ReflectVertical();
}
Добавим следующий код в метод UpdateBall()
Code
private void UpdateBall()
{
// Будущее положение мяча, нужно для предотвращения "залипания" мяча на поверхности обьекта
Rectangle nextRect = new Rectangle((int)(_ball.Position.X + _ball.Velocity.X),
(int)(_ball.Position.Y + _ball.Velocity.Y),
_ball.Width, _ball.Height);
// Столкновение с верхним краем игрового поля
if (nextRect.Y <= 0)
_ball.ReflectHorizontal();
// При сталкивании мяча с нижним краем игрового поля, мячик "умирает"
if (nextRect.Y >= _viewPortRectangle.Height - nextRect.Height)
{
_ball.IsAlive = false;
}
// Столкновение мячика с левым или правым краем игрового поля
if ((nextRect.X >= _viewPortRectangle.Width - nextRect.Width) || nextRect.X <= 0)
{
_ball.ReflectVertical();
}
// Столкновение мячика с ракеткой
if (nextRect.Intersects(_paddle.Bounds))
Collide(_ball, _paddle.Bounds);
// Столкновение мячика с кирпичами
foreach (var brick in _bricks)
{
if (nextRect.Intersects(brick.Bounds) && brick.IsAlive)
{
brick.IsAlive = false;
Collide(_ball, brick.Bounds);
}
}
_ball.Position += _ball.Velocity;
}
Итак, что получилось
Конечно физика в игре, мягко говоря, никакая, мячик иногда залипает при столкновении с движущейся ракеткой. Но, повторюсь, смысл данной статьи знакомство со средой Microsoft XNA Game Studio, и, надо сказать, она отлично справляется с рутиной, освобождая время разработчика для фокусировки внимания на логике игры. Надеюсь помогло
Источник: http://habrahabr.ru/