Pull to refresh
0
Microsoft
Microsoft — мировой лидер в области ПО и ИТ-услуг

Учимся программировать игры на XNA для Windows Phone 7 «Mango» — начало

Reading time 9 min
Views 18K
В свете недавнего анонса HTC о скором появлении телефонов на Windows Phone 7 «Mango» на российском рынке, особую актуальность приобретает разработка приложений для Windows Phone — ведь именно сейчас есть возможность насытить Windows Phone Marketplace приложениями, близкими нашему русскому сердцу. Это одна из причин, по которой мы сегодня (5 сентября) проводим Windоws Phone 7 Camp, и призываем вас приходить, смотреть онлайн-трансляцию и браться за Visual Studio прямо сейчас.



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


Морской бой, который мы будем писать — это не та игра, в которую многие играли в детстве на бумаге в клеточку. Это игра, которая была доступна в игровых автоматах, где надо было попасть в плывущий корабль несколькими выстрелами. В нашей игре также будет корабль, плавающий в верхней части экрана, и снаряды, которые можно будет выпускать из нижней части экрана путём прикосновения к нему.

Для начала – несколько слов об XNA. XNA – это набор библиотек, работающий поверх Microsoft .NET, причем как на телефоне, так и на других устройствах: полноценном персональном компьютере, XBox 360 и Zune. Причем код игры для всех этих устройств может отличаться весьма незначительно – учитывая различие способов управления игрой и разное разрешение экранов. Остальные аспекты – вывод графики, звука, сохранение игры и т.д. – максимально унифицированы для всех устройств. Кроме того, XNA работает поверх доступной платформы .NET (это полноценная .NET 4.0 на компьютере, и .NET Compact Framework на других устройствах), поэтому вы можете использовать и другие возможности платформы (например, средства сетевого взаимодействия). Теоретически, XNA может использоваться не только для создания игр, но и для более широкого круга динамичных богатых графических приложений – например, для научной визуализации.

Архитектура XNA-приложения весьма отличается от классического Windows или Web-приложения. В нем не используется модель событий, поскольку она не очень подходит для решения задач реального времени – а ведь нам хочется, чтобы игра разворачивалась перед нашими глазами именно в реальном времени, со скоростью не менее, чем 25 кадров в секунду! Поэтому игра имеет весьма простую программную модель – цикл игры. В начале игры (или игрового уровня) вызывается специальная функция LoadContent для загрузки основных ресурсов (графических и звуковых элементов), затем в цикле попеременно вызываются методы Update (для обновления состояния игры в зависимости от действия пользователя с устройствами ввода) и Draw (для отрисовки состояния игры на экране). Таким образом, для написания игры надо совершить несколько основных действий:
  1. Создать графические и звуковые элементы оформления игры и поместить их в проект. Для создания графических элементов хорошо подойдут инструменты Microsoft Expression. Графические элементы могут быть как двумерными спрайтами, так и трёхмерными моделями.
  2. Описать переменные для хранения всех необходимых элементов и переопределить метод LoadContent для загрузки их в память.
  3. Понять, как будет выглядеть состояние игры – т.е. набор переменных и структур данных, которые будут описывать игру в каждый момент времени. Состояние может быть весьма простое (как в нашем примере — координаты корабля и снаряда), или состоять из множества независимых взаимодействующих объектов или агентов, обладающих своим интеллектом и логикой.
  4. Обработать действия пользователя с устройствами ввода и запрограммировать логику изменения состояния игры в методе Update.
  5. Запрограммировать отображение состояния на экран в методе Draw. Здесь опять же может использоваться 2D или 3D-графика, в зависимости от стиля игры.
  6. Если игра более сложная, содержит несколько уровней и т.д. – возможно будет полезно усовершенствовать объектную модель, чтобы отделить каждый уровень в отдельный класс – тогда для каждого уровня придётся частично повторять описанные выше действия
  7. Играть, наслаждаться, делиться игрой с другими (сюда входят такие шаги, как создание инсталлятора, распространение игры через Windows Mobile Marketplace, XBox Indie Games и т.д.).


