Сегодня мы продолжим работать над игрой и рассмотрим механизм расчета столкновений между объектами. В любой игре, чтобы узнать, столкнулись или пересеклись объекты, нужно написать исходный код, который создает своего рода детектор столкновений. Если такой детектор срабатывает, значит, объекты столкнулись. Сама же обработка возникшей ситуации может быть разной. В одних случаях вам придется уничтожать объекты, в других необходимо будет оттолкнуться от этого объекта, в третьих – захватить или взять его и так далее. Все зависит исключительно от задач, которые вы должны решать в каждом случае. В нашей игре метеориты летят сверху вниз или навстречу космическому кораблю. Поэтому нам нужно реализовать механизм, который определит, коснулся ли один из метеоритов корабля или нет. Если космический корабль столкнулся с метеоритом, то сила защитного поля корабля должна быть уменьшена на определенное значение – подробнее об этом мы поговорим во второй части публикации. В свою очередь столкнувшийся с кораблем метеорит мы установим на новую позицию, за верхней кромкой экрана, и будем перемещать его опять вниз, зациклив тем самым движение метеорита в пространстве. Что касается взрывов метеоритов, которые должны возникать после касания с кораблем, то пока что мы не будем ими заниматься. Этот код значительно сложнее всего изучаемого материала, но если вы хотите добавить в игру взрыв и не знаете, как это сделать, то я вам советую посетить сайт клуба разработчиков игр XNA Creators Club и найти там пример под названием ParticleSample. В этом примере показано, как можно сделать хороший и добротный взрыв с использованием системы частиц. Ну а мы переходим к работе над игрой и начнем изучать материал с небольшой теоретической части, посвященной основам создания детектора столкновений.
Рис. 2
Структура BoundingBox
Детектором столкновений в XNA Game Studio Express служит системная структура BoundingBox. Дословный перевод названия на русский означает «ограничивающий прямоугольник». Определение столкновений между объектами с его помощью состоит вот в чем. В исходном коде игры создается структурная переменная структуры BoundingBox, которая описывает невидимый глазу прямоугольник. Размер прямоугольника вы задаете сами с помощью элементов структуры BoundingBox. Далее вы «надеваете» этот самый ограничивающий прямоугольник на определенный спрайт, максимально подгоняя размер прямоугольника под размер спрайта. То есть размер ограничивающего прямоугольника должен соответствовать размеру одного фрейма изображения, например метеорита. Такую несложную операцию нужно проделать с каждым спрайтом, участвующим в столкновении. Если в игре происходит пересечение этих прямоугольников, значит, объекты столкнулись и это влечет определенные события. Если пересечение прямоугольников не произошло, то значит, столкновения объектов не было и ничего делать не нужно. Чтобы разобраться в создании ограничивающих прямоугольников, давайте попробуем на абстрактном примере сформировать переменную структуры BoundingBox и определить размер ограничивающего прямоугольника.
// Создаем структурную переменную
public BoundingBox bb;
// Задаем размер прямоугольника
bb.Min = new Vector3(10, 30, 0);
bb.Max = new Vector3(120, 130, 0);
В этом блоке кода структурные переменные Min и Max определяют две точки в пространстве, на основе которых происходит построение ограничивающего прямоугольника. Точка Min задает левый верхний угол ограничивающего прямоугольника, что соответствует левому верхнему углу изображения. В свою очередь, точка Max определяет правый нижний угол прямоугольника, что соответствует правому нижнему углу изображения. Третий параметр структуры, Vector3, в двухмерных вычислениях всегда задается нулевым значением. На основе этих данных происходит построение прямоугольника в пространстве. Если этот прямоугольник подогнать к размеру спрайта, мы получаем тот самый ограничивающий прямоугольник, представленный структурой BoundingBox (рис. 1). При этом чем плотнее вы «наденете» ограничивающий прямоугольник на спрайт, тем точнее будет обрабатываться столкновение между объектами. Дополнительно можно искусственно уменьшать или увеличивать ограничивающий прямоугольник, чтобы соответственно уменьшить или увеличить зону столкновения объектов.
Рис. 3
Добавляем в код обработку столкновений
Переходим непосредственно к работе над новым проектом под названием Collision. Исходный код этого примера вы можете найти на DVD диске в разделе «Игра для Xbox 360 своими руками». Откройте проект Collision и перейдите к изучению программного кода класса Game1. По сравнению с предыдущим примером в области глобальных переменных добавились две новые записи.
public BoundingBox bbShip;
public BoundingBox[] bbMeteorite = new BoundingBox[10];
В первой строке создается переменная bbShip структуры BoundingBox, которая очерчивает ограничивающий прямоугольник вокруг космического корабля. Во второй строке приведенного блока исходного кода происходит создание массива переменных bbMeteorite количеством десять штук. Все эти ограничивающие прямоугольники в дальнейшем «надеваются» на десять имеющихся в игре метеоритов. После того как все необходимые переменные структуры BoundingBox были созданы, зададим размеры прямоугольников для каждого отдельно взятого объекта нашей игры. То есть нужно наложить один конкретный ограничивающий прямоугольник на один конкретный спрайт, четко подогнав его под размер самого спрайта. Также необходимо реализовать неотступное следование ограничивающего прямоугольника за спрайтом. Для всех перечисленных действий в исходном коде класса Game1 мы создадим отдельный метод с названием Collisions(). Вызов этого метода производится в игровом цикле, а значит, мы будем на каждой итерации игрового цикла иметь свежие данные. Весь исходный код метода Collisions() громоздкий и в одну журнальную колонку не уместится, поэтому мы разобьем его на блоки. В первом блоке кода происходит подгонка ограничивающего прямоугольника к космическому кораблю.
// «надеваем» ограничивающий прямоугольник на космический корабль
bbShip.Min = new Vector3(ship.spritePosition.X, ship.spritePosition.Y, 0);
bbShip.Max = new Vector3(ship.spritePosition.X + ship.spriteTexture.Width, ship.spritePosition.Y, 0);
В качестве точек построения прямоугольника (Min и Max) выступает расчет координат местонахождения корабля в пространстве (рис. 2). Это дает нам возможность постоянного перемещения ограничивающего прямоугольника в унисон с космическим кораблем. Вторая часть метода Collisions() основана на создании ограничивающих прямоугольников для метеоритов, которое происходит так же, как и с космическим кораблем, но уже применительно к каждому элементу массива данных.
// «надеваем» ограничивающие прямоугольники на все метеориты
for (int i = 0; bbMeteorite.Length > i; i++)
{
bbMeteorite.Min = new Vector3(meteorite.spritePosition.X, meteorite.spritePosition.Y, 0);
Третья часть метода Collisions() позволяет создать тот самый механизм определения пересечений между ограничивающими прямоугольниками. Для этих целей в коде используется библиотечный метод Intersects() структуры BoundingBox. Этот метод как раз и определяет пересечение между собой прямоугольников.
// обрабатываем столкновения метеоритов с кораблем
for (int i = 0; bbMeteorite.Length > i; i++)
{
if (bbShip.Intersects(bbMeteorite))
{
meteorite.spritePosition = new Vector2(rand.Next(10, screenWidth - 100), -500);}
}
Именно в этом блоке кода следует задавать все события, которые должны происходить с объектами при столкновении. Если оно произошло, нужно просто убрать метеорит с экрана и поставить его на новую игровую позицию в верхней части экрана. Если же пересечение ограничивающих прямоугольников не происходит, то метеорит уходит за пределы экрана и затем опять устанавливается на новую игровую позицию. Также в следующем номере журнала «СИ» мы добавим звуковой сигнал (эффект взрыва), что придаст игре больше правдоподобия. А теперь нам предстоит смастерить счетчик жизней космического корабля, или, точнее, счетчик мощности защитного поля корабля, чем мы сейчас и займемся.
Рис. 4
Считаем жизни корабля
Мы выделяем кораблю всего три жизни. Каждое столкновение с метеоритом будет уменьшать общее количество жизней корабля на единицу. Подсчет утраты жизней строится на основе простого целочисленного счетчика. Алгоритм действий по уменьшению жизней корабля в этом случае очень прост. Изначально мы создаем дополнительную переменную и задаем ее значение равное трем. Затем при каждом столкновении корабля и метеорита значение этой переменной уменьшается на единицу. Как только у корабля не остается жизней, игра или этот небольшой уровень считается проигранным. Пока что мы только добавим в игру сам счетчик, а уже в последней, восьмой публикации реализуем предложенный механизм, поскольку тогда мы как раз и будем изучать смену игровых состояний и добавление в игру меню. Итак, добавим в исходный код класса Game1 в область глобальных переменных новую переменную life, а в методе Initialize() зададим ее значение равное трем.
private int life;
…
life = 3;
Теперь необходимо следить за столкновениями метеоритов и корабля и по мере необходимости уменьшать значение переменной life. Для этих целей в коде программы имеется метод Collisions(), в котором нам нужно добавить всего одну строку кода.
// обрабатываем столкновения метеоритов с кораблем
for (int i = 0; bbMeteorite.Length > i; i++)
{
if (bbShip.Intersects(bbMeteorite))
{
meteorite.spritePosition = new Vector2(rand.Next(10, screenWidth - 100), -500);
life -= 1;
}
}
Все, теперь при каждом столкновении космического корабля и метеорита значение переменной life будет уменьшаться на единицу, а сам метеорит отправится на новую позицию (рис. 3). Но сейчас наш счетчик скрыт, и простой пользователь его не увидит, а точнее, он не увидит, сколько жизней осталось у корабля. Об этом стоит позаботиться и добавить в игру графический счетчик, который покажет подсчет оставшейся жизненной энергии корабля.
Рис. 1Рис. 6
Графический счетчик жизней
Графическое представление счетчика жизней в играх можно делать различными способами, здесь все зависит от общего игрового дизайна. В нашей игре счетчик жизней представлен уменьшенным изображением космического корабля и будет выводиться на экран в левом верхнем углу (рис. 4). Поскольку мы отводим кораблю три жизни, у нас будет три маленьких изображения корабля. Механизм работы предложенного графического счетчика очень прост. Как только корабль сталкивается с метеоритом, переменная life уменьшается на единицу, и с экрана убирается один из кораблей. Каждое последующее столкновение корабля и метеорита убирает с экрана оставшиеся корабли. То есть переменная life напрямую связана с механизмом отображения счетчика жизней. Теперь поговорим о том, как сделать такой счетчик. В исходном коде класса Game1 в области глобальных переменных нужно объявить три новых объекта для каждого из трех графических изображений корабля в уменьшенном варианте.
Sprite[] lifeShip = new Sprite[3];
Как видно из этого блока, мы используем класс Sprite и массив данных, как и в случае с метеоритами. После данного объявления необходимо явно создать эти объекты в программном коде.
for (int i = 0; lifeShip.Length > i; i++)
{
lifeShip = new Sprite();
}
После того как объекты созданы, необходимо добавить в игру графическое изображение счетчика. Сделать это нужно так. В панели Solution Explorer щелкните правой кнопкой мыши на названии папки Textures и в контекстном меню выберите команды Add и далее Exiting Item. Откроется диалоговое окно Add Exiting Item. В этом окне в списке Files of Types необходимо выбрать тип Content Pipeline. Затем найдите на своем жестком диске графическое изображение корабля, выделите его курсором и нажмите кнопку Add. После этого в текущем проекте в папке Textures у вас появится новое графическое изображение (рис. 5). Теперь можно переходить к коду загрузки изображений в игру. Как вы помните по прошлым статьям, для этих целей существует метод LoadGraphicsContent().
Загрузив в игру графику, перейдем к методу Initialize() и установим все корабли на свои игровые позиции. В данном случае игровые позиции для трех кораблей – это левый верхний угол экрана (рис. 6).
// установка жизней на экране
lifeShip[0].spritePosition = new Vector2(70, 50);
lifeShip[1].spritePosition = new Vector2(70 + 80, 50);
lifeShip[2].spritePosition = new Vector2(70 + 160, 50);
Но нельзя забывать одно обстоятельство. Работая с текстом или графическими изображениями, рисуемыми вблизи краев экрана, нужно обязательно учитывать возможность искажения данных. Дело в том, что телевизор, по сравнению с монитором, в своих крайних точках по углам и краям может заметно искажать графику. В связи с этим, рисуя статическую игровую графику на экране телевизора (текст, счетчики, очки.), необходимо обязательно отступать несколько пикселей от краев экрана, чтобы избежать видимых искажений. Это некоторое количество пикселей обычно составляет около 10–20% от ширины или высоты экрана (рис. 7). Теперь, когда выбрано место для вывода счетчика на экран, нам осталось только нарисовать все корабли на экране. Для этого переходим к методу Draw().
if (life == 3)
{
lifeShip[0].DrawSprite(spriteBatch);
lifeShip[1].DrawSprite(spriteBatch);
lifeShip[2].DrawSprite(spriteBatch);
}
else if (life == 2)
{
lifeShip[0].DrawSprite(spriteBatch);
lifeShip[1].DrawSprite(spriteBatch);
}
else if (life == 1)
{
lifeShip[0].DrawSprite(spriteBatch);
}
Вся приведенная конструкция кода в переводе на русский язык означает вот что. Если переменная life равна трем, то на экране рисуются три корабля. Если же переменная life уменьшилась на единицу и стала равна двум, то на экране рисуются только два корабля. А если переменная life уменьшилась еще на единицу, то на экране единовременно будет отображен только один корабль. Соответственно, когда у корабля не останется жизней, то есть переменная life будет равна нулю, ни один корабль на экране не рисуется. Этот простой и достаточно интересный механизм счетчика позволит вам без особых усилий создать видимый для игрока графический подсчет оставшихся жизней. Откомпилируйте рассмотренный проект и передайте его на приставку Xbox 360. Поэкспериментируйте со столкновениями и понаблюдайте за реализацией графического счетчика.
На сегодня все. В следующем номере журнала мы добавим в игру звуковые эффекты, а затем нас ждет еще одна встреча, где мы закончим работать над созданием игры и подведем итоги.
Рис. 5Рис. 7<<5-я часть «Игры своими руками»7-я часть «Игры своими руками»>>
969 Прочтений • [Игра для Xbox 360 своими руками. Часть 6] [12.04.2012] [Комментариев: 0]