Пришло время продолжать работу над игрой! Сегодня нам предстоит изучить много разных премудростей, поэтому новичкам необходимо предельно сосредоточиться. Мы несколько усложним исходный код предыдущего проекта из прошлого номера «СИ» и перейдем к объектной модели разработки игры. Затем мы поговорим о спрайтовой анимации и в конце статьи добавим в игру метеориты, которые будут лететь навстречу космическому кораблю.
Добавляем в игру новый класс
Как вы помните, все данные, связанные с объектом, представляющим космический корабль, объявлялись и создавались нами непосредственно в классе Game1. Как правило, такой подход оправдан в небольших демонстрационных примерах. В настоящих игровых проектах давным-давно делают по-другому, поскольку количество строк исходного кода в нынешних играх составляет не одну сотню тысяч. Поэтому перед созданием игры прежде всего разрабатывается общая структура классов для всего проекта. У нас в игре также будет несколько классов, и сейчас пришло время добавить новый класс Sprite. Он поможет нам упростить создание игры и будет отвечать за все спрайты, загружаемые в программу. Сам класс Sprite сделан мною специально для читателей книг по программированию игр (подробности на сайте www.gornakov.ru), но мы используем его и в нашей игре. Достоинства объектно-ориентированного программирования как раз и состоят в том, что вы можете создать один класс для одного из своих проектов, а затем спокойно применить имеющуюся наработку в другом проекте с минимальными изменениями. Но перейдем к изучению класса Sprite. На DVD к журналу (в корневом каталоге диска) вы найдете новый проект с названием Meteorite. Он основан на исходном коде предыдущего проекта Background, но с некоторыми дополнениями и переработками. Проект Meteorite имеет классы Program, Game1 и Sprite. Исходный код класса Sprite находится в отдельном файле Sprite.cs. Этот новый класс создан для текущего проекта непосредственно в студии разработки игр. Как это делается? Откройте студию разработки игр и, скажем, проект Background. Затем в открытом проекте в панели Solution Explorer щелкните правой кнопкой мыши на названии проекта и в контекстном меню выберите команды Add и затем команду New Item. Откроется новое диалоговое окно Add New Item – Background (рис. 1). Выделите в этом окне курсором мыши шаблон Class и в поле Name задайте имя будущему классу, например Sprite, и нажмите кнопку Add. После этого в рабочем каталоге проекта сформируется новый файл под названием Sprite.cs – это и есть новый, только что созданный класс Sprite. В него скопирован исходный код класса Sprite, о котором упоминалось ранее. Но прежде чем рассматривать его программный код, давайте уделим немного времени игровой анимации.
Анимация
Изображения в играх могут быть как анимированными, так и неанимированными. Неанимированное изображение – это неподвижный рисунок заданного размера, состоящий из одного кадра или фрейма. Анимированное изображение состоит из набора фреймов, или нескольких последовательных взаимосвязанных изображений. Отсчет фреймов происходит от нуля. Их количество не ограничено, но весь набор последующих фреймов анимационной последовательности должен совпадать по размеру с самым первым, как по ширине, так и по высоте. Располагать фреймы можно горизонтально, вертикально или компоновать любым удобным для вас способом. Посмотрите на рис. 2 с примером набора фреймов анимационной последовательности, расположенной горизонтально. Циклическое перемещение по фреймам этого изображения или постоянная перерисовка фреймов на экране телевизора и будет создавать эффект анимации. Мы применим анимационную последовательность к метеоритам. Она состоит из набора фреймов, имитирующих вращение метеорита вокруг своей оси (рис. 3). В игре мы будем просто циклично переходить от фрейма к фрейму и создадим эффект вращения.
Рис. 1
Класс Sprite
Если вы еще не открыли проект Meteorite, то сделайте это и перейдите к исходному классу Sprite. В исходном коде этого класса, в области глобальных переменных происходит объявление нескольких переменных.
public Texture2D spriteTexture;
public Vector2 spritePosition;
public Vector2 speedSprite = new Vector2(0, 8);
private int frameСount;
private double timeFrame;
private int frame;
private double totalElapsed;
Переменная spriteTexture будет содержать то или иное графическое изображение, загружаемое в игру. Вторая переменная spritePosition необходима для выбора позиции на экране телевизора. Переменная speedSprite задает скорость движения метеоритов в пространстве со скоростью 8 пикселей. Как видите, все метеориты двигаются сверху вниз исключительно по оси Y. Затем идет небольшой блок переменных, обеспечивающих анимацию метеоритов. Первая переменная frameСount впоследствии будет содержать в себе фреймы нашей анимационной последовательности и примет прямое участие в организации циклического показа фреймов на экране. Вторая переменная timeFrame получит значение времени, измеряемое в миллисекундах, для показа одного из фреймов на экране. Именно с помощью этой переменной мы определим время задержки показа одного фрейма на экране. Если не делать задержки для показа одного из фреймов анимации, то переход по всей анимационной последовательности будет происходить на огромной скорости. Следующая переменная frame – своего рода счетчик, представляющий в текущий момент один из фреймов. Этот счетчик по прошествии заданного времени задержки увеличивается на единицу, что дает возможность перейти к показу следующего фрейма анимации. Последняя переменная totalElapsed будет содержать значение времени, необходимое для расчета задержки показа одного фрейма, а точнее для расчета прошедшего времени, выделенного на показ одного из фреймов. После объявления в исходном коде всех переменных нам необходимо инициализировать переменные заданными значениями. Для этих целей в классе Sprite формируется конструктор, необходимый для создания анимированных объектов этого класса.
public Sprite(int frameCount, int framesPerSec)
{
frameСount = frameCount;
timeFrame = (float)1 / framesPerSec;
frame = 0;
totalElapsed = 0;
}
Конструктор класса Sprite содержит два целочисленных параметра frameCount и framesPerSec, которые передаются в создаваемый объект класса. Первый параметр frameCount задает общее количество фреймов для загружаемого в игру изображения. Количество фреймов загружаемой анимационной последовательности необходимо знать точно. На основе количества фреймов изображения, происходит расчет времени задержки для показа одного фрейма на экране. Второй параметр framesPerSec конструктора класса Sprite, в момент создания объекта этого класса получает количество кадров, которые необходимо показать за одну секунду. Это значение позволяет рассчитать время задержки, необходимое для показа одного фрейма на экране. Такой подход позволяет реализовывать в игре разную степень скорости анимации объектов, а также возможность создавать различное количество объектов класса Sprite, каждый из которых может содержать любое число фреймов анимационной последовательности. Дополнительно у нас также есть конструктор и для неанимированных объектов – космического корабля.
public Sprite()
{
}
В итоге в одном классе мы имеем два конструктора, которые позволяют нам создавать как анимированные, так и неанимированные объекты этого класса. Далее в исходном коде класса Sprite следует метод UpdateFrame(). Этот метод реализует анимацию метеоритов или перебор фреймов изображения.
public void UpdateFrame(double elapsed)
{
totalElapsed += elapsed;
if (totalElapsed > timeFrame)
{
frame++;
frame = frame % (frameСount - 1);
totalElapsed -= timeFrame;
}
}
Рис. 2
В метод UpdateFrame() передается значение времени, полученное с момента предыдущего вызова этого метода, и его увеличение на единицу. Затем в конструкции кода if/else проверяется условие, в котором время задержки для показа одного фрейма не должно быть больше полученного значения. Как только это время больше или время задержки для показа одного фрейма истекает, происходит увеличение счетчика фреймов на единицу и, соответственно, переход к показу следующего фрейма, что и создаст нам игровую анимацию. Далее в исходном коде файла Sprite.cs следует описание метода Load(), который в качестве параметров принимает объект content класса ContentManager и объект stringTexture класса String.
public void Load(ContentManager content, String stringTexture)
{
spriteTexture = content.Load(stringTexture);
}
Объект content необходим для вызова метода Load(), а значит, и для возможности загрузки в программу изображения непосредственно через класс Sprite. Второй объект stringTexture будет содержать строку текста, в которой мы передадим путь к загружаемому изображению. Достаточно лишь вызвать этот метод в классе Game1 и в качестве параметров передать объект content и указать путь к спрайту. В самом конце исходного кода класса Sprite имеются два метода: DrawSprite() и DrawAnimationSprite(). Первый метод нужен для работы с объектами без анимации.
В этом методе происходит вызов системного метода Draw(), а в качестве параметров выступает спрайт, позиция на экране и цветовая составляющая. Второй метод DrawAnimationSprite() работает уже с анимированными изображениями.
public void DrawAnimationSprite(SpriteBatch spriteBatch)
{
int frameWidth = spriteTexture.Width / frameСount;
Rectangle rectangle = new Rectangle(frameWidth * frame, 0, frameWidth, spriteTexture.Height);
В первой строке кода этого метода переменная frameWidth инициализируется значением ширины одного фрейма анимационной последовательности, измеряемой в пикселях. Строка spriteTexture.Width позволяет получить текущую ширину графического изображения (изображение метеоритов состоит из нескольких кадров) и далее происходит деление этого значения на общее количество фреймов анимационной последовательности (например: 400 пикселей/4 фрейма = 100 пикселей), что в итоге дает нам искомую ширину одного фрейма. Вот поэтому все фреймы анимационной последовательности должны быть одинаковыми! Во второй строке кода метода DrawAnimationSprite() создается прямоугольник или видимая прямоугольная область, которая по своему размеру равна ширине и высоте одного фрейма анимационной последовательности. То есть отсекаются все фреймы анимационной последовательности, кроме одного-единственного, который рисуется в текущий момент на экране. По сути, в коде создается окно заданного размера, в которое мы подставляем необходимые фреймы рисунка, чередуя их с заданной скоростью. В последней строке кода метода DrawAnimationSprite() происходит прорисовка одного из фреймов на экране телевизора, где имеются такие параметры:
spriteTexture – загруженное в игру графическое изображение.
spritePosition – позиция в игре.
rectangle – ограничивающий прямоугольник, равный ширине и высоте одного фрейма.
Color.White – задает цвет для рисуемого изображения.
На этом о классе Sprite все. Если вы не смогли пока разобраться с исходным кодом этого класса, то не беда, главное – научиться работать с ним. Теперь перейдем к рассмотрению исходного кода класса Game1 и подправим загрузку космического корабля в игру, а затем разберемся с метеоритами.
Рис. 3
Работаем с кораблем
Заметьте, что код из предыдущего примера по загрузке спрайта в классе Game1 был удален, потому что загрузка изображения теперь происходит по-другому. В исходном коде класса Game1 в области глобальных переменных происходит объявление объекта ship класса Sprite.
public Sprite ship;
Теперь космический корабль – это объект класса Sprite, и нам необходимо создать или инициализировать объект с помощью конструктора класса Sprite.
ship = new Sprite();
Космический корабль не использует анимацию, поэтому мы применяем конструктор для создания неанимированного изображения. Затем в методе Initialize() через объект ship мы обращаемся к объекту spritePosition и задаем начальную точку вывода изображения корабля на экран телевизора.
ship.spritePosition = new Vector2(600, 400);
А в методе LoadGraphicsContent() вызываем метод Load() класса Sprite, который загружает изображение космического корабля в проект.
ship.Load(content, “Content\\Textures\\ship");
Теперь остается только нарисовать корабль на экране в методе Draw() и несколько видоизменить управление кораблем с помощью джойстика. Посмотрите на исходный код класса Game1, я думаю, вам не составит труда разобраться с изменениями самостоятельно. Проделав все необходимые изменения с космическим кораблем, можно смело переходить к метеоритам.
Рис. 6
Добавляем в игру метеориты
У нас будет десять метеоритов и пять разных графических изображений, то есть одно изображение на два метеорита. Организовать загрузку и работу с метеоритами в игре можно несколькими способами. Самый лучший – создать простой массив данных, где мы будем хранить все наши метеориты. Для этого в проекте Meteorite добавляется объявление массива спрайтов.
Sprite[] meteorite = new Sprite[10];
В этой строке происходит объявление массива данных, исчисляемого десятью спрайтами. Каждый спрайт использует анимационную последовательность, а все изображения, загружаемые в игру, размещаются в рабочем каталоге проекта в папке Content\Textures. После этого в конструкторе класса Game1 с помощью цикла for происходит создание всех объектов, поскольку все создаваемые объекты имеют одинаковую анимационную последовательность.
for (int i = 0; meteorite.Length > i; i++)
{
meteorite = new Sprite(5, 10);
}
Для формирования метеоритов используется конструктор класса Sprite, подготовленный для создания анимированных объектов. В качестве параметров в конструктор класса Sprite передаются два целочисленных значения. Первое значение – это количество фреймов анимационной последовательности для загружаемого в игру изображения. Второй параметр конструктора класса Sprite задает определенное значение, на основе которого происходит расчет времени задержки для показа одного фрейма анимационной последовательности. Чем больше это значение, тем быстрее анимация на экране. Создав такой вот массив данных, можно переходить к методу LoadGraphicsContent() и к загрузке в массив графических изображений метеоритов.
Названия метеоритов заданы в виде цифровых значений, чтобы вам было проще работать с массивом данных, который также ведет свое исчисление от нуля. Теперь перейдем к первичной установке всех спрайтов на свои игровые позиции, которые осуществляются в методе Initialize().
j = 0;
for (int i = 0; meteorite.Length > i; i++)
{
meteorite.spritePosition = new Vector2(rand.Next(10,
screenWidth - 100), j = j - 500);
}
Рис. 7
Как видите, для выбора игровых позиций используется цикл. В качестве координаты по оси X для каждого спрайта применяется механизм случайного выбора позиции по ширине игровой области экрана с помощью метода Next(). Этот метод позволяет генерировать случайные числа в заданном диапазоне. В нашем случае заданный диапазон чисел – это размер ширины экрана телевизора (от 0 и до 1280 – ширина метеорита). Такой подход позволит нам выбирать для метеорита случайную позицию по оси Х. Установка точки отсчета по оси Y проходит в несколько другом ключе с использованием переменной j. На каждой новой итерации цикла значение этой переменной уменьшается на 500 пикселей, что позволяет установить все спрайты как бы друг за другом на расстоянии 500 пикселей и избежать накладки одного изображения на другое (рис. 4). Далее в исходном коде класса Game1 появляется новый метод MoveSprite(), где пишется код для реализации движения спрайтов в пространстве.
public void MoveSprite()
{
…
}
Метод MoveSprite() большой по своему размеру и целиком его исходный код не поместится в колонку журнала, так что изучайте его непосредственно в открытом проекте студии разработки игр. После того как все метеориты установлены на свои позиции, мы задаем им скорость движения и перемещаем сверху вниз (рис. 5). Как только метеорит прошел через экран телевизора и исчез из нашей зоны видимости в нижней части экрана, мы задаем этому метеориту новую позицию на экране, а точнее «закидываем» его опять вверх и перемещаем вниз (рис. 6). Таким образом, в игре мы обеспечиваем очень плотный и бесконечный метеоритный дождь. Конечно, это самый простейший алгоритм искусственного интеллекта, который можно использовать в играх, но для вашей первой игры этот подход как нельзя кстати. Далее в методе Update() класса Game1 нам нужно вызывать метод UpdateFrame() класса Sprite, чтобы при движении метеориты еще и анимировались.
Для обновления анимационной последовательности методом UpdateFrame() используется цикл. В качестве параметра в методе UpdateFrame() класса Sprite передается значение переменной elapsed. Эта переменная при каждой итерации игрового цикла получает текущее значение времени, прошедшее за один игровой цикл или за один проход по коду метода. А уже в методе UpdateFrame() класса Sprite происходит сравнение времени задержки на показ одного фрейма с прошедшим временем и решается, показывать по-прежнему этот фрейм анимационной последовательности или переходить к следующему фрейму. Затем в самом конце исходного кода класса Game1 в методе Draw() нам нужно нарисовать на экране все добавленные в игру метеориты. Теперь откомпилируйте проект Meteorite, передайте готовое приложение на приставку Xbox 360 и запустите программу на исполнение. На экране телевизора вы увидите летящие навстречу космическому кораблю метеориты.
Рис. 4Рис. 5<<4-я часть «Игры своими руками»6-я часть «Игры своими руками»>>
459 Прочтений • [Игра для Xbox 360 своими руками. Часть 5] [12.04.2012] [Комментариев: 0]