Vistual Studio Shot
Для нашей разработки нам потребуется Visual Studio 2010 (напоминаю, что студенты могут получить её по программе DreamSpark бесплатно) и XNA Game Studio, которая входит в состав Windows Phone Developer Tools. Установив всё это, вы должны быть в состоянии создать новый проект типа Windows Phone Game (4.0), который будет представлять собой каркас игры, содержащий описанные выше методы, и выдающий при запуске игру с пустым фиолетовым экраном. Чтобы наполнить игру содержанием, пройдёмся по описанным выше шагам (1-5, поскольку шаги 6 и 7 для простой игры не имеют смысла). Когда Visual Studio спросит вас, под какую версию телефона — 7.0 или 7.1 Mango — следует разрабатывать игру, смело выбирайте 7.1 — так вам будут доступны новые возможности, такие, как дополнительные сенсоры, быстрое переключение приложений и т.д.

Начнем с того, что сделаем одно важное действие, чтобы правильно ориентировать телефон. По умолчанию экран игры может быть повёрнут вертикально или горизонтально. Если нам нужна какая-то конкретная ориентация, то в конструкторе класса Game1 можно задать желаемое разрешение экрана следующим образом:

      graphics.PreferredBackBufferWidth = 480;
      graphics.PreferredBackBufferHeight = 800;

* This source code was highlighted with Source Code Highlighter.


Обратите внимание – в созданном решении два проекта: собственно игра, и ресурсы игры (Content) – изображения, звуки и т.д. В самой игре есть два класса – Program.cs нужен для запуска игры, а Game1.cs содержит основную логику игры (функции LoadContent/Update/Draw), и именно его мы будем модифицировать.

Графическое содержимое в нашем случае будет состоять из трёх элементов – изображения корабля и взрыва, которые мы возьмём из коллекции clipart Microsoft Office, и изображения снаряда – красной чёрточки, которую можно нарисовать в Paint. Полученные графические файлы мы добавим в Content-проект нашей игры (используем меню Add Existing Item) – результат можно наблюдать на рисунке выше.

Далее опишем переменные, отвечающие за состояние игры. Нам понадобится хранить графические изображения корабля, ракеты и взрыва – они будут типа Texture2D, координаты и скорость корабля (скорость нужна для задания направления), а также координаты ракеты – это будут объекты типа Vector2. Дополнительно для отрисовки взрыва понадобится переменная explode – она будет вести обратный отсчёт числа кадров, во время которых вместо корабля показывается взрыв.

Texture2D ship, rocket, explosion;
Vector2 ship_pos = new Vector2(100, 100);
Vector2 ship_dir = new Vector2(3, 0);
Vector2 rock_pos = Vector2.Zero;

int explode = 0;

* This source code was highlighted with Source Code Highlighter.


Метод LoadContent для загрузки содержимого игры будет выглядеть совсем просто (описание функции и первая строчка были сгенерированы автоматически при создании проекта, нам необходимо было добавить лишь три строчки для загрузки описанных нами ресурсов):

protected override void LoadContent()
{
  spriteBatch = new SpriteBatch(GraphicsDevice);
  ship = Content.Load<Texture2D>("Ship");
  rocket = Content.Load<Texture2D>("Rocket");
  explosion = Content.Load<Texture2D>("Explode");
  
}

* This source code was highlighted with Source Code Highlighter.


Метод отрисовки также достаточно прост:

protected override void Draw(GameTime gameTime)
{
  GraphicsDevice.Clear(Color.White);
  spriteBatch.Begin();
  if (explode > 0) spriteBatch.Draw(explosion, ship_pos, Color.White);
  else spriteBatch.Draw(ship, ship_pos,Color.White);
  if (rock_pos != Vector2.Zero){ spriteBatch.Draw(rocket, rock_pos, Color.Red); }
  spriteBatch.End();
  base.Draw(gameTime);
}

* This source code was highlighted with Source Code Highlighter.


Здесь следует отметить одну тонкость – при рисовании спрайтов на экране мы рисуем картинки “порциями”, называемыми SpriteBatch. Соответственно, мы открываем такую “рисовательную транзакцию” вызовом spriteBatch.Begin(), и заканчиваем вызовом spriteBatch.End(), между которыми расположены вызовы spriteBatch.Draw() или DrawString(..). В нашем случае мы рисуем либо корабль, либо картинку взрыва – в зависимости от переменной explode, а также отображаем ракету в том случае, если она выпущена и летит к кораблю – это задаётся ненулевым значением вектора координат ракеты rock_pos.

Теперь перейдём к рассмотрению метода Update. Его будем рассматривать по частям. Первая часть отвечает за отрисовку взрыва: когда переменная-флаг explode ненулевая, единственная задача нашей игры – отрисовать взрыв. Поэтому мы просто уменьшаем счетчик кадров, в течение которых показывается взрыв, а когда он достигает нулевой отметки – возвращаем корабль в исходное положение, чтобы игра началась снова:

protected override void Update(GameTime gameTime)
{
  if (explode > 0)
  {
    explode--;
    if (explode == 0)
    {
      ship_pos.X = 0;
      ship_dir.X = 3;
    }
    base.Update(gameTime);
    return;
  }
  ....  
  base.Update(gameTime);
}

* This source code was highlighted with Source Code Highlighter.


Обратите внимание, что в конце метода Update вызывается метод Update базового класса.

Следующий фрагмент кода отвечает за движение корабля влево-вправо:

ship_pos += ship_dir;
if (ship_pos.X + ship.Width >= 480 || ship_pos.X <= 0)
{
  ship_dir = -ship_dir;
}

* This source code was highlighted with Source Code Highlighter.


Здесь мы в явном виде используем разрешение экрана – на практике конечно так делать не стоит, лучше задать константы вначале программы, или же использовать переменные, которые инициализируются в процессе работы игры в зависимости от конкретного устройства.

Далее будем обрабатывать действия пользователя – в нашем случае прикосновения к экрану. TouchPanel.GetState() позволяет получить состояние панели телефона, которое в свою очередь может содержать информацию о нескольких одновременных касаниях (поддержка MultiTouch). Мы будем обрабатывать лишь одно (первое) касание, и в случае, если такое касание есть – будем запускать ракету из точки, X-координата которой совпадает с касанием, а координата по вертикали – фиксирована где-то внизу экрана:

var tc = TouchPanel.GetState();
if (tc.Count>0)
{
  rock_pos.X = tc[0].Position.X;
  rock_pos.Y = 750;
}

* This source code was highlighted with Source Code Highlighter.


Далее следует код, отвечающий за движение ракеты и отработку столкновения ракеты с кораблём:

if (rock_pos != Vector2.Zero)
{
  rock_pos += new Vector2(0, -7);
  if (rock_pos.Y >= 0 && rock_pos.Y <= ship_pos.Y + ship.Height &&
    rock_pos.X >= ship_pos.X && rock_pos.X <= ship_pos.X + ship.Width)
  {
    explode = 20;
    ship_pos.X = rock_pos.X - explosion.Width / 2;
    rock_pos = Vector2.Zero;
  }
  if (rock_pos.Y == 0) rock_pos = Vector2.Zero;
}

* This source code was highlighted with Source Code Highlighter.


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

Для придания игре окончательно правдоподобности, имеет смысл зеркально отображать изображение корабля в том случае, когда он плывет влево (чтобы он не плыл «задом»). Для этого слегка изменим код для отображения корабля, добавив возможность зеркального отражения:

if (explode == 0)
{
  spriteBatch.Draw(ship, ship_pos, null, Color.White,0f,
   Vector2.Zero,1.0f,
   ship_dir.X>0 ? SpriteEffects.FlipHorizontally : SpriteEffects.None,0f);
}
else spriteBatch.Draw(explosion, ship_pos, Color.White);


* This source code was highlighted with Source Code Highlighter.


Вот и всё, что необходимо для написания простейшей игры. Чтобы сделать её более привлекательной, стоит нарисовать красивую графическую подложку, поверх которой будет разворачиваться стрельба – для этого достаточно рисовать соответствующее изображение в начале spriteBatch, чтобы все все дальнейшие объекты отрисовывались поверх картинки. Также можно добавить счётчик попаданий – при этом для отрисовки строки надо будет использовать метод spriteBatch.DrawString, а шрифт, которым будет выводиться строка, надо будет поместить в ресурсы проекта и загрузить в методе LoadContent.

Вы можете скачать исходный текст всего проекта, чтобы на досуге разобраться с ним, или использовать как отправную точку для создания своих игр. Конечно, данный пример был очень простым, и не являет собой хороший пример того, как надо писать игры в действительности. Многие более реалистичные примеры вы можете найти:



В следующих выпусках я расскажу, как можно улучшить игру, добавив звук и управление кораблем при помощи акселерометра. Буду рад услышать ваши комментарии, пожелания и вопросы ниже, а также в твиттере.
Tags:
Hubs:
+28
Comments 20
Comments Comments 20

Articles

Information

Website
www.microsoft.com
Registered
Founded
Employees
Unknown
Location
США