Программирование компьютерных игр под Windows в XNA Game Studio Express Горнаков С. Г.
Москва, 2008
УДК 004.4 ББК 32...
17 downloads
1324 Views
19MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Программирование компьютерных игр под Windows в XNA Game Studio Express Горнаков С. Г.
Москва, 2008
УДК 004.4 ББК 32.973.26018.2 ***
Содержание ***
Горнаков С. Г. Программирование компьютерных игр под Windows в XNA Game Studio Express. – М.: ДМК Пресс, 2008. – 384 с.: ил. ISBN *** Это серия книг настольной библиотеки начинающего программиста игр. На данный момент серия состоит из двух книг и поможет программистам изучить технику разработки игр для системы Windows и Xbox 360. В этом издании с помощью студии XNA Game Studio Express рассматривается пол( ный цикл создания компьютерных игр для операционной системы Windows. Изучая эту книгу, вы освоите основы работ с инструментариями Visual C# Express и XNA Game Studio Express. Научитесь работать с двухмерной и трехмерной графикой, анимацией, познакомитесь с техникой создания иг( ровых классов и формированием механизма игровых состояний. Овладеете секретами создания интерактивных заставок и меню, работой с устройства( ми ввода и звуком. Итогом книги станет создание двухмерной и трехмер( ной игры с формированием инсталляционного пакета. На базе полученных знаний вы сможет создавать свои собственные компьютерные игры и про( давать или распространять их бесплатно через Интернет. В дополнение на диске имеется потрясающая подборка материала по технике разработки компьютерных игр для операционной системы Windows! УДК 004.4 ББК 32.973.26018.2 Все права защищены. Любая часть этой книги не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения вла( дельцев авторских прав. Материал, изложенный в данной книге, многократно проверен. Но поскольку вероятность технических ошибок все равно существует, издательство не может гарантировать абсолютную точность и правильность приводимых сведений. В связи с этим издательство не несет ответ( ственности за возможные ошибки, связанные с использованием книги.
ISBN ***
© Горнаков С. Г., 2007 © Оформление, ДМК Пресс, 2008
Предисловие ........................................................................... 11 О чем эта книга ............................................................................ 12 Структура книги ........................................................................... 12 Часть первая ............................................................................... 12 Часть вторая ................................................................................ 12 Часть третья ................................................................................ 12 Что вы должны знать .................................................................... 12 Что вы должны иметь ................................................................... 13 Диск ............................................................................................. 13 Благодарности ............................................................................. 13 Об авторе ..................................................................................... 13
Часть 1 Введение в программирование игр Глава 1 В качестве вступительного слова .................................. 14 1.1. Этап проектирования игры .................................................... 16 1.2. Двухмерная игра ................................................................... 16 1.3. Трехмерная игра ................................................................... 17 1.4. Исходные коды проектов ....................................................... 18
Глава 2 Платформа XNA .................................................................... 19 2.1. И был сначала DirectX ............................................................ 19 2.1.1. Уровни абстракции .............................................................. 20 2.2. Новые реалии ........................................................................ 20 2.3. Managed DirectX ..................................................................... 21 2.4. Платформа XNA Framework .................................................... 22
4
Программирование компьютерных игр под Windows в XNA Game Studio Express 2.4.1. Уровни абстракции XNA ............................................................. 2.4.2. Application Model ........................................................................ 2.4.3. Компонент Content Pipeline ........................................................ 2.4.4. Компонент Graphics ................................................................... 2.4.5. Компонент Math ......................................................................... 2.4.6. Компонент Input ......................................................................... 2.4.7. Компонент Audio ........................................................................ 2.4.8. Компонент Storage .....................................................................
Содержание 23 24 27 28 29 30 30 30
Глава 3 Инструментарий Visual C# Express Edition ............... 31 3.1. Инструментарий Visual Studio 2005........................................ 31 3.2. Инструментарий Visual C# Express ........................................ 32 3.3. Установка Visual C# Express вместе с Visual Studio 2005 ........ 34 3.3.1. Механизм установки ............................................................ 34 3.4. Регистрация Visual C# Express ............................................... 41 3.5. Основы работы с Visual C# Express ........................................ 43 3.5.1. Создаем простой проект ...................................................... 45 3.5.2. Компиляция и запуск проекта ............................................... 47 3.5.3. Сборка проекта ................................................................... 48
Глава 4 Студия разработки игр XNA Game Studio Express ........................................................................................ 50 4.1. Студии разработки игр .......................................................... 50 4.1.1. XNA Game Studio Express ...................................................... 50 4.1.2. XNA Game Studio Professional ................................................ 52 4.1.3. XNA Studio .......................................................................... 52 4.2. Установка XNA Game Studio Express ...................................... 53 4.3. Знакомство с XNA Game Studio Express ................................. 54 4.3.1. Настройка получения новостей в XNA Game Studio Express ....... 56 4.3.2. Шаблоны XNA Game Studio Express ............................................ 58
Часть 2 Создаем двухмерную игру Глава 5 Формируем каркас игровых классов ......................... 60 5.1. Формируем проект в Visual C# Express .................................. 61
5
5.2. Структура проекта ................................................................. 62 5.3. Класс Program ....................................................................... 63 5.4. Класс Game1 ......................................................................... 66 5.5. Механизм работы программы ............................................... 72 5.6. Переходим в полноэкранный режим ..................................... 73
Глава 6 Работа с двухмерной графикой .................................... 78 6.1. Система координат ............................................................... 78 6.2. Проект DrawSprite .................................................................. 79 6.3. Проект DrawSpriteClass.......................................................... 86 6.3.1. Класс Sprite проекта DrawSpriteClass ..................................... 87 6.3.2. Класс Game1 проекта DrawSpriteClass .................................... 90
Глава 7 Спрайтовая анимация ........................................................ 93 7.1. Проект Animation .................................................................... 94 7.1.1. Анимационная последовательность ....................................... 94 7.1.2. Класс Sprite проекта Animation .............................................. 95 7.1.3. Класс Game1 проекта Animation ............................................. 99 7.2. Проект Background .............................................................. 103
Глава 8 Движение спрайтов в пространстве .......................... 109 8.1. Проект MoveSprite ............................................................... 112 8.2. Проект MoveSpriteArray ....................................................... 116
Глава 9 Устройства ввода ................................................................ 125 9.1. Проект Platform .................................................................... 125 9.2. Проект PauseGame .............................................................. 131
Глава 10 Игровые столкновения ..................................................... 136 10.1. Структура BoundingBox ..................................................... 136 10.2. Проект Collision .................................................................. 138
6
Программирование компьютерных игр под Windows в XNA Game Studio Express
Глава 11 Подсчет очков и вывод текста на экран ................. 149 11.1. Подсчет очков .................................................................... 150 11.2. Работа с текстом ............................................................... 151 11.2.1. Готовое решение XNAExtras ............................................... 153 11.2.2. Как это работает? ............................................................ 153 11.2.3. Как создать шрифт? ......................................................... 154 11.2.4. Добавляем в проект необходимые компоненты ................... 154 11.2.5. Работа с текстом ............................................................. 157
Глава 12 Создаем игровое меню .................................................... 165 12.1. Планируем запуск меню .................................................... 165 12.2. Проект Menu ...................................................................... 166 12.2.1. Класс Menu ..................................................................... 168 12.2.2. Загружаем в игру меню ..................................................... 172 12.3. Проект MenuCursor ............................................................ 182
Глава 13 Звуковые эффекты ............................................................. 195 13.1. Создаем проект XACT ........................................................ 195 13.1.1. Пошаговая инструкция ..................................................... 196 13.2. Класс Sound....................................................................... 201 13.3. Воспроизведение звука в игре .......................................... 206 13.4. Цикличное воспроизведение музыки ................................ 213
Глава 14 Добавляем в игру новые уровни ................................ 214 14.1. Переход с уровня на уровень ............................................. 215 14.2. Набранные очки ................................................................. 216 14.3. Проект NewLevels .............................................................. 217 14.3.1. Изменения в классе Game1 ............................................... 218
Глава 15 Формируем инсталляционный пакет ....................... 235 15.1. Программа Smart Install Maker ........................................... 236 15.1.1. Информация ................................................................... 236 15.1.2. Файлы ............................................................................ 238
Содержание
7
15.1.3. Требования ..................................................................... 241 15.1.4. Интерфейс ...................................................................... 242 15.1.5. Диалоги .......................................................................... 243 15.1.6. Ярлыки ........................................................................... 245 15.1.7. Деинсталлятор ................................................................ 246
15.2. Инсталляция созданной программы .................................. 247 15.2.1. Окно приветствия ............................................................ 249 15.2.2. Лицензия ........................................................................ 249 15.2.3. Выбор директории ........................................................... 250 15.2.4. Выбор места установки ярлыков ........................................ 250 15.2.5. Начало установки программы ............................................ 251 15.2.6. Установка программы ...................................................... 251 15.2.7. Окончание установки программы ....................................... 252
Часть 3 Создаем трехмерную игру Глава 16 Основы программирования трехмерной графики ................................................................................... 253 16.1. Система трехмерных координат ........................................ 253 16.2. Точки и вершины ............................................................... 255 16.3. Модель .............................................................................. 256 16.4. Матрицы ............................................................................ 257 16.4.1. Сложение и вычитание матриц .......................................... 257 16.4.2. Умножение матриц ........................................................... 258 16.5. Матричные преобразования .............................................. 258 16.5.1. Мировая матрица ............................................................ 259 16.5.2. Матрица вида .................................................................. 261 16.5.3. Матрица проекции ........................................................... 261 16.6. Свет ................................................................................... 262 16.7. Шейдеры ........................................................................... 263 16.7.1. Шейдерная модель .......................................................... 263 16.7.2. Механизм работы шейдерных программ ............................ 265 16.7.3. Вершинные и пиксельные шейдеры ................................... 265 16.7.4. Подводя итоги шейдерной технологии ............................... 266 16.8. Графический конвейер ...................................................... 267
Глава 17 Смена игровых состояний ............................................... 269 17.1. Автоматический подбор разрешения экрана .................... 269 17.2. Улучшаем смену игровых состояний ................................. 271
8
Программирование компьютерных игр под Windows в XNA Game Studio Express
Глава 18 Загружаем в игру модель .............................................. 277 18.1. Рисуем модель на экране монитора .................................. 277 18.1.1. Механизм загрузки модели в игру ...................................... 279 18.1.2. Метод DrawModel() .......................................................... 283 18.2. Класс ModelClass ............................................................... 290 18.3. Создаем объект класса LoadingModelClass ....................... 291
Содержание
9
22.2.1. Как сделана титульная заставка ............................................. 333 22.2.2. Разрабатываем класс SplashScreen ....................................... 334
22.3. Заставки Помощь, Об игре и Книги ................................... 338 22.4. Создаем меню игры .......................................................... 341 22.5. Смена игровых состояний в классе Game1 ........................ 347 22.5.1. Создаем объекты для меню и заставок .................................. 348 22.5.2. Обновляем состояние игры в методе Update() ....................... 348 22.5.3. Обновляем графику в методе Draw() ...................................... 353
22.6. Добавим в игру логику ....................................................... 355
Глава 19 Движение моделей в пространстве ........................... 295 19.1. Задаем скорость движения модели ................................... 295 19.2. Создаем массив данных .................................................... 296 19.3. Инициализация и установка моделей на позиции ............. 296 19.4. Установка матриц .............................................................. 298 19.5. Формируем метод для перемещения моделей .................. 298 19.6. Случайный выбор позиции на экране ................................ 299
Глава 20 Стреляем по целям ............................................................ 305 20.1. Класс для работы с двухмерными изображениями ............ 305 20.2. Задаем радиус для мячей .................................................. 305 20.3. Рисуем на экране прицел .................................................. 306 20.4. Получаем координаты прицела ......................................... 307 20.5. Целимся и стреляем .......................................................... 308
Глава 21 Формируем трехмерную сцену ................................... 317 21.1. Изменяем позицию камеры ............................................... 317 21.2. Загружаем в игру стадион ................................................. 318 21.3. Новые игровые позиции для мячей ................................... 319 21.4. Падение мячей на поле стадиона ...................................... 320 21.5. Небо и тучи ........................................................................ 321
Глава 22 Последние штрихи ............................................................. 330 22.1. Схема работы меню и показ заставок ................................ 330 22.2. Титульная заставка ............................................................ 332
Приложение 1 Обзор компакт-диска ....................................................... 358 Приложение 2 Интернет-ресурсы ............................................................... 359 2.1. Русскоязычные ресурсы ...................................................... 359 2.1.1. http://www.xnadev.ru ................................................................ 2.1.2. http://www.gamedev.ru ............................................................. 2.1.3. http://dft.ru .............................................................................. 2.1.4. http://www.kriconf.ru ................................................................ 2.1.5. http://www.codeplex.com .......................................................... 2.1.6. http://www.gamedev.kz ............................................................. 2.1.7. http://www.render.ru ................................................................. 2.1.8. http://www.xboxrussia.ru ........................................................... 2.1.9. http://www.xboxland.net ............................................................ 2.1.10. http://www.dmk]press.ru ......................................................... 2.1.11. http://www.gornakov.ru ...........................................................
359 359 359 361 361 361 363 363 363 365 365
2.2. Англоязычные ресурсы........................................................ 365 2.2.1. http://www.xbox360.com ........................................................... 2.2.2. http://creators.xna.com ............................................................. 2.2.3. http://www.xna.com ................................................................... 2.2.4. http://www.microsoft.com/xna ................................................... 2.2.5. http://www.msdn.com/xna ........................................................ 2.2.6. http://blogs.msdn.com/xna ....................................................... 2.2.7. http://forum.microsoft.com ....................................................... 2.2.8. http://www.ziggyware.com ........................................................ 2.2.9. http://www.xna101.net .............................................................. 2.2.10. http://xbox360homebrew.com ................................................. 2.2.11. http://www.garagegames.com ................................................. 2.2.12. http://learnxna.com ................................................................. 2.2.13. http://www.turbosquid.com/xna ............................................... 2.2.14. http://www.xnaresources.com .................................................
367 367 367 369 369 369 369 371 371 371 373 373 373 373
10
Программирование компьютерных игр под Windows в XNA Game Studio Express 2.2.15. http://www.xnadevelopment.com ............................................. 2.2.16. http://xnamagic.com ............................................................... 2.2.17. http://blog.tehone.com ........................................................... 2.2.18. http://www.student.dtu.dk ....................................................... 2.2.19. http://www.dhpoware.com ....................................................... 2.2.20. http://www.nuclex.org ............................................................. 2.2.21. http://www.xnaportal.com ....................................................... 2.2.22. http://www.xnatutorial.com ......................................................
376 376 376 376 378 378 378 378
Предметный указатель .................................................... 381
Предисловие Прежде всего хочу поблагодарить вас за то, что вы приобрели эту книгу или обратили на нее свое внимание! В какой(то момент времени рынок компьютерных игр словно «завис». Создалось такое впечатление, что интерес со стороны простых пользователей несколько угас и та былая игровая активность просто пропала. Что называется, сели батарейки. И вдруг компания Microsoft «выкидывает» на рынок студию разработки игр XNA Game Studio Express, с помощью которой практически любой пользователь, зна( комый с элементарными основами языка программирования C#, может создать свою игру. В этот самый момент батарейки моментально сменились на долгоиграющие аккумуляторы, кнопка Reset была нажата, а миллионы людей бросились к компьюте( рам создавать свои маленькие, но греющие душу аркады, стрелялки, леталки, пи( щалки, лягалки, пускалки! Инновация Microsoft в этом плане заключается в том, что она предоставляет шанс огромному количеству программистов реализовать себя и создать игру лю( бого уровня. До этого момента, например, разработка игр для Xbox 360 была уде( лом отдельно взятых компаний с проверенной временем репутацией и хорошими финансовыми возможностями. Комплект разработчика для приставки Xbox 360 стоил порядка 20–30 тыс. долл., для простого программиста(одиночки эта сумма практически не подъемная. Ко всему прочему комплект разработчика, даже при наличии нужной суммы денег, просто так и кому попало не продадут. Ситуация с XNA Game Studio Express прямо противоположна. Студия разра( ботки игр бесплатна и доступна для свободного скачивания. На базе этой студии вы можете создавать свои игры для операционной системы Windows без каких( либо ограничений и лицензий. Эти игры можно продавать, распространять бес( платно и т. д. Ограничений в этом плане нет никаких: сделайте любую игру (2D или 3D) – и продавайте ее через Интернет или любым другим удобным для вас способом! Для приставки Xbox 360 в дальнейшем, по всей видимости, будет предусмот( рена система отбора игр. В настоящее время XNA Game Studio Express находится на этапе становления, и вы можете запускать игры, сделанные в XNA Game Studio Express, на своей приставке, только если вступите в клуб разработчиков игр. Но уже сейчас на сайте http://creators.xna.com для любого человека доступно участие в конкурсе разработки игр, где предусмотрено 20 ценных денежных призов. Все говорит о том, что XNA Game Studio Express – это серьезный инструмент, и Microsoft связывает с этим программным продуктом большие надежды.
12
Программирование компьютерных игр под Windows в XNA Game Studio Express
Предисловие
13
О чем эта книга
Что вы должны иметь
Издание имеет две разные версии книг. В этих книгах рассматривается процесс создания игр для операционной системы Windows и приставки Xbox 360. Цель именно этой книги заключается в том, чтобы научить читателя создавать двух( мерные и трехмерные компьютерные игры. В книге шаг за шагом и от простого материала к сложному вы пройдете этапы формирования двух игр для операци( онной системы Windows с применением инструментария Visual C# Express и сту( дии разработки игр XNA Game Studio Express.
Компьютерную систему с установленной на ней операционной системой от вы( пуска Windows XP и выше, а также диск к этой книге, который поставляется в комплекте с изданием.
Структура книги Часть первая Первая часть книги – это вводный курс в разработку и программирование игр на языке C# с использованием инструментария Visual C# Express и студии разработ( ки игр XNA Game Studio Express. Первые четыре главы этой книги познакомят вас с архитектурой платформы XNA Framework и научат работать с программны( ми продуктами Visual C# Express и XNA Game Studio Express.
Часть вторая Эта часть книги направлена на создание двухмерной игры для операционной сис( темы Windows. Здесь читатель пройдет все этапы создания простой двухмерной игры, сформировав в конечном счете полноценный инсталляционный пакет, гото( вый к установке на любой компьютерной системе.
Часть третья В третьей части книги читатель на конкретном примере познакомится с создани( ем небольшой трехмерной игры.
Что вы должны знать Книга рассчитана на широкий круг читателей и рекомендована к изучению как начинающим программистам, так и более опытным. В книге предполагается, что читатель знаком с основами языка программирования C# и может создать и от( компилировать простейший проект. Тем не менее в первой части книги предлага( ются к изучению основные приемы работы с инструментарием Visual C# Express и XNA Game Studio Express, что дает возможность работать с этой книгой челове( ку, незнакомому с данными программными продуктами.
Диск На диске находятся примеры и исходные коды, созданные за время изучения кни( ги, а также инсталляционные пакеты студии разработки игр XNA Game Studio Express и инструментария Visual C# Express. Сторонние библиотеки, программы, примеры и Starter Kits с сайта XNA Creators Club, а также методические материа( лы с Gamefest 2006 и 2007 по разработке игр для Windows!
Благодарности Хочу поблагодарить всех тех людей, которые так или иначе были связаны с выхо( дом этой книги в свет! Персональные благодарности издательству ДМК Пресс и генеральному директору Дмитрию Алексеевичу Мовчану. Особые слова благодарности моей жене Светлане за все рисунки, сделанные к обеим книгам и играм. Спасибо тебе, дорогая, за помощь!
Об авторе Горнаков Станислав Геннадьевич – профессиональный программист в области создания мобильных и компьютерных игр. Автор книг: «Программирование игр для приставки Xbox 360 в XNA Game Studio Express», «Инструментальные сред( ства программирования и отладки шейдеров в DirectX и OpenGL», «DirectX 9. Уроки программирования на С++», «Symbian OS. Программирование мобильных телефонов на С++», «Программирование мобильных телефонов на Java 2 ME», «Самоучитель работы на смартфонах и коммуникаторах под управлением Sym( bian OS» и «Самоучитель работы на КПК, смартфонах и коммуникаторах под уп( равлением Windows Mobile». Больше информации о книгах автора вы можете найти в Интернете по адресу: http://www.gornakov.ru.
В качестве вступительного слова
Часть 1 Введение в программирование игр
15
В свою очередь, казуальные игры – это огромная индустрия развлечений с боль( шим рынком сбыта по всему миру. Для начала рекомендую вам посетить сайт Ев( ропейской конференции казуальных игр (Casuality Europe: East 2007). На этом сайте вы найдете массу ссылок на компании, которые занимаются распростране( нием казуальных игр и которые с большим удовольствием помогут продвинуть на рынок предлагаемую вами игру. Например, всем известная компания Alawar (ад( рес в Интернете www.alawar.ru) занимается тем, что помогает разработчикам про( двигать свой товар на рынке (рис. 1.1).
Глава 1 В качестве вступительного слова Важно понимать, что процесс создания игры – это очень трудоемкое дело, отни( мающее огромное количество времени из жизни тех людей, которые делают эту работу. Самому, в одиночку, создать полноценную игру фактически нереально, и тем более при всех нынешних технологиях и требованиях к графической кар( тинке. Никогда не пытайтесь в своем первом проекте делать игры плана Gears of War или Need for Speed! Нельзя откусить больше, чем можно прожевать, не говоря уже о том, что все откушенное нужно проглотить, да еще и не поперхнуться. Создание таких боль( ших и мощных игр – это удел игровых студий с огромным штатом программис( тов, художников, дизайнеров, аниматоров, модельеров… Если вы в одиночку или небольшим коллективом решите создавать нечто подобное, то скорее всего ваш проект растянется на долгие, долгие годы работы, а за это время все ваши графические инновации станут просто неактуальными. Эта аксиома проверена временем, а ее несоблюдение сгубило уже множество коллективов и программи( стов(одиночек. Тогда возникает вопрос: а что тогда можно программировать? Чаще всего программисты(одиночки или коллективы единомышленников создают неболь( шие, так называемые казуальные игры, или игры, распространяемые через Интер( нет, стоимость которых составляет не более пары десятков долларов. Набив руку на этом поприще и небольших проектах, эти самые коллективы либо перерастают в более мощные игровые студии, либо сотрудники фирмы находят себе хорошую работу в стане сильных компаний.
Рис. 1.1. Сайт компании Alawar
В контексте этой книги мы постараемся создать нечто подобное и похожее на казуальную игру. Цель книги заключается в том, чтобы научить вас создавать двухмерные и трехмерные игры для операционной системы Windows. Самый луч( ший способ научиться программировать игры – это сделать игру своими соб( ственными руками, поэтому на протяжении всей этой книги мы будем создавать две небольшие, пусть и простенькие, но зато свои игры. В дальнейшем на базе полученных знаний, применив все свое желание и не( реализованные идеи, вы вольны сделать нечто большее и лучшее, чем описано в этой книге (я на это искренне надеюсь). Свою задачу в этом плане я вижу в том, чтобы дать вам необходимый запас знаний для старта и толчок в реализации сво( их идей, все остальное зависит исключительно от вас…
16
Трехмерная игра
В качестве вступительного слова
1.1. Этап проектирования игры Это очень важный и главный этап работы над игрой. На этой стадии необходимо решить массу различных организационных вопросов и выработать общую кон( цепцию игры, создать документацию к игре, проработать дизайн уровней, персо( нажей, спроектировать игровую модель, сформировать набор игровых классов, разделить проектные задания среди персонала и многое, многое другое. По этой теме написано немало книг, но у нас есть одна небольшая проблема. Дело в том, что вы не умеете программировать игры и до сих пор не сделали ни одной игры. Вы не имеете представления о том, как устроен каркас игровых классов, что нуж( но сделать для того, чтобы загрузить в игру графику, и т. д. Поэтому для книги была придумана своя модель реализации игр. Вместо большого этапа проектирования игры мы с вами в каждой последую( щей главе, строка за строкой, будем писать исходный код игры. Наш каждый сле( дующий проект будет модернизировать предыдущий. Например, в двухмерной игре мы сначала создадим пустой проект, затем выберем разрешение для экрана. Загрузим в игру простое графическое изображение, нарисуем его на экране. Со( здадим анимацию, увеличим на экране количество изображений, добавим фон, рассмотрим работу с клавиатурой и мышью. Изучим механизм игровых столк( новений, добавим в игру меню и заставки – и в итоге создадим своими руками простую двухмерную игру, а весь процесс работы над игрой станет для вас про( стым учебным пособием, изучив которое вы в дальнейшем сможете создавать свои проекты. Что касается серьезного подхода в этапе проектирования игры, а также со( ставления необходимой документации к игре, то могу вам порекомендовать инте( ресные материалы на эту тему. В первую очередь это, конечно, книги по этой те( матике, которые можно найти в магазинах, но, пожалуй, самое главное, что можно посоветовать по этой теме, – это документация от ведущих отечественных компа( ний разработчиков игр. Не поленитесь и зайдите на сайт компании 1C в раздел Разработчикам. На этой странице сайта для свободного скачивания доступен потрясающий набор документации, рассказывающий о том, как правильно можно создать дизайн(до( кумент для игры, с чего лучше начать и как лучше всего представить свою игру издателю и т. д. Это просто потрясающая подборка материала, которая проверена жизнью. Аналогичная документация имеется на сайтах компании Бука и Alawar. Это лучшее, что можно порекомендовать вам в этом плане.
1.2. Двухмерная игра Теперь несколько поясняющих слов об идее двухмерной игры. Основной задачей игры является разностороннее изучение способов работы с двухмерной графи( кой. В этом контексте базовой идеей для игры послужили игры, где пользователь должен ловить некоторые предметы, падающие с неба. За каждый такой пойман(
17
ный объект пользователю начисляются очки. Дополнительно с неба падают объ( екты, которые ловить не нужно, за столкновение с такими объектами очки соот( ветственно отнимаются. По достижении определенного порога очков пользова( тель переходит на следующий уровень. Это простой показательный пример, с помощью которого мы сможем просле( дить за сменой игровых состояний, переходом на новые уровни игры, движением объектов на экране, анимацией, столкновением, работой с клавиатурой и т. д. Главное – разобраться с техникой программирования игр и внутренним меха( низмом работы платформы XNA. Все остальное, в том числе графика, – дело вто( ричное. Оформление и антураж игры – также дело личных и коммерческих предпоч( тений. Предлагаемая идея игры, чтобы вам было не скучно, «одета» в обвертку индейских прерий. Соответственно игра носит название «Летящие в прерии»… Предположительно все действия в игре разворачиваются в прериях, над которы( ми терпит крушение самолет, а люди и предметы с самолета падают с неба. У вас как у игрока имеется специальная платформа (с матрасом…), с помощью которой вы должны спасти как можно больше падающих людей. Платформа перемещает( ся исключительно в нижней части экрана с помощью клавиш с командами Влево и Вправо, и вам нужно подставить ее под падающего человека. Спасли человека – получите очки, набрали потолок очков для текущего уровня – переходите на сле( дующий уровень и т. д.
1.3. Трехмерная игра В трехмерной игре, так же как и в двухмерной, главной задачей является возмож( ность всестороннего рассмотрения методов работы с 3D(моделями и графикой. По большому счету, работать с трехмерной графикой на порядок сложнее, чем с двухмерной, поэтому уровень изучаемой игры невысок, но этого уровня вполне достаточно, чтобы понять, как работать с 3D(графикой и в каком направлении необходимо двигаться дальше. В качестве примера в книге была сделана игра «Футбольный стрелок». Суть игрового процесса этой игры состоит в следующем. Сверху на футбольное поле падают три мячика. Задача игрока заключается в том, чтобы стрелять по мячам (курсор мыши – это мушка), подбивая мячи тем самым вверх, не давая в конечном счете одному из мячей коснуться футбольного поля. Как только один из мячей касается футбольного поля, наступает окончание текущего уровня, и игроку предлагается переиграть этот уровень заново. Для того чтобы перейти на новый уровень, необходимо подбить каждый из мячей по двадцать раз. Самое интересное заключается в том, что даже если игрок подбил один из мячей 20 раз, то мяч все равно будет падать на землю, но его прио( ритетность в подбитии значительно уменьшается. Как только все три мяча будут подбиты по двадцать раз, то считается, что текущий уровень пройден. На новом уровне скорость падения мячей увеличивается. Подробнее о способах реализации
18
В качестве вступительного слова
игры и используемой графике вы узнаете из третьей части книги, но изучать кни( гу необходимо строго в хронологическом порядке.
1.4. Исходные коды проектов Я по(прежнему остаюсь сторонником книг, в которых дается полный исходный код рассматриваемого примера. Не всегда удобно, а порой и невозможно иметь под рукой компьютер для просмотра всего примера в целом. Поэтому в книге в каждой главе приводится полный листинг изучаемого в данный момент класса, но поскольку в каждой последующей главе мы модернизируем код, то часть кода методов может не изменяться. В связи с этим в некоторых главах код этих методов может быть вырезан за ненадобностью. Плюс все нововведения в коде выделяют( ся жирным шрифтом, чтобы вам было проще следить за изменениями. На этом небольшая вступительная часть этой главы подошла к концу, пора переходить от слов к делу!
Глава 2 Платформа XNA Разработка игр для компьютерных систем, консольных приставок и мобильных устройств – занятие трудоемкое. Если говорить только о специфике создания игр для любой из перечисленных платформ, то можно отметить, что подход в созда( нии игр у каждой платформы будет свой (по крайней мере, так было до недавнего времени). Более того, огромное количество устройств может иметь различное ап( паратное обеспечение. Если взять, к примеру, несколько компьютеров, то все они с большой долей вероятности будут компоноваться различными комплектующи( ми. И как быть в этом случае бедному программисту, которому нужно попытаться создать игру для всех этих разных устройств? Ответ прост: необходимы общие стандарты и спецификации. И они есть, за что им большой и огромный респект от всех нас. Конечно, не все в этом мире иде( ально и гармонично, те же стандарты и спецификации бывают разными, но! Для компьютерных систем есть свой стандарт – это DirectX и теперь XNA Framework, для мобильных устройств Java 2 ME, Symbian OS & C++ и Windows Mobile & C++ и C#, для Xbox 360 тоже появился свой стандарт в виде платформы XNA. В целом все постепенно унифицируется и сводится к одному стандарту, по край( ней мере, в семействе продуктов Microsoft. Кстати, я больше чем уверен, что вы не очень сильно любите Microsoft и дядюшку Билла, но при этом все равно пользуе( тесь Windows на своем компьютере. Но это не главное, главное другое. Представьте, если бы у нас сейчас было 10 разных и мощных операционных систем от разных компаний. Думаю, что все эти компании ни за что и никогда не договорились бы об общем стандарте в разработке игр для своих операционных систем. И тогда у нас на рынке сейчас имелся бы не DirectX 10, а десять разных DirectX’ов (или как бы они там назывались)… Со стороны пользователя конку( ренция – это просто замечательно, а вот со стороны программиста куча разных стандартов в одной сфере – это ой как тяжело.
2.1. И был сначала DirectX Осознав факт необходимости целостной спецификации, корпорация Microsoft много лет назад попыталась создать стандартный набор игровых библиотек для операционной системы Windows. В 1995 году на свет появилась первая версия DirectX 1.0 (изначально, правда, была еще одна ранняя версия с названием WinG, но сейчас это не столь важно). Эта версия игровой библиотеки (API) была построе( на на базе библиотеки Reality Lab компании RenderMorfics, которую Microsoft прикупила по этому случаю.
20
Managed DirectX
Платформа XNA
Первый блин, как всегда, комом, поэтому и у DirectX не все сложилось сразу. Выходили новые версии библиотек, которые сменяли одна другую, а главное – постоянно изменялось содержимое самих библиотек. И только в 1999 году после выхода DirectX 7.1 (2D) и в 2001 году после выхода DirectX 8.1 (с полноценной 3D(поддержкой и первой версией шейдеров) появилась относительная стабиль( ность. Выход новой версии DirectX 9 в 2002 году привносит ряд изменений в API, но это все плановая модернизация, а не коренное изменение всей целостности биб( лиотеки. В период с 2004 по 2007 год DirectX 9 подвергался мощной перестройке, причем обновления происходили (особенно в 2006 году) буквально через каждые два(три месяца. И уже в 2007 году появилась новая библиотека DirectX 10. Все изменения в DirectX в основном связаны с перестройкой работы с 3D(гра( фикой и, главное, с некоторыми упрощениями в подходе создания конечного продукта. Нельзя сказать, что перестройка DirectX прошла безболезненно, в том числе и для нас с вами. Вы просто не представляете, какое количество писем я получил за это время! Люди, купившие мою книгу «DirectX 9. Уроки программи( рования на С++» (дата выхода – 2004 год), абсолютно не понимали, почему но( вый DirectX SDK не хочет работать с примерами, рассматриваемыми в книге! Но со временем все утряслось, и сейчас на рынке появились стабильные и мощные инструменты, направленные на создание хороших игр.
2.1.1. Уровни абстракции Схема взаимодействия DirectX и компьютерной системы на уровне абстракции может «вылиться» примерно в следующий рисунок (рис. 2.1). Как видно из этой схемы, любая игра базируется на классах DirectX, которые, в свою очередь, взаи( модействуют с операционной системой или специальными сервисами, включаю( щими в себя драйвера устройства и другие API. Такой подход в реализации многоуровневых прослоек позволяет программис( ту не обращать внимания на аппаратную часть устройства и спокойно работать с программным кодом. В итоге для программиста абсолютно не важно (в идеале), какое аппаратное обеспечение имеет это устройство, он работает непосредственно с кодом игры и библиотечными классами DirectX. Останавливаться подробно на устройстве и работе библиотеки DirectX в этой главе и всей книге мы не будем, поскольку к нашей теме DirectX не имеет прямого отношения и служит просто звеном одной цепочки, о котором обязательно нужно было упомянуть.
2.2. Новые реалии Время вносит в жизнь свои коррективы. В какой(то момент стали очень бурно развиваться мобильные устройства, а также у Microsoft появился определенный интерес к рынку консольных приставок. Этот интерес материализовался кон(
21
Рис. 2.1. Схема взаимодействия DirectX и аппаратной части устройства
солью Xbox, а затем появилась и вторая версия приставки с несколько видоизмененным названием Xbox 360. После этого корпорация Microsoft имела на руках три разные платформы – компьютерную Windows, мобильную Win( dows Mobile и консольную приставку. Для всех трех плат( форм игровая спецификация была абсолютно разной. Разработчики, создавая игру для PC, могли перенести ее на Xbox, но для этого приходилось тратить очень много времени. Порой проекты для обеих платформ делали две разные команды, а точнее одна команда создавала, а другая адаптировала. С этим нужно было что(то делать. Создание общей спецификации для всех платформ се( мейства Microsoft стало большой головной болью для про( граммистов, работающих в корпорации. Чтобы создать та( кую спецификацию, необходимо было создать общую инструментальную базу, один общий язык программирования, а также общую библиотеку классов. Но задача была решена. Сейчас в качестве инструментальной базы выступает инструментарий Vi( sual C# Express или Visual Studio. Общим языком программирования стал язык C#, который безусловно проще и лучше своих прародителей С/C++/Java. Общей библиотекой классов стала платформа XNA Framework, которая включает огром( ный набор системных инструментов для решения большинства серьезных задач. В итоге мы имеем одну спецификацию, меньше головных болей и безболезненное портирование игр с одной платформы на другую. Правда, перед XNA Framework у Microsoft был еще один промежуточный этап понимания того, как должна быть устроена подобная библиотека, и этот этап был ознаменован выходом Managed DirectX.
2.3. Managed DirectX Основная проблема переноса DirectX на все семейство продуктов Microsoft связа( на с тем, что библиотека DirectX построена на независимой спецификации моде( ли составных компонентов (COM – Component Object Model), которая, в свою очередь, очень сильно привязана к Windows и языку программирования С++. По( этому перенос DirectX на все продукты был просто невозможен. Необходимо было выдумать и реализовать нечто новое, простое, да так чтобы еще и работало на всех платформах сразу и одинаково. Вот так и родилась библиотека Managed DirectX для языка С# и .NET(плат( формы, которая по своей сути представляла системную надстройку классов над стандартным DirectX. В конечном счете на свет появились всего две версии Mana(
22
Платформа XNA Framework
Платформа XNA
ged DirectX. В данный момент эта технология пребывает в летаргическом сне и, по всей видимости, там и будет пребывать еще долгое время, поскольку от разви( тия этой платформы на неопределенное время пришлось отказаться. В основном проблема Managed DirectX и всего проекта в частности заключа( лась в том, что Managed DirectX – это всего лишь надстройка над DirectX. В мо( мент создания и развития Managed DirectX никто не принимал во внимание кон( соль Xbox 360, а также возможность портирования полноценных трехмерных игр на мобильные устройства под управлением Windows Mobile. Когда встал вопрос о том, чтобы абстрагироваться от привязанности к DirectX и Windows, выясни( лось, что с Managed DirectX сделать это будет очень(очень трудно. Вот тогда и начались разработки новой платформы, которая впоследствии получила назва( ние XNA Framework.
23
даже больше создадите за пару строк исходного кода, при этом эти строки сфор( мирует за вас сам компилятор и XNA Framework! Работать стало проще, быстрее, и главное – нет борьбы и драк с DirectX… Роль DirectX в этом деле – это низко( уровневая прослойка API, к которой вы не касаетесь никоим образом. Все мани( пуляции производятся только через библиотеку XNA Framework.
2.4.1. Уровни абстракции XNA Так же как и в случае с DirectX, мы можем графически представить взаимодей( ствие приложений через XNA Framework с аппаратной частью устройства. По( смотрите на рис. 2.2, где представлены уровни абстракции платформы XNA Framework.
На момент выхода книги работать с мобильными устройствами на базе Windows Mobile 5.0 можно в основном с языком программирования С++. В Visual Studio 2005 есть встроенные инструменты для работы с С# и .NET Compact Framework 1.0. В новой операционной системе Windows Mobile 6.0 используется уже вторая версия платформы .NET Compact Framework 2.0, которая значительно мощнее и лучше.
2.4. Платформа XNA Framework Платформа XNA Framework – это большой набор системных библиотек, построен( ных на базе .NET(библиотек и направленных на улучшение и упрощение созда( ния игр для всех продуктов семейства Microsoft. Создавая игру для PC на базе платформы XNA Framework, вы можете быть уверены, что эта игра будет работать на приставке Xbox 360 и в скором времени на новой системе Windows Mobile. Ис( ключения в этом случае могут составлять некоторые системные классы, которые необходимы только для работы с той или иной платформой. Например, приставка Xbox 360 не имеет мыши, поэтому в программах для консоли нельзя использовать методы и классы, направленные на работу с мышью. В свою очередь, для компьютерных игр можно задавать различные разрешения экрана, тогда как Xbox 360 использует в качестве монитора телевизор, который имеет свою специфику вывода изображения на экран. Эти и другие исключения составляют мышиную долю (примерно 3–5%) от всей библиотеки XNA Frame( work. При правильном подходе в использовании библиотечных классов можно написать программу, которая на 100% будет работать как на консоли, так и на компьютере и, надеемся, на Windows Mobile тоже. Еще одной важной особенностью платформы XNA Framework является зна( чительное упрощение в подходе реализации игр для всех платформ. Прежде чем начать писать именно исходный код игр с использованием DirectX SDK, необхо( димо создать большую кучу различных объектов, создать окно, настроить видео( адаптер и т. д. Применяя платформу XNA Framework, вы все перечисленное и
Рис. 2.2. Взаимодействия XNA и аппаратной части устройства
Как видно из рис. 2.2, схема работы XNA Framework напоминает схему работы с DirectX, но в основе библиотеки XNA лежат другие компоненты и механизмы взаимодействия с аппаратной частью устройства. На диаграмме также видно, что вся платформа XNA Framework состоит из трех уровней абстракции, через кото( рые программа обращается к системе.
24
Платформа XNA Framework
Платформа XNA Игра – это самый высокий уровень абстракции, включающий в себя исход( ный код игры, различные игровые данные и компоненты. Это все то, что вы пишите и создаете лично сами. Расширенный каркас (Extended Framework) – расширенный каркас сис( темных высокоуровневых классов содержит простые механизмы для рабо( ты с загрузкой моделей, текстур и других графических элементов. В состав этого каркаса входят Application Model и Content Pipeline. Со временем эта часть библиотеки может пополняться новыми компонентами, что по( зволит еще больше упростить создание игр. Методы, классы, структуры этого уровня абстракции можно смело использовать в своих программах. Основной каркас (Core Framework) – основной каркас библиотеки за( ключает в себе ядро платформы XNA Framework и обеспечивает базовые механизмы работы всей библиотеки в целом. Этот уровень абстракции со( держит несколько основных классов, таких как Graphics, Math, Input, Audio и Storage. Все классы упрощают работу соответственно с графикой, звуком, устройствами ввода информации, математическими операциями и работой с данными для записи или чтения их с файловой системы. Классы этого уровня вам также будут доступны в полном объеме. Со временем эта часть библиотеки может пополняться новыми компонентами. Платформа (Platform) – это самый нижний и независимый уровень абст( ракции или набор классов, осуществляющий обращение непосредственно к аппаратной части устройства. Доступ к компонентам этого уровня у про( граммиста есть, но если их использовать, то это значительно сузит пор( тирование созданного приложения на другие платформы. Более того, не рекомендуется использовать прямое обращение исходного кода игры к прослойке этих компонентов, и по большому счету ваша игра даже не должна подозревать об этом уровне абстракции. В состав этой прослойки входят DirectX, XACT, XInput и XContent.
Подводя промежуточный итог обзора платформы XNA Framework, можно констатировать, что механизмы работы как DirectX, так XNA Framework с аппа( ратной частью устройства почти одинаковы, но внутренние особенности обеих библиотек значительно отличаются друг от друга, и сейчас мы поговорим об этом более подробно.
2.4.2. Application Model В расширенном каркасе высокоуровневых классов и, в частности, в Application Model (Модель приложения) реализована общая структура работы модели при( ложения. Этот набор компонентов позволяет автоматизировать процесс создания игры и создает за вас механизм реализации оконного или полноэкранного прило( жения, организует необходимый в игре таймер, обрабатывает различные сообще( ния, управляет процессом рисования сцены на экране монитора (рендиринг), за( грузкой ресурсов игры и т. д.
25
Все перечисленные элементы преподносятся вам в виде готового шаблона игры с небольшим количеством строк исходного кода, в которые вам необходимо вставлять свой код игры. То есть за вас создается полнофункциональный каркас игрового приложения, готовый к «употреблению». Например, давайте посмотрим на исходный код каркаса игры, созданный шаблоном XNA Game Studio Express и Visual C# Express, представленный в листинге 2.1. // ============================================================================= /// <summary> /// Ëèñòèíã 2.1 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 2 /// Êëàññ: Game1 /// Ïðîñòîé øàáëîííûé ïðîåêò /// <summary> // ============================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace WindowsGame1 { /// <summary> /// Ýòî øàáëîí âàøåé èãðû /// public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); } /// /// /// /// ///
<summary> Ýòîò ìåòîä ïðåäíàçíà÷åí äëÿ èíèöèàëèçàöèè ðàçëè÷íûõ èãðîâûõ äàííûõ Çäåñü ìîæíî çàäàâàòü ðàçëè÷íûå ïåðâè÷íûå ñîñòîÿíèÿ èãðîâûì êîìïîíåíòàì Íî çäåñü íåëüçÿ çàãðóæàòü ãðàôèêó, ýòîò ìåòîä ïðåäíàçíà÷åí òîëüêî äëÿ èíèöèàëèçàöèè ïåðâè÷íûõ èãðîâûõ ñîñòîÿíèé
26
Платформа XNA Framework
Платформа XNA /// protected override void Initialize() { // Äîáàâüòå çäåñü êîä èíèöèàëèçàöèè äàííûõ base.Initialize(); } /// <summary> /// Ýòîò ìåòîä ïðåäíàçíà÷åí äëÿ çàãðóçêè ãðàôè÷åñêîé ñîñòàâëÿþùåé â èãðó /// protected override void LoadGraphicsContent(bool loadAllContent) { if(loadAllContent) { // Çäåñü ïðîèñõîäèò çàãðóçêà êîìïîíåíòîâ â èãðó } } /// <summary> /// Ìåòîä äëÿ îñâîáîæäåíèÿ çàõâà÷åííûõ ñèñòåìîé ðåñóðñîâ /// Ýòîò ìåòîä àâòîìàòè÷åñêè âûãðóæàåò çàãðóæåííûå êîìïîíåíòû /// ïðè âûõîäå èëè çàêðûòèè èãðû /// protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } } /// <summary> /// Çäåñü ïðîèñõîäèò îáíîâëåíèå ñîñòîÿíèÿ èãðîâûõ îáúåêòîâ, ëîãèêè, çâóêà, /// ïîëó÷åíèå ñîáûòèé ñ óñòðîéñòâ ââîäà è òàê äàëåå /// Ìåòîä ðåàëèçóåò ïðîñòîé òàéìåð /// protected override void Update(GameTime gameTime) { // Îáðàáîòêà ñîáûòèé, ïîëó÷àåìûõ ñ äæîéñòèêà if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // Äîáàâüòå ñâîé êîä äëÿ îáíîâëåíèÿ ñîñòîÿíèÿ èãðîâûõ îáúåêòîâ // Ýòî òàéìåð base.Update(gameTime); } /// <summary> /// Ðåíäèðèíã ñöåíû èëè âûâîä íà ýêðàí ãðàôèêè /// protected override void Draw(GameTime gameTime) {
27
graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // Äîáàâüòå çäåñü ñâîé êîä base.Draw(gameTime); } } }
Класс Game1 описан в файле с расширением *.cs (Game1.cs). Расширение в данном случае указывает на то, что используется язык программирования С#. Весь исходный код создан шаблоном, а перевод комментариев на русский язык осуществлен в вольной форме. Класс Game1.cs создал за нас полное игровое оконное приложение с метода( ми для загрузки графической составляющей игры, инициализации данных, систе( му вывода сцены на экран, систему обновления состояния игры (таймер) и даже обработку событий, получаемых с джойстика. Неплохо, не правда ли? Более под( робно с классом Game1 мы начнем знакомиться в следующих главах, когда при( ступим к работе над своей игрой.
2.4.3. Компонент Content Pipeline Одной из важнейших составляющих любой программы является графический и игровой контент, который включает в себя различные графические изображения (текстуры, спрайты…), звуковые файлы, модели и материалы. Если вспомнить методику загрузки графики в DirectX, то станет понятно, почему в XNA уделено этому большое внимание. В DirectX, чтобы загрузить модель, приходилось писать десяток(другой строк исходного кода! Более того, загружать можно было только определенные графи( ческие файлы, которые DirectX понимал. Для остальных моделей и изображений приходилось писать свои экспортеры или искать инструменты для конвертирова( ния контента в необходимый формат данных. Используя XNA Framework и Content Pipeline (Конвейер контента), который входит в пространство имен Microsoft.Xna.Framework.Content, вам не нужно беспокоиться о том, в каком формате представлено изображение или мо( дель. Вы просто пишете пару строк исходного кода, и ваш графический контент будет успешно загружен в игру внутренними средствами конвейера. В главе 6 мы подробно остановимся на этом вопросе. Конвейер контента понимает следующие форматы. Трехмерные модели – формат X и формат FBX. Формат Х – это всем изве( стный Х(файл, или мэш. Формат FBX – относительно новый формат в иг( ровой индустрии, продвигаемый компанией Autodesk, и поскольку эта же компания является создателем всем известного инструментария 3DMAX Studio, то сомнений в дальнейшем распространении этого формата нет. На сайте компании Autodesk можно бесплатно скачать плагин и конвертер для
28
Платформа XNA Framework
Платформа XNA
29
преобразования сделанной модели в 3DMAX Studio до версии 8 в формат FBX. В 3DMAX Studio 9 все перечисленные средства встроены по умол( чанию. Графические изображения – поддержаны наиболее распространенные графические форматы DDS, BMP, JPG, PNG и TGA. Материал – имеется поддержка формата FX, или формата шейдеров. Аудиоформат – осуществлена поддержка работы с проектами, созданны( ми при помощи XACT. Подробно о самом формате и загрузке в игру звуко( вых данных вы узнаете в главе 13. Как видите, список поддерживаемых форматов достаточно большой и внуши( тельный, но главное – это простота загрузки, например текстуры или модели в игру. Посмотрите на пример двух строк исходного кода, загружающих в про( грамму графическое изображение и модель. private Texture2D mySprite; private Model myBoll; … mySprite = content.Load(“sprite”) myBoll = content.Load<Model>(“boll”);
В последних двух строках этого блока кода мы загрузили в программу из корне( вого каталога проекта графическое изображение и модель (для дополнительной или вложенной папки используется следующая запись «Èìÿ_ïàïêè\\boll»). Вот и все, теперь вы можете использовать загруженные элементы для вывода на экран монитора (о том, как это сделать, в следующих главах). Если представить себе абстрактно внутренний принцип работы Content Pipeline и то, как происходит загрузка контента в игру и его преобразование, то можно перейти к следующей схеме (рис. 2.3). Как видно из этого рисунка, весь игровой контент проходит через конвейер импорта и преобразования данных, после чего на выходе в проекте получаются файлы со специфическим расширени( ем XNB и XACT для звуковых данных. На этапе запуска игры в игровом процессе принимают участие именно преобразованные файлы с расширением XNB и XACT. Все преобразования контента происходят внутренне, без вашего участия и на эта( пе компиляции проекта в Visual C#.
2.4.4. Компонент Graphics Графическая часть библиотеки Microsoft.Xna.Framework.Graphics осно( вана на принципе работы библиотеки DirectX. Подход в реализации программ при помощи графического конвейера XNA Framework полностью исключает ра( боту с фиксированным конвейером, присущим всем программам с использовани( ем DirectX. Как вы помните, DirectX до последней девятой и десятой версии имел два конвейера: фиксированный и программируемый. Второй конвейер характе( рен тем, что все работы с вершинами объектов, пикселями текстур, материалом,
Рис. 2.3. Работа Content Pipeline в XNA Framework
светом, матрицами происходил посредством шейдеров. Тогда как в фиксирован( ном конвейере можно было и не использовать шейдеры. В XNA Framework используются только программируемый конвейер и шей( деры. Все операции со светом, материалом, матрицами, текстурами и т. д. необхо( димо производить только через шейдеры. В связи с этим в XNA Framework пре( дусмотрены два разных класса – BasicEffect и Effect. Первый класс BasicEffect позволяет не писать и не использовать код шей( дера или, в крайнем случае, не писать супермощные шейдерные программы. Тог( да все вышеперечисленные операции будут выполняться в простом штатном ре( жиме, а система сама будет обрабатывать данные с внутренними значениями по умолчанию. Такой подход позволяет создавать игры не только профессиональ( ным, но и начинающим программистам. Второй класс Effect представляет полный спектр возможностей по работе с шейдерами всех трех версий. По умолчанию применяется вторая и выше версия шейдеров, при желании можно использовать и первую версию шейдеров, но толь( ко для компьютерных систем. Приставка Xbox 360 работает лишь со второй и выше версиями шейдеров.
2.4.5. Компонент Math Как видно из названия компонента, это библиотека для работы с математически( ми операциями. Компонент Math входит в пространство имен Microsoft.Xna. Framework и содержит большое количество классов, структур и методов для ра( боты с векторными данными, матрицами, операциями по трансформации, перено( су, преобразованию и т. д. В качестве основной системы координат в XNA
30
Платформа XNA
Framework применяется правосторонняя система координат, но об этом мы пого( ворим подробно в третьей части книги.
Глава 3
2.4.6. Компонент Input Компонент Input принадлежит к пространству имен Microsoft.Xna. Framework.Input и является, в свою очередь, надстройкой над XInput. Биб( лиотека Input содержит набор инструментов для работы с устройствами ввода информации. К устройствам ввода информации традиционно причисляются кла( виатура, мышь и джойстик. Клавиатура, мышь и джойстик повсеместно использу( ются в компьютерных системах, и в играх в том числе. В консольной приставке Xbox 360 можно работать только с джойстиком и клавиатурой, причем джой( стик – это основное устройство ввода данных. Клавиатура может использоваться, но вряд ли она будет применяться пользователями повсеместно. Поэтому при со( здании игр для Xbox 360 в качестве основного устройства лучше использовать джойстик.
2.4.7. Компонент Audio Работа со звуком в XNA Framework построена на базе Microsoft CrossPlatform Audio Creation Tool, или просто XACT. С помощью этой утилиты или даже от( дельной программы создаются специализированные звуковые проекты, которые затем при помощи компонента Audio загружают все звуковые данные в игру. Та( кой подход сделан намеренно, дабы иметь возможность работать со звуковыми дан( ными вне зависимости от платформы, будь то компьютер или Xbox 360. Компонент Audio принадлежит к пространству имен Microsoft.Xna.Framework.Audio.
2.4.8. Компонент Storage Компонент Storage, относящийся к пространству имен библиотеки Microsoft. Xna.Framework.Storage, организует работу с файловой системой устройства. Здесь доступен набор стандартных операций по записи и чтению данных, удале( нию, перезаписи и т. д. Это стандартный набор для любого устройства с файловой системой организации хранения данных, который может потребоваться в играх, например для сохранения игры или ее последующей загрузки. На этом обзор общей составляющей XNA Framework завершен. Я думаю, что смог убедить вас в том, что создавать игры стало действительно легче. В двух сле( дующих главах мы рассмотрим инструментальные средства, которые нам понадо( бятся для написания исходного кода игр, а затем перейдем к созданию игр.
Инструментарий Visual C# Express Edition Необходимыми атрибутами в создании любых игр и программ являются инстру( ментальные средства разработки приложений. Это так называемые визуальные инструментарии, обладающие встроенными средствами для работы с исходным кодом, компиляцией и отладкой, сборкой и упаковкой проектов в готовый к рас( пространению дистрибутив. На данный момент в качестве промышленного стан( дарта для работы с DirectX и XNA мы имеем инструментальные средства от кор( порации Microsoft. В активе этой корпорации имеется большой инструментарий Visual Studio 2005, а также ряд отдельных инструментариев для работы с различ( ными языками программирования. В этой обзорной главе вы познакомитесь с ос( новными методами работы в Visual C# Express, а также узнаете, как правильно инсталлировать этот инструментарий на свой компьютер, если у вас уже установ( лен Visual Studio 2005.
3.1. Инструментарий Visual Studio 2005 Visual Studio 2005 – это большой инструментальный пакет, включающий в себя мощный и емкий комплекс программных средств, необходимых для создания все( возможных приложений под Windows, Windows Mobile, Xbox 360, дополнительно можно работать и под Unix, Symbian OS, и т. д. Инструментарий Visual Studio 2005 (рис. 3.1) включает в себя ряд отдельных инструментариев для работы с несколькими языками программирования. В со( став пакета Visual Studio 2005 входят следующие средства:
Visual C++ – необходим для работы с языком программирования С++; Visual C# – программирование на языке С#; Visual J# – работа с Java(подобным языком программирования; Visual Basic – использование старого доброго языка Basic.
Инструментарий Visual Studio 2005 не бесплатен и стоит достаточно дорого. Откровенно говоря, не все могут позволить себе приобрести этот отличный про( граммный продукт, поэтому корпорация Microsoft трезво рассудила и предложи( ла ряд бесплатных инструментариев. Одним из таких средств и является Visual C# Express.
32
Инструментарий Visual C# Express Edition
Инструментарий Visual C# Express
33
Инструментарий Visual C# Express абсолютно полнофункционален, без каких( либо урезок и недочетов. Дополнительно к Visual C# Express также бесплатно предлагается справочная система MSDN 2005 Express Edition. Еще нужно доба( вить одно важное обстоятельство. Инструментарий Visual C# Express бесплатен, но требует обязательной регистрации на сайте Microsoft. Сам процесс не сложен, и в разделе 3.4 этой главы мы поговорим о нем более подробно. Для работы с этой книгой вам обязательно понадобятся Visual C# Express, бесплатная версия XNA Game Studio Express (о которой речь пойдет в следующей главе) и последняя версия Runtime DirectX, которую можно скачать с сайта Microsoft. Потребности непосредственно в DirectX SDK нет. Если вы являетесь счастливым обладателем Visual Studio 2005, то пользоваться XNA Game Studio Express не получится, но в следующем разделе объясняется, как все же можно на( строить Visual Studio 2005 на работу с XNA Game Studio Express. Все подробности об этом и многом другом вас ждут в следующей главе. Первоначально на компьютер устанавливается инструментарий Visual C# Express и Runtime DirectX (последовательность не имеет значения, но Run) time DirectX лучше поставить вперед), и только после этого происходит инсталляция XNA Game Studio Express.
Рис. 3.1. Рабочее окно Visual Studio 2005
3.2. Инструментарий Visual C# Express Около миллиона человек со всего мира на данный момент скачали и установили на свои компьютерные системы бесплатный инструментарий Visual C# Express. Инструментарий доступен для бесплатного скачивания с сайта корпорации Microsoft по адресу в Интернете: http://msdn.microsoft.com/vstudio/express/ visualcsharp (размер дистрибутива около 440 Мб). Для читателей этой книги (для тех читателей, которые приобрели эту книгу в магазине, а не скачали из Интерне( та ее пиратскую копию, которая, я не сомневаюсь, в скором времени появится в сети) инструментарий Visual C# Express поставляется на диске, идущем в комп( лекте с книгой. Инструментарий Visual C# Express распространяется в формате ISO. Это образ диска, который вам просто надо записать на любой чистый компакт(диск или воспользоваться программами для чтения образов и создания виртуальных дисков. Самый простой способ – это обычная запись образа из папки \VCS на компакт(диск. После чего запуск данного компакт(диска инициализирует сис( тему установки Visual C# Express. Сам процесс установки инструментария стандартен.
Рис. 3.2. Рабочее окно Visual C# Express
34
Инструментарий Visual C# Express Edition
3.3. Установка Visual C# Express вместе с Visual Studio 2005 В последнее время начинающие программисты, особенно после появления XNA Game Studio Express, очень часто сталкиваются с проблемой установки Visual C# Express поверх ранее установленной на их компьютер Visual Studio 2005. Сама установка Visual C# Express на Visual Studio 2005 – естественно, не проблема, а вот последующая установка XNA Game Studio Express выдает массу различных ошибок. Одна из них – это полная невозможность установки XNA Game Studio Express на компьютерную систему, или другой вариант – отсутствие интеграции документации и примеров, шаблонов проектов XNA Game Studio Express в инст( рументарий Visual C# Express. В этом разделе объясняется механизм правильной и, что самое главное, кор( ректной установки инструментария Visual C# Express поверх уже ранее установ( ленной среды программирования Visual Studio 2005. Более того, если вы имеете на своей машине установленную среду Visual Studio 2005 с компонентом Visual C#, то вам НЕТ НЕОБХОДИМОСТИ скачивать из Интернета полностью Visual C# Express весом в 440 Мб!!! Достаточно простого Dialup(соединения с сетью Интер( нет и совсем немного денежных средств на своем счету для загрузки Visual C# Express размером примерно в 30–50 Мб (в зависимости от установленных у вас на компьютерной системе компонентов). То есть при установленной среде програм( мирования Visual Studio 2005 с компонентом Visual C# достаточно скачать толь( ко часть инструментария Visual C# Express, поскольку все остальное вы уже имеете на своем компьютере. В чем же заключается механизм такой установки Visual C# Express поверх Visual Studio 2005?
Установка Visual C# Express вместе с Visual Studio 2005
ходимо лишь подгрузить некоторые дополнительные компоненты Visual C# Ex( press в Visual Studio 2005. Как правильно и корректно произвести установку Visual C# Express поверх Visual Studio 2005, вам объяснит следующая пошаговая инструкция.
Пошаговая инструкция 1. На компакт(диске в папке \VSС находится установочный пакет Visual C# Express Edition Setup под названием vcssetup.exe. Это последняя версия пакета на сегодняшний день. Дополнительно можно зайти на сайт корпо( рации Microsoft и проверить обновление этого установочного пакета. Для этого соединитесь с сетью Интернет и откройте по нижеприведенной ссыл( ке страницу сайта корпорации Microsoft. Откроется домашняя страница инструментария Visual C# Express, изображенная на рис. 3.3. Здесь вы об( наружите некоторую полезную информацию и, самое главное, ссылку (Download) на автоматический пакет установщика Visual C# Express Edi( tion Setup через сеть Интернет. По ссылке Download загрузите пакет на свой компьютер и разъединитесь с Интернетом. Ссылка на страницу загрузки установщика Visual C# Express: http://msdn.microsoft.com/vstudio/express/visualcsharp/download
3.3.1. Механизм установки Корпорация Microsoft предусмотрела возможность корректной установки Visual C# Express поверх Visual Studio 2005, правильно рассудив, что этот шаг со стороны многих программистов все равно неизбежен после появления такого прекрасного инструмента, как XNA Game Studio Express. Для этих целей был специально со( здан так называемый установочный пакет Visual C# Express Edition Setup весом в 2,8 Мб. Если у вас на компьютере установлен инструментарий Visual Studio 2005, то нельзя прямо поверх него инсталлировать Visual C# Express. Необходи) мо использовать установочный пакет Visual C# Express Edition Setup и связь с Интернетом, иначе у вас возникнут некоторые проблемы с функ) циональностью XNA Game Studio Express. Этот установочный пакет регулирует установку Visual C# Express на ваш компьютер прямо из сети Интернет и позволяет определить необходимые для за( грузки компоненты с сайта корпорации Microsoft. С помощью этого пакета необ(
35
Рис. 3.3. Страница загрузки инструментария Visual C# Express
36
Инструментарий Visual C# Express Edition 2. Далее запустите установочный пакет vcssetup.exe на своем компьютере. На экране монитора вы увидите первое небольшое по размеру информаци( онное окно Setup, представленное на рис. 3.4. На этом этапе установочный пакет определит все установленные у вас на машине компоненты (на это может уйти несколько минут времени) и затем запустит инсталлятор Visual C# Express, загрузка которого будет происходить через Интернет.
Установка Visual C# Express вместе с Visual Studio 2005
37
ном переводе это обозначает, что после установки Visual C# Express в кор( порацию Microsoft будет отослан отчет о вашем опыте этой установки. Здесь можете выбрать этот флажок, а можете и снять его, после чего на( жмите кнопку Next для продолжения этапа установки. 4. Следующее диалоговое окно Visual C# Express Edition Setup – End User License Agreement, изображенное на рис. 3.6, содержит текст лицензион( ного соглашения. Выберите в этом окне флаг I accept the terms of the License Agreement и нажмите кнопку Next. В противном случае установка будет не возможна.
Рис. 3.4. Информационное окно Setup
3. После оценки установленных компонентов на экране монитора появится диалоговое окно Visual C# Express Edition Setup – Welcome to Setup (рис. 3.5). Это окно приветственного характера, объясняющее вам на анг( лийском языке, что именно вы будете инсталлировать на ваш компьютер. При этом в нижней части диалогового окна расположен флаг Yes, send information about my setup experiences Microsoft Corporation. В дослов(
Рис. 3.6. Лицензионное соглашение
Рис. 3.5. Диалоговое окно Visual C# Express Edition Setup – Welcome to Setup
5. Последующее диалоговое окно Visual C# Express Edition Setup – Installa tion Options (рис. 3.7) предоставляет возможность дополнительной за( грузки документации Microsoft MSDN 2005 Express Edition (размер 248 Мб). Если вам необходима эта документация, то выберите флажок с этой надписью, если нет, то флажок необходимо снять и нажать кнопку Next. 6. Поле этого откроется новое окно Visual C# Express Edition Setup – Desti nation Folder, изображенное на рис. 3.8. Если вы устанавливаете инстру( ментарий Visual C# Express поверх Visual Studio 2005, то возможности сменить директорию установки в этом окне не будет. Если вы устанавли( ваете Visual C# Express с нуля, то поле Install in folder будет активным. Для начала процесса скачивания Visual C# Express нажмите кнопку Ins tall, но перед этим не забудьте подключиться к сети Интернет.
38
Инструментарий Visual C# Express Edition
Установка Visual C# Express вместе с Visual Studio 2005
39
7. Пакет Visual C# Express Edition Setup по установленному соединению с сетью Интернет подключится к сайту Microsoft и начнет закачивать к вам на компьютер недостающие компоненты инструментария Visual C# Express (рис. 3.9). Обрывы связи с сетью Интернет установочному пакету не страш( ны. Если произошел обрыв связи, то соединитесь с Интернетом снова, и ус( тановочный пакет продолжит закачку компонентов с того места, с которого его прервали. При этом диалоговое окно Visual C# Express Edition Setup – Download and Install Progress (рис. 3.9) закрывать нет необходимости. При разрыве связи с сетью докачка оставшихся компонентов происходит в авто( матическом режиме. Если по какой(то причине вы вдруг все же закрыли это окно, то запустите установочный пакет Visual C# Express Edition Setup вновь, выполнив все шаги до этого места (1–6), и установочный пакет про( должит закачивать оставшиеся компоненты Visual C# Express.
Рис. 3.7. Диалоговое окно Visual C# Express Edition Setup – Installation Options
Рис. 3.9. Диалоговое окно Visual C# Express Edition Setup – Download and Install Progress
Рис. 3.8. Диалоговое окно Visual C# Express Edition Setup – Destination Folder
8. По окончании процесса скачивания инструментария Visual C# Express по( явится новое окно, изображенное на рис. 3.10. На этом этапе уже можно отключиться от сети Интернет, а процесс скачивания Visual C# Express плавно перейдет к фазе его инсталляции на компьютерную систему. 9. По окончании установки Visual C# Express появится последнее окно Visual C# Express Edition Setup – Setup Complete (рис. 3.11). Это окно оповестит об удачной установке инструментария и предупредит вас о том, что в вашем ак(
40
Инструментарий Visual C# Express Edition
Регистрация Visual C# Express
41
тиве имеется всего 30 дней пользования Visual C# Express без регистрации. О процессе регистрации мы поговорим в следующем разделе. Для окончания установки нажмите кнопку Exit либо выберите ссылку Register now.
3.4. Регистрация Visual C# Express Регистрация инструментария Visual C# Express бесплатна. На работу с инстру( ментарием без регистрации вам отводится всего 30 дней. За это время необходимо обязательно зарегистрировать Visual C# Express через сеть Интернет. Процесс регистрации не сложен, и в нижеприведенной инструкции перечислен пошаговый этап прохождения всей процедуры. Постарайтесь зарегистрировать версию Visual C# Express до установки XNA Game Studio Express. При регистрации инструментария через сеть Ин) тернет от вас потребуется наличие учетной записи в Hotmail.com, Microsoft Passport или MSN либо Windows Live ID. Если ничего из перечисленного нет, то проще всего создать почтовый ящик на сервере Hotmail.com.
Рис. 3.10. Установка Visual C# Express на компьютер
Рис. 3.11. Диалоговое окно Visual C# Express Edition Setup – Setup Complete
1. Итак, откройте на компьютере инструментарий Visual C# Express и выбе( рите команды Help ⇒ Registry Product. Откроется диалоговое окно, в ко( тором необходимо избрать ссылку Register now. Система запросит под( ключение к сети, откроет Интернет(браузер, установленный у вас по умолчанию, и загрузит на компьютер страницу регистрации инструмента(
Рис. 3.12. Интернет&страница для входа в систему
42
Инструментарий Visual C# Express Edition рия (рис. 3.12). На этой странице в поле Email address введите адрес элект( ронной почты на Hotmail.com. В поле Password необходимо ввести пароль к почтовому ящику, который был указан при регистрации учетной записи. Затем нажмите кнопку Sign in. 2. Если имя пользователя и пароль были указаны правильно, то вы войдете в систему и переместитесь на следующую страницу (рис. 3.13). Эта страни( ца представляет собой обычный шаблон с набором параметров для сбора информации о пользователе. Аккуратно заполните на английском языке все поля, в особенности те, которые обозначены красными звездочками. В поле My Email Address необходимо указать рабочий адрес почтового ящика, именно на этот адрес после регистрации придет письмо со ссылкой для продолжения регистрации. Адрес электронной почты можно указы( вать любой, привязанности к Hotmail.com нет. После того как вы заполни( те все поля, нажмите внизу страницы кнопку Continue. 3. Следующая страница носит информационный характер (рис. 3.14) и объ( яснит вам последующие шаги для удачной регистрации Visual C# Express. В частности, вам будет сказано, что на адрес электронной почты, который был указан в поле My Email Address, выслано письмо со ссылкой для ре( гистрации. Следовательно, вам необходимо получить это письмо и перейти по имеющейся в тексте письма ссылке. Эту Интернет(страницу сейчас мож( но закрыть, а нажатие кнопки Continue приведет вас на домашнюю страни( цу инструментария Visual C# Express, что сейчас совсем не обязательно.
Основы работы с Visual C# Express
43
Рис. 3.14. Страница, объясняющая следующие этапы регистрации Visual C# Express
4. Получив письмо и перейдя по имеющейся ссылке, вы попадете на страницу входа в систему (рис. 3.15). Здесь опять необходимо ввести свой адрес электронной почты с Hotmail.com и пароль, а затем нажать кнопку Sign in. 5. После входа в систему вы попадете на Интернет(страницу, оповещающую вас об успешной регистрации Visual C# Express (рис. 3.16). В тексте стра( ницы в строке Your registration key is: будет указан регистрационный ключ, с помощью которого вы сможете зарегистрировать Visual C# Express. Скопируйте этот код в буфер обмена, откройте Visual C# Express, выпол( ните команды Help ⇒ Registry Product. В открывшемся диалоговом окне в поле Registration key вставьте код и нажмите кнопку Complete regist ration. Все, на этом этап регистрации Visual C# Express окончен, и вы мо( жете спокойно работать с этим мощным и бесплатным инструментарием. Кстати, нажатие кнопки Continue на этой последней странице приведет вас на домашнюю страницу инструментария Visual C# Express.
3.5. Основы работы с Visual C# Express
Рис. 3.13. Сбор информации о пользователе
Эта книга предполагает, что вы, по крайней мере, знакомы с языком программи( рования С# или С++ и умеете работать с инструментарием Visual Studio, но на всякий случай для тех, кто только(только вступает в наши ряды или мигрирует с других платформ, предлагается этот раздел. В этом небольшом разделе мы рас(
44
Инструментарий Visual C# Express Edition
Основы работы с Visual C# Express
45
смотрим основные способы работы с инструментарием Visual C# Express. В част( ности, создадим простой проект, откомпилируем код, а затем соберем и запустим программу на компьютере.
3.5.1. Создаем простой проект Открываем Visual C# Express и в меню инструментария выполняем команды File ⇒ New Project (горячие клавиши Ctrl+Shift+N). Дополнительно эти самые действия также доступны со стартовой страницы Visual C# Express (Start Page) по выбору ссылки Create Project. В ответ на это откроется диалоговое окно New Project (рис. 3.17).
Рис. 3.15. Вход в систему
Рис. 3.17. Диалоговое окно New Project
Рис. 3.16. Получение регистрационного ключа
В этом диалоговом окне в текстовой области Visual Studio installed templates перечислены все шаблоны проектов, которые можно использовать при создании своих программ. Дополнительно вы можете создавать свои шаблоны или приме( нять шаблоны, созданные другими людьми. Кстати, после установки XNA Game Studio Express в эту область добавится ряд отличных шаблонов для создания сво( их игр и программ, но об этом в следующей главе. Для создания проекта из списка выбираем необходимый шаблон. В качестве примера был задействован самый первый шаблон Windows Application, который создает простое приложение на основе формы. Выбор шаблона происходит щелч( ком левой кнопки мыши на его названии и иконки в области Visual Studio installed templates. Затем в поле Name задается имя будущего проекта, и далее нажимается
46
Основы работы с Visual C# Express
Инструментарий Visual C# Express Edition
кнопка OK. Инструментарий Visual C# Express на основе полученных данных фор( мирует проект и открывает его в рабочем окне нового проекта (рис. 3.18).
47
3.5.2. Компиляция и запуск проекта Для компиляции проекта необходимо вы( полнить команды Build ⇒ Build Solution или воспользоваться горячей клавишей F6. Эта операция компилирует в первый раз весь проект, а впоследствии команды Build ⇒ Build Solution производят компиляцию только измененных файлов проекта. Если вы хотите откомпилировать весь проект в це( лом, то необходимо выполнить команды Build ⇒ Rebuild Solution. После удачной компиляции для запус( Рис. 3.20. Запуск проекта ка проекта необходимо выполнить коман( WindowsApplication1 ды Debug ⇒ Start Debug (F5) либо Debug ⇒ Start With Debugging (Ctrl+F5). В первом случае происходят запуск программы и одновременный запуск отладчика Visual C# Express, а во втором
Рис. 3.18. Рабочее окно проекта в Visual C# Express
После создания проекта его необходимо явно сохранить, для этого выполните команды File ⇒ Save All (Ctrl+Shift+S) или используйте панель инструментов Visual C# Express и кнопку Save All (с изображением нескольких дискет). Откро( ется диалоговое окно Save Project (рис. 3.19). В поле Location необходимо ука( зать директорию для сохранения проекта и нажать кнопку Save.
Рис. 3.19. Явное сохранение проекта в определенном каталоге
Рис. 3.21. Ошибки во время компиляции
48
Инструментарий Visual C# Express Edition
случае программа запускается без отладчика. Результат работы программы по( казан на рис. 3.20. Если на этапе компиляции возникают ошибки, то в нижней части рабочего окна Visual C# Express появляется дополнительная панель Error List со списком ошибок, как показано на рис. 3.21. В данном случае я просто инициализировал ошибку, удалив из имени одного метода букву в названии. Все ошибки в коде вы( деляются синим цветом, а в текстовой области панели Error List поясняется тип допущенной ошибки. Откровенно говоря, интеллектуальная система Visual C# Express в плане выявления ошибок, различных подсказок и подсветок реализова( на выше всяких похвал! Для примера в исходном коде любого проекта наведите курсор мыши на одно из ключевых слов или системную функцию, класс и посмот( рите на уровень инициализации подсказок (рис. 3.22).
3.5.3. Сборка проекта
Основы работы с Visual C# Express
того как вы подготовили программу и готовы ее распространять, необходимо вы( брать версию Release. В версии Release инструментарий Visual C# Express убе( рет различную отладочную информацию и минимизирует размер конечной про( граммы. Для задания типа конфигурации воспользуйтесь панелью инструментов (рис. 3.23) и выберите из списка версию Debug или Release. Эту операцию можно проделать также из меню Visual C# Express, выполнив команды Build ⇒ Confi guration Manager. На эти действия открывается диалоговое окно Configuration Manager, где в списке Active Solution configuration можно задать тип конфигура( ции для приложения (рис. 3.24). После компиляции и сборки проекта в его ката( логе будет сформирована рабочая программа. В зависимости от типа конфигура( ции в каталоге создаются две папки с названиями Debug и Release. Каждая последующая компиляция и сборка проекта подменяет предыдущую версию про( граммы.
В работе над проектами у вас есть возможность создавать два типа конфигурации программы – это отладочная версия (Debug) и окончательная версия (Release). По ходу работы над проектом лучше пользоваться отладочной версией, а после Рис. 3.23. Выбор типа конфигурации приложения
Рис. 3.24. Диалоговое окно Configuration Manager
Рис. 3.22. Подсказки в Visual C# Express
49
Студии разработки игр
Глава 4 Студия разработки игр XNA Game Studio Express Чтобы создать компьютерную игру для Windows в XNA Game Studio Express, вам понадобятся относительно небольшие познания языка программирования С#, умение работать с бесплатным инструментарием Visual C# Express, наличие этой книги, трудолюбие, фантазия, а также компакт(диск к этой книге, на котором на( ходится потрясающая подборка инструментов и материалов.
51
Корпорация Microsoft организовала клуб разработчиков игр XNA Creators Club, членство в котором позволит вам иметь доступ к большой базе данных ис( ходных кодов, различных бесплатных проектов и информации. Для создания игр под ПК членство в клубе не обязательно. При этом покупка приставки Xbox 360 – дело обязательное, поскольку регистрация в XNA Creators Club происходит не( посредственно через приставку и сервис Marketplace. На сайте http://creators.xna.com вы найдете множество различной информа( ции, примеров, статей, исходных кодов, пакетов Starter Kits и для приставки Xbox 360 и системы Windows (рис. 4.1). Дополнительно в книге, посвященной разработке игр для Xbox 360, имеются все пошаговые инструкции, связанные со вступлением в клуб XNA Creators Club, настройкой Xbox Live, переносом игр на консоль и многим другим.
4.1. Студии разработки игр Студия XNA Game Studio Express не имеет своего интерфейса, а представляет со( бой пакет или программный набор средств, позволяющих значительно упростить разработку компьютерных игр для Windows. Студия XNA Game Studio Express бесплатна и при инсталляции интегрируется в бесплатный инструментарий Visual C# Express. Всего имеются три разные версии студий: XNA Game Studio Express; XNA Game Studio Professional; XNA Studio. Все три версии идентичны в механизме работы и программной составляющей. Отличительной особенностью каждой последующей версии в порядке увеличе( ния (XNA Game Studio Express, XNA Game Studio Professional и XNA Studio) яв( ляется наличие дополнительных возможностей, предусмотренных для создания игр и попутных игровых сервисов.
4.1.1. XNA Game Studio Express Финальная версия XNA Game Studio Express появилась на свет 11 декабря 2006 года, но до этого времени было еще два бета(релиза. Переход от первой беты XNA Game Studio Express был значительно болезненнее для разработчиков, чем переход от второй беты к релизу. Бесплатная версия студии XNA Game Studio Express ориен( тирована в первую очередь на новичков в программировании игр, разработчиков( любителей, студентов и т. д. С помощью этой версии студии вы сможете создавать только некоммерческие проекты для приставки Xbox 360, но и любые виды ком( мерческих и бесплатных проектов для системы Windows.
Рис. 4.1. Ресурс http://creators.xna.com
В XNA Game Studio Express имеется возможность в подключении Starter Kit (Стартовый комплект разработчика). Один такой комплект представляет, как правило, одну готовую игру. На основе такого пакета, или шаблона, вы можете создавать свои игры. В комплект поставки XNA Game Studio Express входит
52
Студия разработки игр XNA Game Studio Express
Spacewar Starter Kit (для Xbox 360 и ПК), однако дополнительно можно исполь( зовать наработки других компаний или людей. Подписчикам клуба XNA Creators Club такая возможность предоставляется бесплатно, но более серьезные наработ( ки предлагаются за деньги, соответственно вам также никто не мешает продавать и свои наработки через сервис Xbox Live. Здесь мы фактически ведем речь об иг( ровых движках и возможных путях их сбыта.
4.1.2. XNA Game Studio Professional Студия XNA Game Studio Professional появится в ближайшее время. Эта версия студии является платной и направлена уже на создание коммерческих игр. Сту( дия XNA Game Studio Professional построена полностью на программном карка( се XNA Game Studio Express, то есть изучение первой версии ведет к автомати( ческому изучению второй версии студии. В чем разница между этими версиями студий? Во(первых, Microsoft выпустила XNA Game Studio Express раньше для того, чтобы обкатать весь программный комплекс в целом и выявить очевидные недо( четы и недоработки в студии. На базе этих полученных данных соответственно подкорректировать уже коммерческую версию XNA Game Studio Professional. Во( вторых, в XNA Game Studio Professional будет добавлен ряд дополнительных воз( можностей, например по работе с сервисом Xbox Live. Сама версия XNA Game Studio Professional ориентирована на профессиональ( ных разработчиков, и здесь в качестве инструментария уже может использоваться не только Visual C# Express, но и Visual Studio 2005. То есть версия XNA Game Studio Professional – это полностью коммерческий продукт, рассчитанный на не( большие игровые студии или коллектив единомышленников, желающих делать консольные и компьютерные игры на языке С#. Не стоит забывать о том, что че( рез год или даже меньше платформа XNA и С# станут стандартом и для другой платформы Windows Mobile. В итоге одну игру, созданную в XNA Game Studio, можно будет практически без затрат и усилий портировать на мобильное устрой( ство, Xbox 360 и ПК!
Установка XNA Game Studio Express
4.2. Установка XNA Game Studio Express Запустите установочный пакет XNA Game Studio Express, инициализировав тем самым инсталляцию студии на свой компьютер. Далее в пошаговом режиме мы проследим за процессом установки XNA Game Studio Express. Перед установкой студии у вас на компьютере должен быть установлен инструментарий Visual C# Express с полноценной регистрацией (см. гла) ву 3). Если у вас сейчас на машине установлена одна из предыдущих версий XNA Game Studio Express, удалите ее через команды Панель управления ⇒ Установка/удаление программ. После удаления студии запустите ин) струментарий Visual C# Express и закройте его, а затем перезагрузите компьютер. Только после перезагрузки компьютера устанавливайте позд) нюю версию XNA Game Studio Express. 1. Двойной клик левой кнопкой мыши на установочном файле xnagse_ setup.msi запустит процесс инсталляции XNA Game Studio Express на компьютере. Откроется окно приветствия Microsoft XNA Game Studio Express Setup – Welcome to the Microsoft XNA Game Studio Express Se tup (рис. 4.2). Для продолжения установки нажмите кнопку Next. 2. Следующее диалоговое окно Microsoft XNA Game Studio Express Setup – EndUser License Agreement содержит текст лицензионного соглашения, регулирующего использование этого продукта у себя на компьютере
4.1.3. XNA Studio Версия XNA Studio выйдет несколько позже, чем две предыдущие версии и будет предназначена для больших мощных и профессиональных игровых студий разра( батывающих игры для Xbox 360 и ПК. Эта версия студии будет построена на двух предыдущих версиях с большими возможностями в планировании и разработке игр. К сожалению, на момент написания книги информации по XNA Studio очень мало и она ужасно скудна, а по одним данным эта версия может и вовсе не по( явиться на свет, хотя и декларировалась Microsoft ранее. Пожалуй, это все что я могу вам сообщить на данный момент по версии XNA Studio, больше информации можно найти в Интернете по адресу http://msdn.com/xna.
53
Рис. 4.2. Окно Microsoft XNA Game Studio Express Setup – Welcome to the Microsoft XNA Game Studio Express Setup
54
Студия разработки игр XNA Game Studio Express
Знакомство с XNA Game Studio Express
55
(рис. 4.3). Чтобы продолжить установку, выберите в этом окне флажок I accept the terms of the License Agreement и нажмите кнопку Next.
Рис. 4.4. Диалоговое окно Microsoft XNA Game Studio Express Setup – Ready to install Microsoft XNA Game Studio Express Setup
Рис. 4.3. Диалоговое окно Microsoft XNA Game Studio Express Setup – End&User License Agreement
3. Откроется информационное окно Microsoft XNA Game Studio Express Setup – Ready to install Microsoft XNA Game Studio Express Setup (рис. 4.4). На этом этапе вам необходимо отключить у себя на компьютере любой брандмауэр, отличный от встроенного в Windows. Выполнение это( го условия обязательно, и оно необходимо для корректной работы при( ставки Xbox 360 и компьютера. Для запуска процесса инсталляции студии нажмите кнопку Install. Инсталлятор XNA Game Studio Express запустит установку студии на компьютер (рис. 4.5). 4. Последнее информационное окно Microsoft XNA Game Studio Express Se tup – Completed Microsoft XNA Game Studio Express Setup (рис. 4.6) опо( вестит вас об окончании установки студии. Нажмите кнопку Finish, чтобы закончить процесс инсталляции XNA Game Studio Express на машину.
4.3. Знакомство с XNA Game Studio Express Как мы уже выяснили, студия XNA Game Studio Express не имеет своего интер( фейса, или программной оболочки, и интегрируется непосредственно в инстру( ментарий Visual C# Express. Если вы откроете меню Пуск ⇒ Все программы, то обнаружите следующие установленные компоненты:
Рис. 4.5. Инсталляция студии на компьютер
XNA Game Studio Express – запускает работу студии, а точнее инструмен( тарий Visual C# Express; XNA Game Studio Express Readme – файл Readme;
56
Студия разработки игр XNA Game Studio Express
Знакомство с XNA Game Studio Express
57
Рис. 4.6. Информационное окно Microsoft XNA Game Studio Express Setup – Completed Microsoft XNA Game Studio Express Setup
XNA Game Studio Express Documentation – документация по XNA Game Studio Express; Tools – набор дополнительных утилит, включающих в себя следующие средства: – Microsoft CrossPlatform Audio Creation Tool (XACT) – кросс(плат( форменная утилита для работы со звуком в XNA; – XACT Auditioning Utility – утилита аудиотюнинга, работает из команд( ной строки; – XNA Framework Remote Performance Monitor for Xbox 360 – монито( ринг подключения приставки Xbox 360 к компьютеру.
Рис. 4.7. Стартовая страница инструментария Visual C# Express
4.3.1. Настройка получения новостей в XNA Game Studio Express Выбор в меню команды XNA Game Studio Express откроет Visual C# Express. Пос( ле открытия инструментария на его стартовой странице (если вы подключены к Интернету) отобразятся последние новости, как показано на рис. 4.7. Времен( ной интервал загрузки новостей в Visual C# Express можно регулировать через команды меню инструментария Tools ⇒ Options ⇒ Startup (рис. 4.8). В окне Options на вкладке Startup в списке At Startup можно выбрать вид стартовой страницы инструментария, а в поле Start Page news channel опреде( лить новостной канал загрузки новостей. Выбор флажка Download Content Every в этом окне включает режим загрузки новостей. В инкрементном регулято(
Рис. 4.8. Настройка загрузки новостей
ре minutes необходимо определить сам временной интервал в минутах. Если снять флажок Download Content Every, то загрузка новостей происходить не бу( дет. Дополнительно перемещаясь по вкладкам окна Options, вы можете настроить и другие различные параметры Visual C# Express.
58
Студия разработки игр XNA Game Studio Express
4.3.2. Шаблоны XNA Game Studio Express При формировании проекта в Visual C# Express (File ⇒ New Project) в окне New Project после установки XNA Game Studio Express добавятся несколько хороших и нужных шаблонов, которые будут нам помогать в создании всех примеров для этой книги (рис. 4.9). Новые шаблоны проектов значительно упрощают и автома( тизируют процесс создания игр для компьютерных систем. Выбрав один из шаб( лонов, вы фактически создаете готовый костяк игры, в который необходимо встроить свой исходный код. Конечно, саму игру за вас никто не напишет, но имею( щиеся средства и сервисы значительно облегчают создание игр. Например, за( грузку в проект моделей и, в частности, X(файлов. Как было раньше, кто помнит? А было так: строк двадцать исходного кода или свой сформированный ранее класс для загрузки мэшей. А сейчас одна строка кода – и ваш мэш загружен в игру!
Рис. 4.9. Окно New Project
Но вернемся к шаблонам. Итак, в XNA Game Studio Express появилось шесть следующих шаблонов: Windows Game – шаблон, создающий каркас классов, и игровой механизм, реализующий простой игровой процесс в приложении для Windows; Windows Game Library – шаблон для создания собственных игровых биб( лиотек;
Знакомство с XNA Game Studio Express
59
Xbox 360 – шаблон, создающий каркас классов и игровой механизм, реа( лизующий простой игровой процесс в приложении для Xbox 360; Xbox 360 Game Library – шаблон для создания собственных игровых биб( лиотек; Spaceware Windows – Starter Kit игры Spaceware для Windows; Spaceware Xbox 360 – Starter Kit игры Spaceware для Xbox 360. Выбрав один из шаблонов, вы сформируете структуру классов, на основе ко( торой легко будет создать свою собственную игру. Мы на протяжении всей книги будем работать с шаблоном Windows Game. Дополнительно вы можете создавать свои шаблоны (Starter Kit) или использовать сторонние разработки. Например, сейчас на сайте http://creators.xna.com доступен для свободного скачивания комп( лект Marblets Starter Kit, с помощью которого вы можете создать интересную двухмерную аркаду.
Формируем проект в Visual C# Express
Часть 2 Создаем двухмерную игру
61
5.1. Формируем проект в Visual C# Express Откройте на компьютере Visual C# Express. Для создания проекта выполните ко( манды File ⇒ New Project (Ctrl+Shift+N). Откроется диалоговое окно New Project, изображенное на рис. 5.1. В этом окне в списке Templates изберите курсо( ром мыши шаблон с названием Windows Game. Затем в поле Name укажите имя будущего проекта, у нас это FirstProject. В поле Location необходимо задать ката( лог для хранения создаваемого проекта, и вот тут вас могут поджидать некоторые неприятности.
Глава 5 Формируем каркас игровых классов С этой главы мы начинаем работать с исходным кодом игры, применяя инстру( ментальные средства, которые были рассмотрены в предыдущих главах. За время изучения этой главы мы создадим два проекта. Первый проект будет создан на базе шаблона Windows Game студии XNA Game Studio Express с использованием Application Model. Затем уже во втором проекте мы приступим к модификации исходного кода. В итоге каждая новая глава – это модификация проекта из преды( дущей главы. Предложенный подход в видоизменении или развитии проекта бу( дет сохранен до конца книги. Таким образом, вы шаг за шагом, как и в реальной разработке игр, создадите к концу книги две игры. В связи с этим рекомендуется изучать все главы книги исключительно в хронологическом порядке. Первый проект под названием FirstProject мы сформируем с использованием шаблона Windows Game, который добавился в Visual C# Express после установки на компьютер XNA Game Studio Express. Это простой шаблонный проект, форми( рующий каркас будущей игры. Шаблон хорош тем, что он автоматически создает необходимую структуру программы, включая все этапы – от инициализации ком( понентов игры до освобождения всех ресурсов, захваченных программой. Этот шаблон как раз и иллюстрирует в действии работу Application Model. Второй проект с названием FullScreen станет логическим продолжением пер( вого проекта. По умолчанию шаблон Windows Game создает программу, работаю( щую в оконном режиме. Этот режим для игр не всегда интересен, поэтому во вто( ром проекте мы перейдем из оконного режима в полноэкранный. После перехода в полноэкранный режим закрытие работающей программы с помощью курсора мыши станет недоступным, поэтому будет добавлен механизм выхода из прило( жения по нажатию одной из клавиш.
Рис. 5.1. Диалоговое окно New Project
Использование имен папок, написанных кириллицей, как, впрочем, и назва( ний проектов, не допустимо. В названиях необходимо применять только англий( ский алфавит. Возможны также проблемы, если вы храните ваши проекты на дру( гом жестком диске или в другом разделе одного диска. Все эти проблемы могут быть, а могут и не быть, в зависимости от того, какие средства будут задействова( ны в текущем проекте. Например, XNA Content Pipeline весьма капризен к папкам с русскими именами. В связи с этим лучше всего создать в корневом каталоге дис( ка «С» папку с любым английским названием и формировать в этой папке свои проекты. Тогда вы гарантированно будете избавлены от разного рода ошибок при компиляции проекта. Я для всех примеров книги создал папку Code, в которой формировались отдельные папки для каждой главы, например для этой главы это
62
Класс Program
Формируем каркас игровых классов
63
папка Chapter5. Каждый новый проект одной главы также имеет свою папку, совпадающую с названием этого проекта. В диалоговом окне New Project присутствует флаг Create directory for solution. Выбор этого флажка автоматически формирует вложенную папку в ка( талоге создаваемого проекта для хранения данных Solution Explorer. Этот флаг лучше не использовать и снять. Поле всех действий в окне New Project нажмите кнопку ОК. Инструментарий Visual C# Express на основе избранного вами шаб( лона создаст каркас проекта.
5.2. Структура проекта После создания проекта в рабочем каталоге диска «С» появится папка с названи( ем программы. В этой папке будут находиться все файлы проекта, а также папка под названием bin. В папке bin после компиляции и сборки проекта создаются еще две вложенные папки: для отладочной версии программы – папка Debug, а для окончательной версии – папка Release. После создания проекта FirstProject в рабочем пространстве Visual C# Express с правой стороны открывается панель Solu tion Explorer, где отображается древовидная структура текущего проекта, которая состо( ит из следующих компонентов (рис. 5.2). Папка Properties – эта папка со( держит файл AssemblyInfo.cs с описательными свойствами всего проекта. Вы можете открыть этот файл и редактировать его через тек( стовый редактор. А если щелкнуть Рис. 5.2. Панель Solution Explorer правой кнопкой мыши на файл AssemblyInfo.cs и в появившем( ся контекстном меню выбрать команду Properties, то в нижней части пане( ли Solution Explorer появится дополнительная панель Properties, через которую также возможно редактирование файла AssemblyInfo.cs. До( полнительно если щелкнуть правой кнопкой мыши уже на названии всего проекта в панели Solution Explorer и в появившемся меню выбрать коман( ду Properties, то в текстовом редакторе откроется добавочная вкладка с од( ноименным названием проекта (рис. 5.3). Здесь также можно задавать раз( личные дополнительные свойства для всего проекта. Папка References – в этой папке отображаются все задействованные в проекте ссылки на библиотечные классы платформы XNA. Выбор любого класса на панели Solution Explorer откроет в текстовом редакторе вкладку с представлением этого класса (рис. 5.4). В какой(то мере этот механизм можно использовать даже как справочную систему.
Рис. 5.3. Вкладка со свойствами проекта
Графический файл Game.ico – это иконка программы размером 32 на 32 пикселя, которая назначена вашему проекту по умолчанию студией XNA Game Studio Express. Вы можете отредактировать эту иконку конкретно для вашей игры. Для редакции иконки можно использовать любой графи( ческий редактор. Чтобы заменить в проекте иконку на новую, откройте до( бавочную вкладку (щелчок правой кнопкой мыши на названии проекта и выбор команды Properties) со свойствами проекта и выберите Application. Далее в поле Icon укажите путь к новой иконке (рис. 5.3). Класс Game1.cs – шаблонный класс Game1, сформированный Visual C# Ex( press, содержит костяк программы с набором необходимых методов и объектов. Класс Program.cs – это также шаблонный класс, который является вход( ной точкой для всех приложений. С рассмотрения этого файла мы сейчас приступим к изучению исходного кода созданной программы.
5.3. Класс Program Класс Program – это класс(шаблон, который Visual C# Express автоматически создает для любой программы вне зависимости от ее направленности. Этот класс служит входной точкой для начала работы всей программы. В любой программе
64
Класс Program
Формируем каркас игровых классов
65
using System; namespace FirstProject { static class Program { static void Main(string[] args) { using (Game1 game = new Game1()) { game.Run(); } } } }
Рис. 5.4. Обзор библиотек
на любом языке есть свой метод или функция, которая выступает в роли зажига( ния (если брать аналогию с машиной) и заводит программу. После чего в работу подключаются уже остальные винтики всего механизма. Класс Program и есть зажигание для всей программы, и очень редко, когда необходимо вносить изменения в исходный код класса Program, но это возмож( но, и подчас в больших проектах такой подход действительно оправдан. Теперь давайте посмотрим на листинг 5.1 класса Program проекта FirstProject и поста( раемся разобраться, что здесь к чему. //========================================================================= /// <summary> /// Ëèñòèíã 5.1 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 5 /// Ïðîåêò: FirstProject /// Êëàññ: Program /// Ñîçäàíèå ïåðâîãî ïðîåêòà /// <summary> //=========================================================================
Обратите внимание на комментарии. Использование в комментариях трой( ных слэшей и различных ключевых слов, таких как или <param name=“èìÿ ïàðàìåòðà”>, для языка программирования C# – нормальная практика. Этот подход позволяет сделать хорошо документированный исходный код программы. В начале исходного кода в файле Program.cs происходит подключение раз( личных системных классов посредством оператора using, а также для всего про( екта объявляется пространство имен оператором namespace FirstProject. Пространство имен организовывается для избежания возможных конфликтов при обращении к библиотекам с одинаковыми названиями, созданными различ( ными производителями. Пространство имен официально ограничивает использо( вание конкретного имени в пределах только этого программного кода или целого проекта, в частности. За объявлением пространства имен в исходном коде следуют объявление класса Program и вызов метода Main(). Среда исполнения CLR при запуске программы начинает поиск метода Main(). С запуска этого метода начинается работа всего приложения. В программе можно создать несколько методов Main(), но в этом случае все равно один из методов будет главным, а чтобы обо( значить, какой именно метод главный, придется использовать командную строку, дабы показать компилятору C# точку входа в программу. Технически подобную операцию сделать можно, но у нас такой необходимости нет. Внутри метода Main() происходит создание объекта game класса Game1, после чего в работу включается системный метод Run(). Этот метод и есть зажи( гание, который запускает бесконечный цикл, где и происходит выполнение всей программы. Пока программа работает, бесконечный цикл функционирует; как только пользователь закрывает программу, работа бесконечного цикла автомати( чески прекращается. После запуска метода Run() управление работой приложе( ния передается в класс Game1, где начинается выполнение исходного кода файла Game1.cs, к рассмотрению которого мы переходим.
66
Класс Game1
Формируем каркас игровых классов
5.4. Класс Game1
protected override void Initialize() { base.Initialize(); }
Каркас исходного кода класса Game1 формируется посредством шаблона инстру( ментария Visual C# Express и студии XNA Game Studio Express. Набор методов этого класса имеет стандартные функции, к которым вы вправе добавлять свои новые методы либо изменять имеющиеся. Рассмотрим исходный код файла Game1.cs из листинга 5.2. //========================================================================= /// <summary> /// Ëèñòèíã 5.2 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 5 /// Ïðîåêò: FirstProject /// Êëàññ: Game1 /// Ñîçäàåì ïåðâûé ïðîåêò /// <summary> //=========================================================================
/// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } }
#region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion
/// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();
namespace FirstProject { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); Window.Title = “Ïåðâûé ïðîåêò. Îêîííîå ïðèëîæåíèå”; } /// <summary> /// Èíèöèàëèçàöèÿ /// <summary>
67
base.Update(gameTime); } /// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); base.Draw(gameTime); } } }
В самом начале исходного кода класса Game1 происходит подключение сис( темных библиотек и определение пространства имен проекта. Название класса Game1 предлагается Visual C# Express по умолчанию, но если вам не нравится
68
Класс Game1
Формируем каркас игровых классов
это имя класса, естественно, вы его можете изменить. Для изменения имени клас( са необходимо щелкнуть в Solution Explorer правой кнопкой мыши на названии файла Game1.cs и в появившемся контекстном меню выбрать команду Rename. Соответственно вам придется изменить и название класса в исходном коде файла Program.cs, а также в других классах программы, где вы упоминаете этот класс. На самом деле, как вы понимаете, особой необходимости в изменении имени клас( са нет, поскольку для проекта все равно определяется свое пространство имен. Далее идет объявление самого класса Game1: public class Game1 : Microsoft.Xna.Framework.Game
Эта строка кода говорит о том, что класс Game1 является производным от системного класса Game библиотеки Microsoft.Xna.Framework. Иначе класс Game1 наследует все возможности своего базового класса Microsoft.Xna. Framework.Game, что дает ему право использовать всю силу и мощь системного класса, а также возможность модификации всех методов базового класса. Затем в исходном коде Game1.cs происходит глобальное объявление двух объектов: GraphicsDeviceManager graphics; ContentManager content;
Первая строка определяет объект graphics класса GraphicsDeviceManager. С помощью этого объекта в исходном коде происходят настройка и конфигурация графического устройства, или, говоря простым языком, видеоадаптера, установ( ленного в компьютере. Объект content необходим для загрузки различной гра( фической составляющей в проект при помощи XNA Content Pipeline. В этой главе объект content мы использовать не будем, поэтому поговорим о нем подробнее в следующей главе. Далее в исходном коде следует конструктор класса Game1(). public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); Window.Title = “Ïåðâûé ïðîåêò. Îêîííîå ïðèëîæåíèå”; }
Здесь происходит создание двух объявленных ранее объектов стандартным образом. Фактически объект graphics класса GraphicsDeviceManager за( менил всю ту череду вызовов различных функций в DirectX, необходимых для инициализации и настройки графического устройства. Единственное, что впо( следствии стоит добавить в конструктор, – так это несколько параметров ини( циализации графического устройства через объект graphics, например для пе( рехода в полноэкранный режим, но об этом позже в этой главе.
69
Следующая строка кода Window.Title = “Ïåðâûé ïðîåêò. Îêîííîå ïðèëîæåíèå”;
не входит в определение шаблона, создавшего наш проект. Эта строка была добав( лена в код специально, чтобы заменить заголовок окна с именем проекта FirstProject на более звучное и понятное название. За конструктором класса Game1() следует определение метода Initialize(). protected override void Initialize() { // çäåñü ñëåäóåò êîä èíèöèàëèçàöèè base.Initialize(); }
Этот метод предназначен для инициализации различных компонентов игры, которые вам понадобятся перед самым первым стартом игры. Из этого метода вы можете передавать управление и в другие методы, связанные с инициализацией различных начальных данных (позиции на экране, инициализация различных пе( ременных и т. д.). Единственные ограничения касаются того, что в этом методе нельзя загружать графические компоненты игры (текстуры, модели), для этих целей служит метод LoadGraphicsContent(). protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { // çäåñü çàãðóæàþòñÿ ìîäåëè è ãðàôè÷åñêèå ýëåìåíòû } }
Метод LoadGraphicsContent() необходим для загрузки графической со( ставляющей вашей игры, используя мощь XNA Content Pipeline, который позво( ляет загрузить в одну строку кода любую 3D(модель или графику определенного формата. Поддерживаемых форматов много, но об этом в следующей главе, когда мы с этим непосредственно столкнемся. Следующим в исходном коде идет метод UnloadGraphicsContent(), ко( торый в автоматическом режиме производит выгрузку загруженных 3D(моделей и графики на этапе выхода из игры. Это своего рода менеджер, работающий на автомате и освобождающий захваченные ресурсы системы по сигналу выхода из программы. В стандартном использовании загрузчика добавлять в метод UnloadGraphicsContent() дополнительный исходный код не нужно. Но если применяются различные дополнительные загрузчики, то выгрузку загруженных элементов в методе UnloadGraphicsContent() необходимо прописать явно. Затем в исходном коде класса Game1 идет метод Update() со следующим ис( ходным кодом:
70
Класс Game1
Формируем каркас игровых классов
protected override void Update(GameTime gameTime) { if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // êîä èãðû base.Update(gameTime); }
Этот метод предназначен для обновления состояния игры. В методе Update() необходимо расположить все методы, отвечающие за логику игры, реализовать механизм получения событий с клавиатуры, мыши или джойстика, воспроизведе( ние звука. Определить, что именно будет происходить после столкновений, вы( стрелов, ударов и т. д. Этот метод вызывается с циклической периодичностью, а точнее через определенный промежуток времени (таймер). Промежуток време( ни реализуется в автоматическом режиме через объект gameTime. Первая и вторая строки кода в методе Update() получают события с джой( стика и реализуют закрытие программы. Когда мы создадим проект, работающий в полноэкранном режиме, то нам может понадобиться выход из программы по( средством клавиатуры (не у всех пользователей есть джойстик). Тогда и рассмот( рим возможность обработки событий, полученных с клавиатуры, а пока выход из программы осуществляется кнопкой мыши. Последний метод класса Game1 – это метод Draw().
71
ект, затем откомпилируйте и запустите программу. На экране монитора появится простое оконное приложение, как показано на рис. 5.5. По умолчанию в XNA Game Studio Express создается оконный режим работы программы, где ширина окна равна 800 пикселей, а высота – 600 пикселей. По желанию вы можете изме( нить размеры окна, а также перейти в полноэкранный режим, об этом мы погово( рим в разделе 5.6. Созданное окно по умолчанию не может быть развернуто на весь дисплей, но если добавить в конструктор класса Game1 следующий исход( ный код: Window.AllowUserResizing = true;
то в правом верхнем углу окна станет доступна кнопка с командой Развернуть.
protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // çäåñü ðèñóåì ãðàôèêó base.Draw(gameTime); }
Данный метод необходим для отрисовки всей графики на экране монитора. Метод Draw(), как и метод Update(), работает в циклическом режиме, постоян( но обновляя состояние игры и перерисовывая игровую графику. Периодичность перерисовки графики зависит от объекта gameTime. Но еще раз повторяю, весь этот механизм работает самостоятельно и организовывает обновление состояния игры на автомате, стремясь достичь максимально возможной смены кадров в игре (FPS). Такой шаблонный подход в конструкции классов Program и Game1 дает вам возможность сосредоточить свои силы на написании игры, а не на инициализации и создании окна Windows, обработке циклов, объявлении кучи различных объек( тов для работы с DirectX и многом другом. Все это за вас уже сделали два класса – Program, Game1, инструментарий XNA Game Studio Express и системные классы платформы XNA Framework. Расслабьтесь и наслаждайтесь написанием исходно( го кода игры! Исходный код этого примера вы найдете на компакт(диске в папке Code\ Chapter5\FirstProject. Откройте инструментарий Visual C# Express и этот про(
Рис. 5.5. Работа программы FirstProject
Рабочее пространство окна в проекте FirstProject закрашено в синий цвет. Это достигается посредством вызова метода Clear(). Параметр этого метода CornflowerBlue как раз определяет синий цвет закраски экрана в программе,
72
Формируем каркас игровых классов
и, естественно, эту цветовую составляющую можно изменять по своему усмотре( нию. Чаще всего используют черный цвет, а закраска экрана цветом – на самом деле целый отработанный механизм. Этот механизм заключается в том, чтобы перед показом нового кадра (фрейма) стирать содержимое экрана одним из цветов и рисовать графику вновь. То есть если вы рисуете на экране какой(то объект и затем желаете его переместить по экрану, то необходимо сначала стереть этот объект со старого места и нарисовать уже в новом месте. Метод Clear() выступает в роли стирательной резинки, очищая экран мо( нитора. Если не стирать экран цветом, то за движущимся объектом будет оставать( ся большой шлейф, состоящий из предыдущих сформированных кадров.
5.5. Механизм работы программы Изучив исходный код обоих классов Program и Game1, необходимо разобраться с общим механизмом работы всей программы и тем, как все рассмотренные нами методы на самом деле функционируют. Итак, откомпилировав и собрав проект FirstProject, вы тем самым создали готовую программу, которая находится в папках Code\Chapter5\FirstProject\bin\x86\Release или Debug. После запуска программы среда времени выполнения CLR ищет входную точку в приложении. Этой точкой является метод main() класса Program. Вы( полнение этого метода приводит к созданию нового класса Game1. В момент со( здания класса Game1 управление программой на какой(то момент переходит к файлу Game1.cs. Там происходит инициализация всех глобальных переменных класса Game1, а затем выполнение исходного кода конструктора класса. Как толь( ко конструктор создал класс Game1, управление программой опять возвращается к классу Program и исходному коду файла Program.cs. После этого в работу вступает метод game.Run() класса Program. Этот ме( тод создает системный поток и запускает цикл, в котором происходит выполнение соответствующих методов класса Game1. В частности, первым в дело вступает метод Initialize() класса Game1. В методе Initialize() происходит ини( циализация всех компонентов игры, которые в нем расположены. Затем управле( ние программой переходит к методу LoadGraphicsContent(). Здесь происхо( дит загрузка всей графической составляющей проекта, после чего в цикле, созданном методом game.Run() класса Program, происходит цикличное выпол( нение двух методов – Update() и Draw(). Метод Update() постоянно следит за обновлением состояния игры, а метод Draw() рисует графику на экране монитора. Работа этих методов выполняется до тех пор, пока не последует команда выхода из программы. Таким образом, меха( низм работы программы сводится к организации фактически бесконечного цикла, в котором происходит выполнение методов Update() и Draw() для постоянной смены состояния игры или цикличной смены кадров в этой самой игре. Не так все и сложно на самом деле, не правда ли?! Достаточно хорошо проду( мать общую концепцию структуры будущих классов и на базе механизма работы
Переходим в полноэкранный режим
73
классов Program и Game1 реализовать свою идею. В дальнейшем мы так и будем поступать, с той лишь разницей, что параллельно изучению платформы XNA и XNA Game Studio Express мы будем создавать еще свою собственную неболь( шую игру. Для того чтобы более подробно изучить работу проекта FirstProject, ис) пользуйте отладчик инструментария Visual C# Express. Для этого необхо) димо откомпилировать проект и выполнить команды Debug ⇒ Step Into либо нажать на клавиатуре клавишу F11. Инструментарий Visual C# Express запустит механизм пошаговой отладки. Каждое последующее на) жатие клавиши F11 (Debug ⇒ Step Into) будет перемещать вас на один шаг вперед в работе программы. Таким образом, можно проследить в поша) говом режиме весь механизм функционирования программы и ее устрой) ство.
5.6. Переходим в полноэкранный режим Итак, двигаемся дальше и из оконного режима работы программы переходим в полноэкранный режим. Полноэкранный режим работы программы – это режим, в котором приложение захватывает всю рабочую поверхность дисплея и работает в приоритетном режиме. Чаще всего этот режим используется в играх, поэтому мы с самого первого примера переходим в полноэкранный режим работы. Продолжим модифицировать исходный код первого проекта. Далее в книге все новые строчки исходного кода, добавляемые в каждый последующий проект, я буду выделять жирным шрифтом, чтобы вам было легче следить за изменения( ми в исходном коде. Создадим второй проект с названием FullScreen и скопируем в него исходный код из первого проекта. В качестве названия проекта будем ис( пользовать имя FullScreen. Исходный код проекта находится на компакт(диске к книге в папке Code\Chapter5\ FullScreen. Для того чтобы создать полноэкранный режим, в исходный код конструктора класса Game1() необходимо добавить следующие строки кода. graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.ToggleFullScreen();
Первые две строки задают размер экрана по горизонтали и вертикали, ус( танавливая тем самым разрешение экрана. Для игры используется разрешение 1024 × 768 пикселей. Если у вас другой режим разрешения экрана, то задайте свои значения для этих двух параметров настроек. Параметры PreferredBackBufferHeight и PreferredBackBufferHeight задают разрешение для так называемого заднего буфера, или внеэкранной по( верхности. Этот буфер необходим для создания плавной анимации в играх, и его
74
Переходим в полноэкранный режим
Формируем каркас игровых классов
механизм функционирования чрезвычайно прост. Чтобы достичь плавной игро( вой анимации, создается дополнительный задний буфер, идентичный по своим параметрам первичной поверхности экрана. Каждый последующий кадр графики рисуется непосредственно в этом буфере. Когда графика на экране стирается оп( ределенным цветом, то происходит показ информации, находящейся в заднем бу( фере, а новый кадр формируется уже в следующем заднем буфере. Такое цикли( ческое переключение между внеэкранными поверхностями дисплея приводит к плавной анимации игрового процесса. Если стирать графику непосредственно на экране и тут же ее рисовать заново, то ваш глаз обязательно все это увидит, и будет казаться, что графика на экране дерга( ется, поэтому в свое время в DirectX и была придумана концепция работы заднего буфера. Смена поверхностей экрана происходит в автоматическом режиме и осу( ществляется системными сервисами, поэтому специально беспокоиться об этом не нужно. Просто задайте размер клиентской области экрана для заднего буфера па( раметрами PreferredBackBufferHeight и PreferredBackBufferHeight, а система выполнит всю черновую работу сама. Далее мы отключаем в приложении работу Multisampling, выставив его значение в false, а последняя строка кода в этом блоке graphics. ToggleFullScreen() включает полноэкранный режим отображения информа( ции. Вот и все настройки. Если не использовать параметры настройки режима дисплея PreferredBackBufferHeight и PreferredBackBufferHeight, то разрешение экрана даже в полноэкранном режиме будет установлено по умолча( нию в 800 × 600 пикселей. После того как работа программы будет происходить в полноэкранном режи( ме, необходимо предусмотреть корректный выход из программы. В предыдущем проекте FirstProject в качестве механизма выхода из программы в методе Update() использовался следующий исходный код: protected override void Update(GameTime gameTime) { if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); base.Update(gameTime); }
Метод Update() следит за обновлением состояния игры, а значит, именно в этом методе можно реализовать обработку событий, получаемых с устройств ввода информации (джойстика, мыши и клавиатуры). Вышеприведенный исход( ный код направлен исключительно на работу с джойстиком и формируется авто( матически шаблоном Visual C# Express. Этот код и механизм выхода из програм( мы также актуален и сейчас, но только в том случае, если в качестве устройства ввода используется джойстик. Если нет, то нужно реализовать дополнительный код для выхода из программы по нажатии одной из клавиш клавиатуры. Обычно в качестве кнопки выхода используется клавиша Esc.
75
Для этих целей в глобальных переменных класса Game1 объявим дополни( тельный объект. KeyboardState keyboardState;
Этот объект класса KeyboardState предназначен для работы с клавиатурой. Затем в методе Update() добавим три строки исходного кода. keyboardState = Keyboard.GetState(); if(keyboardState.IsKeyDown(Keys.Escape)) this.Exit();
В первой строке этого кода объект keyboardState инициализируется по( средством вызова метода Keyboard.GetState(), что позволяет нам получать события с клавиатуры и хранить их в keyboardState. Метод Update() работа( ет в циклическом режиме, то есть код этого метода выполняется строка за строкой до самого конца. После чего выполнение метода начинается снова в том же ключе от первой строки кода метода на протяжении работы всей программы. Все это означает, что один проход по коду метода Update() от начала до конца – это и есть один кадр в игре, соответственно состояние объекта keyboardState будет обновляться в каждом новом кадре. Во второй строке блока этого исходного кода используется логическая конст( рукция операторов if/else, позволяющая производить слежение за состоянием нажатия клавиш. Здесь все просто, если клавиша Esc на клавиатуре нажата – keyboardState.IsKeyDown(Keys.Escape), то происходит вызов метода Exit(), который останавливает работу программы и закрывает приложение. Ме( тод Exit() – это системный метод, необходимый для корректного завершения программы. В ином случае исходный код метода Update() продолжает выполняться дальше строка за строкой. По необходимости конструкцию if/else можно за( ключить в фигурные скобки, чтобы код стал читабельным и более понятным. keyboardState = Keyboard.GetState(); if(keyboardState.IsKeyDown(Keys.Escape)) { this.Exit(); }
Ниже приведен полный листинг 5.3 исходного кода файла Game1.cs проекта FullScreen. Все добавленные строки кода выделены жирным шрифтом. На ком( пакт(диске проект находится в папках Code\Chapter5\ FullScreen. //========================================================================= /// <summary> /// Ëèñòèíã 5.3
76
Переходим в полноэкранный режим
Формируем каркас игровых классов {
/// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 5 /// Ïðîåêò: FullScreen /// Êëàññ: Game1 /// Ïåðåõîä â ïîëíîýêðàííûé ðåæèì /// <summary> //=========================================================================
if (loadAllContent) { } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } }
#region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion
/// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if(keyboardState.IsKeyDown(Keys.Escape)) this.Exit();
namespace FullScreen { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; } /// <summary> /// Èíèöèàëèçàöèÿ /// <summary> protected override void Initialize() { base.Initialize(); } /// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent)
base.Update(gameTime); } /// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); base.Draw(gameTime); } } }
77
Проект DrawSprite
Глава 6 Работа с двухмерной графикой
79
ность работы с форматами BMP, JPG, PNG, TGA и DDS. Это самые распростра( ненные и чаще всего используемые в играх графические форматы. Отображая на экране монитора спрайт, необходимо понимать следующее ус( ловие. Любой спрайт – это изображение, которое заключено в прямоугольник. Рисуя спрайт на экране, его начальной точкой отчета всегда будет оставаться верхний левый угол этого самого прямоугольника (рис. 6.2). Поэтому если вы оп( ределяете, допустим, столкновения между спрайтами, то вам необходимо знать ширину и высоту изображения, чтобы прибавить эти значения к начальной точке координат спрайта (левый верхний угол). Соответственно середина спрайта будет находиться в половине ширины и высоты графического изображения (рис. 6.2).
В этой главе мы начнем работать с двухмерными изображениями и создадим два проекта. В первом проекте мы загрузим в игру и выведем на экран монитора про( стое двухмерное изображение, или, как принято говорить, спрайт. В этом проекте общая структура классов останется неизменной, такой, какой она была в преды( дущих двух проектах пятой главы. Весь код по загрузке и выводу изображения на экран будет написан непосредственно в классе Game1. После того как вы разбере( тесь и поймете, как загружаются и рисуются двухмерные изображения на экране, мы создадим второй, более сложный проект. Идея второго проекта (DrawSpriteClass) также будет заключаться в загрузке и выводе на экран спрайта, но в этом случае структура проекта будет значительно улучшена. В частности, добавится новый класс Sprite, отвечающий за создание и загрузку двухмерных изображений. Впоследствии с каждой новой главой класс Sprite будет постоянно обновляться и улучшаться, обрастая новыми функцио( нальными возможностями.
6.1. Система координат Двухмерная система координат, предназначенная для представления графики в компьютерных играх, перевернута по отношению к обычной декартовой систе( ме координат. Начало этой системы координат находится в левом верхнем углу экрана. Положительная ось Х проходит по верхней кромке экрана слева направо, а положительная ось Y – сверху вниз по левой боковой стороне экрана. Отрица( тельная часть обеих осей координат лежит за областью экрана монитора, как это показано на рис. 6.1. Игровая графика, которая не по( падает в область экрана, отсекается системными серви( сами и не участвует в построении сцены. Ключевым понятием в двухмерной графике также является спрайт. Спрайт – это простое двухмерное изображение, нарисованное в любом графическом ре( дакторе и сохраненное в одном из графических форма( тов. В XNA Game Studio Express реализована возмож( Рис. 6.1. Система координат
Рис. 6.2. Представление спрайта на экране
6.2. Проект DrawSprite В этом проекте нам предстоит нарисовать спрайт на экране, или, иначе, вывести определенное изображение в заданном месте дисплея. Что для этого нужно? Преж( де всего необходимо само изображение в формате, понятном XNA Game Studio Express. Такое изображение можно создать в любом графическом редакторе. За( тем в нарисованном изображении нужно вырезать фон, чтобы при выводе спрайта на экран вокруг исходного изображения фона не было видно. Делается это с по( мощью графического редактора, и чаще всего для этих целей используется Adobe Photoshop, который уже давно стал своего рода промышленным стандартом.
80
Проект DrawSprite
Работа с двухмерной графикой
После того как вы подготовили изображение, можно приступать к работе с исходным кодом. Создайте новый проект (у нас это DrawSprite) и скопируйте в него программный код из предыдущего примера пятой главы. Либо можно прос( то модернизировать предыдущий проект, или открыть готовую программу с ком( пакт(диска, которая находится в папке Code\Chapter6\DrawSprite. Чтобы работать с изображениями в проектах, необходимо эти изображения добавить в ваш проект. При этом простое добавление спрайта в каталог с програм( мой никаких результатов не даст, необходимо явно добавить спрайт в проект. Де( лается это следующим образом. Все изображения игры, а также модели, шрифты, звуковые файлы можно хра( нить прямо в корневом каталоге проекта, но значительно лучше организовать для этих целей простую и понятную иерархию каталогов. В справочной информации по XNA Game Studio Express предлагается следующая структура папок для хране( ния в проекте различных компонентов игры. Папка Content – это большая общая папка, в которой хранится весь так называемый игровой контент или компоненты игры. Создав такую папку в каталоге проекта, впоследствии в ней вы будете создавать дополнитель( ные папки для различных файлов, например графических изображений, моделей, звуковых файлов, шрифтов и т. д. Папка Textures – это вложенная папка в папке Content, которая содержит все графические изображения, будь то спрайты, текстуры, игровые карты и т. д. Папка Models – это также вложенная подпапка в папке Content, где содер( жатся все трехмерные модели проекта. Дополнительно если при создании модели текстурная составляющая этой модели была прописана под один ка( талог модели, то можно хранить необходимые текстуры прямо в этой папке. К этим двум подпапкам папки Content можно добавлять любое количество папок для хранения, например, звуковых файлов, шрифтов, эффектов, вплоть до создания папок для каждого уровня или нескольких однотипных файлов исход( ного кода. Конечно, на самом деле все эти разделения по папкам – дело личных предпочтений, и никто вам не мешает хранить весь контент игры в корневом ката( логе, но в книге используется именно такая модель разделения файлов по папкам. Для того чтобы создать структуру папок, необходимо в текущем проекте, в панели Solution Explorer щелкнуть правой кнопкой мыши на названии проекта и в контекстном меню выбрать команды Add ⇒ New Folder. По выполнении этой команды в каталоге проек( та сформируется новая папка, которой необходимо дать название Content. Затем в этой папке создайте еще одну папку, но уже с названием Textures, где у нас и бу( дет находиться графический файл sprite.png (рис. 6.3). Чтобы явно добавить файл в текущий проект, в панели Solution Explorer щелкните правой кнопкой Рис. 6.3. Папка Content
81
мыши на названии папки Textures и в контекстном меню выберите команды Add ⇒ Exiting Item. Откроется диалоговое окно Add Exiting Item (рис. 6.4). В этом окне в списке Files of Types нужно выбрать вид файла, который требуется добавить в проект. Все графические изображения и модели принадлежат к типу Content Pipeline. Выберите этот тип, найдите спрайт, где бы он у вас ни находился, выделите его курсором и нажмите кнопку Add. Вне зависимости от того, в какой директории лежал графический файл, Visual C# Express скопирует его в выбран( ную папку проекта.
Рис. 6.4. Добавляем файл в проект
В нашем проекте изображение sprite.png представляет собой девушку, кото( рая падает, а точнее будет падать сверху вниз (рис. 6.5). Рисунок был нарисован специально под нашу задачу, поэтому девушка представлена в такой необычной позе. Изображение девушки заключено в прямоугольник, но при этом белый фон рисунка вырезан средствами Photoshop. Сейчас изображение статично и состоит из одного фрейма, или одного кадра, но в следующей главе мы дорису( ем еще несколько фреймов для создания иллюзии анимации. Добавив спрайт в проект, можно приступать к работе над исходным кодом класса Game1. В гло( бальных переменных к уже имеющимся объявлени( ям добавляем следующие три строки кода. Рис. 6.5. Статичное изображение девушки
82
Проект DrawSprite
Работа с двухмерной графикой
Texture2D spriteTexture; Vector2 spritePosition; SpriteBatch spriteBatch;
В первой строке кода объявляется объект spriteTexture класса Texture2D. Это системный класс, позволяющий работать с двухмерными текстурами. Объект этого класса spriteTexture как раз и будет представлять или содержать графи( ческий файл sprite.png. Объект spriteTexture отвечает только за загрузку в программу изображений, а с помощью объекта spriteBatch класса SpriteBatch происходит рисование спрайтов на дисплее. Во второй строке кода этого блока объявляется переменная spritePosition структуры Vector2. Эта структура дает возможность задавать сразу две коорди( наты по осям X и Y. С помощью переменной spritePosition мы сможем задать точку вывода спрайта на экран. Платформа XNA также имеет в своем составе еще структуры Vector3 и Vector4 для работы с тремя и четырьмя координатами со( ответственно (X, Y, Z и W). В этом случае ось Z представляет буфер глубины, а величина W – это коэффициент перспективы. В последней строке блока создается еще один объект spriteBatch класса SpriteBatch. Класс SpriteBatch имеет в своем составе сервисы, необходимые для вывода или рисования графических изображений на экране монитора. Далее в методе LoadGraphicsContent() создадим, или инициализируем (кто к чему привык), объект spriteBatch. protected override void LoadGraphicsContent(bool loadAllContent) { if(loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); spriteTexture = content.Load(“Content\\Textures\\sprite”); } }
В этом примере создание объекта spriteBatch происходит типичным, или стандартным, образом. Эта конструкция кода имеет место во всех программах. В следующей строке кода происходит загрузка изображения sprite.png в програм( му. Для этих целей используется метод Load(), а в качестве его параметра указывается полный путь к графическому изображению. В этой записи два слэша обозначают одну из папок в рабочем каталоге проекта. Если вы хотите загрузить текстуру, например, из корневого каталога, то достаточно указать про( сто имя файла без слэшей, если, конечно, ваш файл действительно находится в корневом каталоге. Загрузка графического файла в программу происходит посредством сервисов XNA Content Pipeline. В строке загрузки графического файла указывается только название файла без его расширения. Загрузчику XNA Content Pipeline абсолютно все равно, в каком формате выполнено изображение, главное, чтобы это были
83
форматы, с которыми он умеет работать (BMP, JPG, PNG, TGA и DDS), а все ос( тальное – дело техники. После компиляции приложения XNA Content Pipeline преобразует все графические ресурсы игры в свой специфический формат XNB, и программа будет читать уже преобразованные файлы. Далее в методе Initialize() мы зададим две координаты по осям X и Y для вывода спрайта на экран монитора. protected override void Initialize() { spritePosition.X = Window.ClientBounds.Width / 2; spritePosition.Y = Window.ClientBounds.Height / 2; base.Initialize(); }
Начальная точка координат для спрайта – это его верхний левый угол. Пере( менная spritePosition представляет собой вектор, который позволяет зада( вать координаты по двум осям. Обращение к осям X и Y происходит посредством оператора точка. Дополнительно можно использовать и другую конструкцию кода с явным созданием объекта spritePosition следующим образом. spritePosition = new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height / 2);
Эта запись идентична предыдущей записи. Кстати, параметры Window. ClientBounds.Width и Window.ClientBounds.Height автоматически оп( ределяют размер экрана по ширине и высоте соответственно. А деление этих зна( чений на 2 позволяет найти центр экрана. В ином случае можно было записать следующие строки. spritePosition.X = 512; /* 1024/2 */ spritePosition.Y = 384; /* 768/2 */
Если не определять координаты для вывода спрайта на экран, то по умолча( нию значение положения спрайта задается двумя нулями, то есть верхний левый угол экрана или окна. После всех этих действий мы будем иметь загруженный в программу спрайт, а точнее механизм, который будет загружать в программу спрайт и точку вывода спрайта на экране. Теперь нам осталось только нарисовать/вывести/отобразить спрайт на экране. Для этих целей служит метод Draw(), код которого изменяется следующим образом: protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(spriteTexture, spritePosition, Color.White);
84
Проект DrawSprite
Работа с двухмерной графикой spriteBatch.End(); base.Draw(gameTime);
}
Методы Begin() и End() класса SpriteBatch задают начало и окончание представления всей двухмерной сцены на экране монитора. Между двумя вызова( ми этих методов необходимо производить вывод или рисование всех графических изображений на экране. На каждый вызов метода Begin() должен обязательно следовать вызов метода End(). Это необходимое условие! Между вызовами методов Begin() и End() происходит вызов метода Draw() класса SpriteBatch. spriteBatch.Draw(spriteTexture, spritePosition, Color.White);
Первым параметром этого метода является спрайт, представленный объектом spriteTexture. Второй параметр задает расположение спрайта на экране по осям X и Y, а последний параметр устанавливает цветовую составляющую спрай( та или то, каким цветом будет закрашен спрайт. Сейчас используется белый цвет, что означает рисование спрайта в оригинале без какой(либо дополнительной ре( туши цветом. В этом параметре для окраски спрайтов можно использовать любые доступные цвета. После компиляции и запуска проекта DrawSprite на экране монитора ото( бразится графическое изображение. В листинге 6.1 класса Game1 проекта DrawSprite находится полный исходный код рассматриваемого примера. //========================================================================= /// <summary> /// Ëèñòèíã 6.1 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 6 /// Ïðîåêò: DrawSprite /// Êëàññ: Game1 /// Âûâîä ñïðàéòà íà ýêðàí ìîíèòîðà /// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace DrawSprite
{ public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; Texture2D spriteTexture; Vector2 spritePosition; SpriteBatch spriteBatch; /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; } /// <summary> /// Èíèöèàëèçàöèÿ /// <summary> protected override void Initialize() { spritePosition.X = Window.ClientBounds.Width / 2; spritePosition.Y = Window.ClientBounds.Height / 2; base.Initialize(); } /// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if(loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); spriteTexture = content.Load(“Content\\Textures\\sprite”); } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if(unloadAllContent == true) {
85
86
Работа с двухмерной графикой content.Unload(); } } /// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { … }
Проект DrawSprite Class
87
ект, а точнее создать в проекте еще один дополнительный класс с названием Sprite. Делается это следующим образом. В открытом проекте в панели Solution Explorer щелкните правой кнопкой мыши на названии проекта и в контекстном меню выберите команды Add ⇒ New Item. В появившемся диалоговом окне Add New Item – DrawSpriteClass выдели( те курсором мыши шаблон Class и в поле Name задайте имя будущему классу (рис. 6.6). После этого в рабочем каталоге проекта инструментарий Visual C# сформирует новый файл под названием Sprite.cs.
/// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(spriteTexture, spritePosition, Color.White); spriteBatch.End(); base.Draw(gameTime); } } }
6.3. Проект DrawSpriteClass В первом проекте, работая с изображением, мы использовали класс Game1, в кото( ром непосредственно обеспечивалась загрузка спрайта в программу. Такой под( ход оправдан для небольших демонстрационных примеров, где программный код может составлять несколько десятков строк. В больших играх этот подход не го( дится, поскольку количество строк исходного кода может составлять несколько тысяч и в итоге можно элементарно запутаться в своем коде, не говоря уже о сто( ронних программистах. Поэтому перед созданием игры всегда необходимо тща( тельно продумывать структуру будущих классов. Тем более что язык программи( рования C# тем и хорош, что он объектно(ориентированный и все его лучшие качества заключены именно в этом стиле программирования. В проекте DrawSpriteClass мы создадим дополнительный класс Sprite, который будет отвечать за все спрайты, загружаемые в игру. Далее с каждой новой главой функционал класса Sprite будет пополняться новыми методами, что по( зволит нам разработать универсальный класс для работы с графическими изобра( жениями, который в дальнейшем вы можете усовершенствовать по своему усмот( рению. Проект DrawSpriteClass основан на исходном коде предыдущего проекта DrawSprite, но с некоторыми дополнениями и переработками. Проект будет включать в себя классы Program, Game1 и Sprite. Исходный код класса Sprite находится в отдельном файле Sprite.cs. Этот файл необходимо добавить в про(
Рис. 6.6. Добавляем в проект новый класс Sprite
6.3.1. Класс Sprite проекта DrawSpriteClass Рассмотрим исходный код класса Sprite (листинг 6.2), а затем перейдем к его подробному анализу. //========================================================================= /// <summary> /// Ëèñòèíã 6.2 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 6 /// Ïðîåêò: DrawSpriteClass /// Êëàññ: Sprite /// Ñîçäàåì êëàññ Sprite
88
Работа с двухмерной графикой
/// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace DrawSpriteClass { public class Sprite { public Texture2D spriteTexture; public Vector2 spritePosition; /// <summary> /// Êîíñòðóêòîð /// <summary> public Sprite() { } /// <summary> /// Çàãðóçêà ñïðàéòà â èãðó /// <summary> public void Load(ContentManager content, String stringTexture) { spriteTexture = content.Load(stringTexture); } /// <summary> /// Ðèñóåì ñïðàéò /// <summary> public void DrawSprite(SpriteBatch spriteBatch) { spriteBatch.Draw(spriteTexture, spritePosition, Color.White); } } }
В сформированном файле Sprite.cs в области подключения библиотечных классов добавляются дополнительные библиотеки, которые при формировании шаблона Visual C# добавлены не были. Затем в исходном коде класса Sprite идет объявление двух объектов. public Texture2D spriteTexture; public Vector2 spritePosition;
Проект DrawSprite Class
89
Первый объект spriteTexture класса Texture2D будет содержать загру( женное в программу изображение, а второй объект spritePosition класса Vector2 – задавать местоположение спрайта на экране. Здесь наблюдается пря( мая аналогия с примером, рассмотренным в предыдущем проекте, но все объявле( ния и действия по загрузке изображений будут происходить непосредственно в классе Sprite. Конструктор класса Sprite остается без реализации. В следующей главе мы будем изучать спрайтовую анимацию и создадим дополнительный конструктор для класса Sprite, направленный на создание объекта с возможностью анима( ции. В итоге у нас получится два разных конструктора: один – для создания не( анимированных объектов, а другой – для анимированных изображений. Затем в исходном коде файла Sprite.cs следует описание метода Load(), ко( торый в качестве параметров принимает объект content класса ContentManager и объект stringTexture класса String. public void Load(ContentManager content, String stringTexture) { spriteTexture = content.Load(stringTexture); }
Объект content необходим для вызова метода Load(), а значит, и для возможности загрузки в программу изображения непосредствен( но через класс Sprite. Второй объект stringTexture будет содержать строку текста, в которой мы будем передавать путь к загружаемому изображению. На этом этапе можно было сразу прописать путь к графическому файлу, но тогда метод Load() можно будет использовать только один раз для загрузки одного жестко заданного изображения. В нашем случае метод Load() универсален и годится для загрузки любого спрайта. Достаточно лишь вызвать этот метод в классе Game1 и в качестве параметров передать объект content и указать путь к спрайту. После метода Load() идет реализация метода DrawSprite(), отвечающего за рисование спрайта на экране монитора. public void DrawSprite(SpriteBatch spriteBatch) { spriteBatch.Draw(spriteTexture, spritePosition, Color.White); }
В этом методе происходит вызов системного метода Draw(), а в качестве па( раметров выступают спрайт, позиция на экране и цветовая составляющая. В сам метод DrawSprite() в качестве параметра передается объект spriteBatch класса SpriteBatch, для возможности вызова метода Draw() непосредственно через класс Sprite. На этом реализация класса Sprite закончена, давайте пе( рейдем к рассмотрению исходного кода класса Game1.
90
Проект DrawSprite Class
Работа с двухмерной графикой
91
protected override void Initialize() { sprite.spritePosition = new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height / 2);
6.3.2. Класс Game1 проекта DrawSpriteClass Исходный код класса Game1 представлен в листинге 6.3. //========================================================================= /// <summary> /// Ëèñòèíã 6.3 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 6 /// Ïðîåêò: DrawSpriteClass /// Êëàññ: Game1 /// Ñîçäàåì êëàññ Sprite /// <summary> //=========================================================================
base.Initialize(); }
/// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); sprite.Load(content, “Content\\Textures\\sprite”); } }
#region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion
/// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { … }
namespace DrawSpriteClass { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; SpriteBatch spriteBatch; Sprite sprite;
/// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit(); base.Update(gameTime);
/// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; sprite = new Sprite(); }
}
/// <summary> /// Èíèöèàëèçàöèÿ /// <summary>
} }
/// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); sprite.DrawSprite(spriteBatch); spriteBatch.End(); base.Draw(gameTime);
}
92
Работа с двухмерной графикой
Заметьте, что код из предыдущего примера по загрузке спрайта в классе Game1 был удален, потому что загрузка изображения теперь происходит через класс Sprite. В глобальных переменных класса Game1 добавляется объявление нового объекта sprite класса Sprite. Sprite sprite;
А в конструкторе класса Game1 происходит создание этого объекта строкой кода. sprite = new Sprite();
Затем в методе LoadGraphicsContent() через объект sprite вызывается метод Load() класса Sprite, который загружает изображение в игру. В качестве второго параметра этого метода передается путь к графическому файлу sprite.png. protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); sprite.Load(content, “Content\\Textures\\sprite”); } }
Потом в методе Initialize() через объект sprite мы обращаемся к объек( ту spritePosition и задаем начальную точку вывода изображения на экран монитора. protected override void Initialize() { sprite.spritePosition = new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height / 2); base.Initialize(); }
И в конце в методе Draw() вызываем метод DrawSprite() класса Sprite для вывода спрайта на дисплей в заданном месте.
Глава 7 Спрайтовая анимация Изображения в играх могут быть как анимированными, так и неанимированны( ми. Неанимированное изображение – это некий рисунок заданного размера, сос( тоящий из одного кадра, или фрейма. Примером неанимированного изображения может послужить статический или двигающийся объект, рассмотренный нами в предыдущей главе. Анимированное изображение – это изображение, состоящее из определенного набора фреймов или ряда последовательных, взаимосвязанных изображений (анимационная последовательность). Переход по всем имеющимся фреймам анимационной последовательности создает иллюзию анимации в игро( вом процессе. Анимированное изображение может состоять из любого количества фреймов, задающих анимационную последовательность для персонажей игры. Отсчет фреймов изображения происходит от нуля, точно так же, как это делается в про( стом массиве данных. Количество фреймов анимационной последовательности не ограничено, но весь набор последующих фреймов анимационной последова( тельности должен совпадать с размером самого первого фрейма как по ширине, так и высоте. То есть все фреймы должны быть одинакового размера. Располагать фреймы изображения можно горизонтально, вертикально или компоновать лю( бым удобным для вас способом. Отсчет по фреймам изображения всегда происхо( дит слева направо и сверху вниз. Посмотрите на рис. 7.1, где изображен шагаю( щий робот. Изображение шагающего робота на рис. 7.1 состоит из четырех фреймов ани( мационной последовательности, где каждый фрейм определяет одну из фаз дви( жения робота. Циклическое перемещение по фреймам этого изображения или по( стоянная перерисовка фреймов на экране монитора создаст эффект ходьбы робота в игре. Этот механизм работы с анимацией используется в компьютерных и мобильных играх, а также в мультипликационных фильмах.
sprite.DrawSprite(spriteBatch);
На этом этапе это все улучшения и новшества, добавленные в исходный код бу( дущей игры. После запуска игры вы увидите на экране девушку в интересной позе. Рассматриваемый пример находится на компакт(диске в папке Code\Chapter6\ DrawSpriteClass. Откомпилируйте и запустите программу, а также поэкспери( ментируйте с выбором различных значений для задания позиций спрайта на экра( не монитора.
Рис. 7.1. Шагающий робот
94
Проект Animation
Спрайтовая анимация
В этой главе вашему вниманию будет представлено два проекта: Animation и Background. В первом проекте Animation вы научитесь загружать в программу анимационные последовательности и создавать анимацию на экране. Во втором проекте или во второй части этой главы мы добавим в игру статическое фоновое изображение (как принято говорить, Background, или задний фон), заметно улуч( шив тем самым графический интерфейс всей игры. Итак, переходим к работе над проектами.
7.1. Проект Animation Задача этого проекта заключается в загрузке в программу анимационной после( довательности, а также реализации механизма перебора всех имеющихся фрей( мов. Сам же спрайт рисуется в центре экрана без возможности его перемещения по экрану.
7.1.1. Анимационная последовательность В нашей игре анимационная последовательность будет состоять из набора не( скольких фреймов, имитирующих движение объекта в момент его падения сверху вниз. В предыдущей главе в проект загружалось изображение девушки, состоящее из одного фрейма. Чтобы сделать анимационную последовательность, необходимо к имеющемуся фрейму дорисовать еще несколько дополнительных фреймов. Коли( чество фреймов и содержание самого рисунка может быть любым, главное, чтобы циклический переход по анимационной последовательности создавал эффект ка( кого(то движения, которое было близко к естественным движениям. Наша задача в этой связи понятна и не очень проста. Необходимо нарисовать падающую сверху вниз девушку, которая должна «трепыхать» руками, ногами, телом и т. д. Заметьте, что чем больше фреймов вы нарисуете, тем плавнее будет анимация, но и, с другой стороны, уж очень большое количество фреймов может несколько притормаживать игру. Конечно, в компьютерных играх количество фреймов одной анимационной последовательности не критично и может состав( лять 10–20 и более фреймов, тогда как в мобильных играх разработчикам прихо( дится себя сильно ограничивать в этом плане. На рис. 7.2 представлен набор фреймов для падающей с небес девушки.
Рис. 7.2. Анимационная последовательность девушки
95
7.1.2. Класс Sprite проекта Animation В исходный код класса Sprite проекта Animation необходимо добавить несколь( ко новых элементов по сравнению с предыдущим проектом. Как вы помните, все наши последующие разработки модифицируют предыдущие. В первую очередь в классе Sprite нужно создать четыре новые переменные, объявление которых происходит в глобальной области исходного кода файла Sprite.cs. private private private private
int frameÑount; double timeFrame; int frame; double totalElapsed;
Первая переменная frameÑount впоследствии будет содержать в себе коли( чество фреймов нашей анимационной последовательности и примет прямое учас( тие в организации циклического показа фреймов на экране. Вторая переменная timeFrame получит значение времени, измеряемое в миллисекундах, отведенное для показа одного из фреймов на экране. Именно с помощью этой переменной мы будем регулировать время задержки показа одного фрейма на экране. Если не де( лать задержки для показа одного из фреймов анимации, то переход по всей анима( ционной последовательности пройдет с такой скоростью, что вы не успеете даже заметить, что мы там пытались анимировать и зачем. Следующая переменная frame – это своего рода счетчик, представляющий в текущий момент один из фреймов. Этот счетчик по прошествии заданного вре( мени задержки (переменная timeFrame) увеличивается на единицу для перехо( да к показу следующего фрейма. Последняя переменная totalElapsed будет со( держать значение времени, необходимое для расчета времени задержки показа одного фрейма, а точнее, расчета прошедшего времени, выделенного на показ од( ного из фреймов. После объявления в исходном коде всех переменных их необходимо инициа( лизировать заданными значениями. Для этих целей в классе Sprite формирует( ся второй конструктор, необходимый для создания анимированных объектов это( го класса. public Sprite(int frameCount, int framesPerSec) { frameÑount = frameCount; timeFrame = (float)1 / framesPerSec; frame = 0; totalElapsed = 0; }
Теперь в классе Sprite имеются два конструктора этого класса, позволяющие создавать как статические, так и анимированные спрайты или объекты этого класса. Второй конструктор класса Sprite содержит два целочисленных параметра frameCount и framesPerSec, которые передаются в создаваемый объект класса.
96
Проект Animation
Спрайтовая анимация
Первый параметр frameCount задает общее количество фреймов для загру( жаемого при создании этого объекта изображения. Количество фреймов загру( жаемой анимационной последовательности необходимо точно знать, и они опре( деляются на этапе создания рисунка. В нашем случае мы имеем 12 фреймов. На основе полученного значения или количества фреймов изображения происходит расчет времени задержки для показа одного фрейма на экране. Второй параметр framesPerSec конструктора класса Sprite в момент со( здания объекта этого класса получает количество кадров, которые необходимо показать за одну секунду. Иначе говоря, в этом параметре задается определенное количество кадров, показываемых на экране за одну секунду. Это значение позво( ляет рассчитать время задержки, необходимое для показа одного фрейма на экра( не. Параметр framesPerSec в какой(то мере позволяет задавать скорость пере( мещения по фреймам или задает скорость перебора фреймов анимационной последовательности изображения. Этот механизм дает возможность реализовы( вать различную степень скорости анимации объектов. Такой подход в реализации анимированного конструктора позволяет созда( вать множество различных объектов класса Sprite, каждый из которых волен содержать любое количество фреймов анимационной последовательности, а так( же определять количество фреймов изображения, показываемых за одну секунду. Далее в исходном коде класса Sprite добавляется новый метод UpdateFrame(), реализующий механику перебора фреймов изображения на основании получен( ного системного времени и его сравнения со временем задержки, отведенным на показ одного фрейма. public void UpdateFrame(double elapsed) { totalElapsed += elapsed; if (totalElapsed > timeFrame) { frame++; frame = frame % (frameÑount - 1); totalElapsed -= timeFrame; } }
В метод UpdateFrame() передается значение времени, полученное с момен( та предыдущего вызова этого метода, и его увеличение на единицу (получение и передача времени реализуются в классе Game1). Затем в конструкции кода if/else проверяется условие, в котором время задержки для показа одного фрейма (переменная timeFrame) не должно быть больше полученного значения времени. Как только это время больше или время задержки для показа одного фрейма истекает, то происходит увеличение счетчика фреймов на единицу и соот( ветственно переход к показу следующего фрейма. И в конце исходного кода класса Sprite добавляется новый метод DrawAnimationSprite(), предназначенный для рисования анимированных спрайтов, созданных посредством анимированного конструктора класса Sprite.
97
public void DrawAnimationSprite(SpriteBatch spriteBatch) { int frameWidth = spriteTexture.Width / frameÑount; Rectangle rectangle = new Rectangle(frameWidth * frame, 0, frameWidth, spriteTexture.Height); spriteBatch.Draw(spriteTexture, spritePosition, rectangle, Color.White); }
В первой строке кода этого метода переменная frameWidth инициализируется значением ширины одного фрейма анимационной последовательности, измеряе( мой в пикселях. Строка spriteTexture.Width позволяет получить текущую ши( рину графического изображения (длину всех 12 фреймов изображения sprite.png), и далее происходит деление этого значения на общее количество фреймов анимаци( онной последовательности (например: 200 пикселей / 5 фреймов = 40 пикселей), что в итоге дает нам искомую ширину одного фрейма. Вот поэтому все фреймы ани( мационной последовательности и должны быть одинаковыми! Дополнительно в этом исходном коде можно использовать и числовые значения для каждого конк( ретного изображения, но тогда для каждого загружаемого в игру спрайта вам при( дется писать отдельный метод DrawAnimationSprite(). Во второй строке кода метода DrawAnimationSprite() создается прямо( угольник или видимая прямоугольная область, которая по своему размеру равна ширине и высоте одного фрейма анимационной последовательности. То есть со( здается механизм отсечения всех фреймов анимационной последовательности, кроме одного(единственного фрейма, который рисуется в текущий момент на эк( ране монитора. Если не создавать такой механизм, то на экране будет нарисована вся анимационная последовательность разом, состоящая из 12 фреймов. Факти( чески в коде создается этакая дыра в стене заданного размера, за которой мы под( ставляем необходимые рисунки, чередуя их с заданной скоростью, что в итоге и создает эффект анимации. В последней строке кода метода DrawAnimationSprite() происходит про( рисовка одного из фреймов на экране монитора: spriteBatch.Draw(spriteTexture, spritePosition, rectangle, Color.White);
Где параметры: spriteTexture – это загруженное в игру изображение; spritePosition – позиция на экране монитора; rectangle – ограничивающий или отсекающий прямоугольник, равный ширине и высоте одного фрейма; Color.White – задает цвет для рисуемого изображения. Полный исходный код класса Sprite проекта Animation представлен в лис) тинге 7.1. //========================================================================= /// <summary>
98
Проект Animation
Спрайтовая анимация
/// Ëèñòèíã 7.1 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 7 /// Ïðîåêò: Animation /// Êëàññ: Sprite /// Ñîçäàåì àíèìàöèþ /// <summary> //=========================================================================
{
#region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics;
/// <summary> /// Çàãðóçêà ñïðàéòà â èãðó /// <summary> public void Load(ContentManager content, String stringTexture) { spriteTexture = content.Load(stringTexture); }
99
totalElapsed += elapsed; if (totalElapsed > timeFrame) { frame++; frame = frame % (frameÑount - 1); totalElapsed -= timeFrame; } } }
#endregion
/// <summary> /// Ðèñóåì ïðîñòîé ñïðàéò /// <summary> public void DrawSprite(SpriteBatch spriteBatch) { spriteBatch.Draw(spriteTexture, spritePosition, Color.White); }
namespace Animation { public class Sprite { public Texture2D spriteTexture; public Vector2 spritePosition; private int frameÑount; private double timeFrame; private int frame; private double totalElapsed;
/// <summary> /// Ðèñóåì àíèìèðîâàííûé ñïðàéò /// <summary> public void DrawAnimationSprite(SpriteBatch spriteBatch) { int frameWidth = spriteTexture.Width / frameÑount; Rectangle rectangle = new Rectangle(frameWidth * frame, 0, frameWidth, spriteTexture.Height); spriteBatch.Draw(spriteTexture, spritePosition, rectangle, Color.White); } }
/// <summary> /// Êîíñòðóêòîð /// <summary> public Sprite() { } } /// <summary> /// Êîíñòðóêòîð äëÿ àíèìèðîâàííîãî ñïðàéòà /// <summary> public Sprite(int frameCount, int framesPerSec) { frameÑount = frameCount; timeFrame = (float)1 / framesPerSec; frame = 0; totalElapsed = 0; } /// <summary> /// Öèêëè÷íûé ïåðåõîä ïî ôðåéìàì - àíèìàöèÿ /// <summary> public void UpdateFrame(double elapsed)
7.1.3. Класс Game1 проекта Animation Теперь давайте перейдем к рассмотрению класса Game1 проекта Animation. Ниже в листинге 7.2 представлен исходный код этого класса. Сначала посмотрим на весь код этого класса, а затем приступим к его изучению. //========================================================================= /// <summary> /// Ëèñòèíã 7.2 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 7
100
Проект Animation
Спрайтовая анимация
/// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); sprite.Load(content, “Content\\Textures\\sprite”); } }
/// Ïðîåêò: Animation /// Êëàññ: Game1 /// Ñîçäàåì àíèìàöèþ /// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion
/// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } }
namespace Animation { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; SpriteBatch spriteBatch; Sprite sprite;
/// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit(); double elapsed = gameTime.ElapsedGameTime.TotalSeconds; sprite.UpdateFrame(elapsed);
/// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; sprite = new Sprite(12, 10); }
base.Update(gameTime); } /// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
/// <summary> /// Èíèöèàëèçàöèèÿ /// <summary> protected override void Initialize() { sprite.spritePosition = new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height / 2); base.Initialize(); } /// <summary>
101
spriteBatch.Begin(SpriteBlendMode.AlphaBlend); sprite.DrawAnimationSprite(spriteBatch); spriteBatch.End(); base.Draw(gameTime); } } }
В этом коде в области глобальных переменных происходит объявление объек( та sprite класса Sprite.
102
Проект Background
Спрайтовая анимация
Sprite sprite;
103
{ graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
Затем в конструкторе класса Game1 создается полноценный объект sprite.
spriteBatch.Begin(SpriteBlendMode.AlphaBlend); sprite.DrawAnimationSprite(spriteBatch); spriteBatch.End();
sprite = new Sprite(12, 10);
Для формирования этого объекта используется конструктор класса Sprite, подготовленный для создания анимированных объектов. В качестве параметров в конструктор класса Sprite передаются два целочисленных значения. Первое значение – это количество фреймов анимационной последовательности для за( гружаемого в игру изображения. Второй целочисленный параметр конструктора класса Sprite задает определенное значение, на базе которого происходит расчет времени задержки для показа одного фрейма анимационной последовательности. Далее в исходном коде класса Game1 в методе Initialize() назначается позиция для спрайта на экране монитора, а в методе LoadGraphicsContent() происходит загрузка изображения sprite.png. Исходное изображение располага( ется в рабочем каталоге проекта в папках Content\Textures. Затем в методе Update() мы обновляем состояние игры и вызываем метод UpdateFrame() класса Sprite.
base.Draw(gameTime); }
Полный исходный код проекта Animation вы найдете на компакт(диске в пап( ке Code\Chapter7\Animation.
7.2. Проект Background Фоновые изображения в играх могут быть различного плана, здесь все зависит от конкретной задачи в реализации самой игры. В нашем случае фон представлен сразу двумя статическими изображениями размером в 1024 пикселя по ширине и 768 пикселей по высоте (размер экрана монитора). Поэтому начальной точкой вывода этих изображений служит начало отсчета системы координат (рис. 7.3).
protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit(); double elapsed = gameTime.ElapsedGameTime.TotalSeconds; sprite.UpdateFrame(elapsed); base.Update(gameTime); }
В качестве параметра в методе UpdateFrame() класса Sprite передается значение переменной elapsed. Эта переменная при каждой итерации игрового цикла, то есть при каждом новом проходе по методу Update(GameTime gameTime), получает текущее значение времени, прошедшее за один игровой цикл или за один проход по коду метода Update(GameTime gameTime). А уже в методе UpdateFrame() класса Sprite происходит сравнение времени задерж( ки на показ одного фрейма с прошедшим временем и соответственно решается, показывать по(прежнему этот фрейм анимационной последовательности или пе( реходить к следующему фрейму. В самом конце исходного кода класса Game1 происходит вызов метода Draw(), где на экран выводится один из фреймов всей анимационной последова( тельности изображения. protected override void Draw(GameTime gameTime)
Рис. 7.3. Расположение фона на экране монитора
В качестве фоновой картинки мы используем два изображения. Первое изображение – это основной фон игры, а второе изображение представляет спе( циальную рамку и бортик. Рамка рисуется по окантовке всего экрана, а внутрен( нее пространство этой рамки вырезается (рис. 7.4). Впоследствии в рамке будет выводиться подсчет очков и другая сопутствующая игровая информация. Шири( на рамки с правой стороны составляет 300 пикселей. Это значение нам понадо(
104
Проект Background
Спрайтовая анимация
105
но. Если же в других ваших играх фоновое изображение будет иметь гораздо боль( ше функциональных возможностей, например механизм скроллинга, то стоит со( здать для этих целей отдельный класс. Пример реализации скроллинга в играх показан в документации к XNA Game Studio Express (Раздел Programming Guide ⇒ Graphics ⇒ 2D Graphics ⇒ Make a Scrolling Background). В листинге 7.3 представлен код класса Game1 проекта Background. Полный исходный код всего проекта находится на компакт(диске в папке Code\Chapter7\ Background.
Рис. 7.4. Фоновое изображение
бится в дальнейшем при расчете местоположения падающих с неба объектов. Но возникает вопрос: а зачем нам два изображения, когда все это можно было нарисо( вать в одном рисунке? Делается это только с одной целью – для того чтобы украсить игру. Сначала на экране мы нарисуем фоновый рисунок, затем на этом фоне будем рисовать падающие объекты, а затем поверх всей графики наложим рамку. Получится, что все игровые объекты (кроме платформы) будут перемещаться (падать вниз) меж( ду фоном и рамкой. К самой рамке мы еще подрисуем трос и поверх рамки в сле( дующих главах нарисуем платформу. Таким образом, все объекты будут красиво падать между фоном и рамкой, а платформа – скользить по тросу. Если пользова( тель не успеет поймать объект своего желания, то он красиво упадет под рамкой и тросом, но поверх фонового изображения. Такой механизмы слоев позволяет формировать в двухмерных играх полноценный Z(буфер или ось Z. Графические файлы фонов background1.png и background2.png располага( ются в рабочем каталоге проекта в папке Content\Textures. Добавление изобра( жений в проект необходимо произвести явно через выполнение команд Add ⇒ Exiting Item (глава 6, раздел 6.2), как это мы делали для изображения sprite.png. Загружать фоновое изображение мы будем напрямую прямо в классе Game1, поскольку для фона не определено никаких дополнительных функций, то и созда( ние отдельного класса для фона или использование класса Sprite не обязатель(
//========================================================================= /// <summary> /// Ëèñòèíã 7.3 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 7 /// Ïðîåêò: Background /// Êëàññ: Game1 /// Äîáàâëÿåì ôîí /// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace Background { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; SpriteBatch spriteBatch; Sprite sprite; private Texture2D background1; private Texture2D background2; /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024;
106
Проект Background
Спрайтовая анимация graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; sprite = new Sprite(12, 10);
107
} /// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
} /// <summary> /// Èíèöèàëèçàöèÿ /// <summary protected override void Initialize() { sprite.spritePosition = new Vector2(300, 200);
spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(background1, new Vector2(0, 0), Color.White); sprite.DrawAnimationSprite(spriteBatch); spriteBatch.Draw(background2, new Vector2(0, 0), Color.White); spriteBatch.End();
base.Initialize(); }
base.Draw(gameTime);
/// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); sprite.Load(content, “Content\\Textures\\sprite”); background1 = content.Load(“Content\\Textures\\background1”); background2 = content.Load(“Content\\Textures\\background2”); } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } } /// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit();
} } }
В исходном коде класса Game1 в области глобальных переменных происходит объявление двух объектов background1 и background2. Эти объекты пред( ставляют в игре фоновое изображение. private Texture2D background1; private Texture2D background2;
Затем в методе LoadGraphicsContent() происходит загрузка фона в игру из файла background1.png и background1.png. В данном случае механизм загруз( ки фона идентичен механизму загрузки простого спрайта без создания дополни( тельного класса Sprite. Этот механизм мы изучали в начале главы 6. Background1 = content.Load(“Content\\Textures\\background1”); Background2 = content.Load(“Content\\Textures\\background2”);
В свою очередь, в методе Draw() фон, а также анимированный спрайт рисует( ся на экране монитора. protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(background1, new Vector2(0, 0), Color.White); sprite.DrawAnimationSprite(spriteBatch); spriteBatch.Draw(background2, new Vector2(0, 0), Color.White); spriteBatch.End();
double elapsed = gameTime.ElapsedGameTime.TotalSeconds; sprite.UpdateFrame(elapsed);
base.Draw(gameTime); base.Update(gameTime);
}
108
Спрайтовая анимация
Главное в исходном коде метода Draw() – это последовательность вывода ри( сунков на экран монитора. Каждое последующее рисуемое изображение наклады( вается поверх предыдущего. Запомнить это очень легко, просто помните, что пер( вая строка кода метода Draw(), которая рисует изображение, – это первый слой, следующая строка кода метода Draw() – соответственно следующий слой, кото( рый накладывается поверх предыдущего слоя, и т. д. (рис. 7.5). Этот механизм позволяет формировать многослойные фоновые и игровые сцены, где, например, главный герой может заходить за дом или дерево, формируя тем самым потен( циальную ось Z, которая удалена от глаз пользователя как бы внутрь монитора (трехмерный мир).
Рис. 7.5. Наложение слоев в игровом мире
Если в нашем коде взять и поменять местами строки с выводом на экран фона и спрайта, то на дисплее, кроме фона, вы ничего не увидите, потому что фоновый рисунок размером во весь экран просто(напросто закроет собой нашу анимацию. Это, кстати, очень частая ошибка со стороны начинающих программистов, поэто( му не забывайте: каждый последующий слой накладывается поверх предыду щего! На этом все, переходим к следующей главе и поговорим о движении объектов в пространстве, а также о некоторых особенностях реализации простого искусст( венного интеллекта.
Глава 8 Движение спрайтов в пространстве Одной из главных составляющих любой игры является перемещение объектов в игровом пространстве. Часть таких объектов может перемещаться в соответствии с командами, получаемыми от пользователя через устройство ввода информации (клавиатуру, джойстик или мышь). Как правило, это либо главный герой игры, либо определенный инструмент, играющий главную роль в игровом процессе, как это задумано в нашей игре. Как вы помните, у нас в качестве такого инструмента выступает платформа, висящая над пропастью и ловящая различные объекты, па( дающие с неба. Другая часть объектов игры может перемещаться на основе своей логики, за( программированной создателем. Такая логика в играх называется игровым ис) кусственным интеллектом. В этой главе мы наделим наши игровые объекты простейшими алгоритмами игровой логики, научив их перемещаться в заданном направлении, а также выбирать себе случайное место в игровом пространстве. Ис( пользование более мощных алгоритмов искусственного интеллекта в нашей игре не понадобится, но если вы интересуетесь этим вопросом серьезно, то рекомен( дую вам изучить книгу издательства ДМК Пресс «Программирование искусст( венного интеллекта в приложениях», автор М. Тим Джонс. Итак, что такое движение в пространстве? Каждый спрайт при выводе на эк( ран монитора получает свои координаты по двум осям X и Y, например: Vector2 spritePosition = new Vector2(100, 300);
Также может иметь место и следующая запись: Vector2 spritePosition; spritePosition.X = 100; spritePosition.Y = 300;
В этих строках кода позиция для спрайта задается по оси X в 100 пикселей (от левого верхнего угла экрана) и по оси Y в 300 пикселей вниз все от того же верхне( го левого угла. Чтобы переместить спрайт в заданном направлении, необходимо просто изменить позицию спрайта или стереть спрайт с экрана в старом месте и нарисовать его уже на новом месте. То есть если вы желаете переместить спрайт сверху вниз (только по оси Y), то необходимо на каждой итерации игрового цикла
110
Движение спрайтов в пространстве
или в один проход исходного кода метода Update() класса Game1 изменить или увеличить позицию спрайта на определенное количество пикселей, например:
Движение спрайтов в пространстве
111
ва налево. Посмотрите на рис. 8.1, где схематически представлен механизм дви( жения спрайта по различным осям со скоростью в 3 пикселя.
// Îáíîâëÿåì ñîñòîÿíèå èãðû protected override void Update(GameTime gameTime) { spritePosition.Y += 3; }
В этом примере мы на каждой итерации игрового цикла, то есть за один кадр, увеличиваем позицию спрайта на 3 пикселя. Циклическое увеличение позиции спрайта по оси Y на 3 пикселя в каждой итерации игрового цикла (перерисовка спрайта на новом месте) создаст эффект движения спрайта по экрану сверху вниз. Получается, что значение в 3 пикселя задает фактическую скорость для дви( жения спрайта по экрану. Меньшее от 3 пикселей значение (например, 1 пиксель) уменьшит скорость перемещения спрайта по экрану, а большее значение (напри( мер, 5 пикселей) соответственно увеличит скорость движения спрайта. В целом выбирать скорость для движения объектов в игре необходимо аккуратно и на ос( нове большого количества тестов игры. Дело в том, что очень много хороших и красивых игр из(за неправильно выбранной скорости объектов и главного героя проигрывают менее удачным играм. Если выбрать маленькую скорость для объ( екта, то пользователю может казаться, что игра несколько тормозит и лишена ди( намики. Если, в свою очередь, скорость будет сильно большой, то осуществлять контроль над игрой будет чрезвычайно трудно. Но в любом случае все зависит именно от той задачи, которую вы реализовываете в вашей игре. Задавая скорость только по одной оси координат, вы соответственно будете передвигать спрайт вдоль этой оси, а если задавать скорость по обеим осям коор( динат, то спрайт будет перемещаться под определенным углом. Например, если скорость по осям X и Y равна по 3 пикселя, то этот угол будет равен четко 45 гра( дусам. Если используются неравные значения по осям, то угол движения будет либо меньше, либо больше. Дополнительно можно использовать и отрицательные значения для скорости, в этом случае позиция спрайта будет уменьшаться. На( пример, в приведенном выше примере это будет выглядеть следующим образом: Vector2 spritePosition; spritePosition.X = 100; spritePosition.Y = 300; ... // Îáíîâëÿåì ñîñòîÿíèå èãðû protected override void Update(GameTime gameTime) { spritePosition.Y -= 3; }
В этом исходном коде мы получаем движение спрайта снизу вверх по оси Y. Если такой подход использовать для оси X, то движение будет происходить спра(
Рис. 8.1. Техника движения спрайтов на экране
Надеюсь, теперь смысл перемещения спрайтов в пространстве вам стал поня( тен, поэтому мы можем смело переходить к работе над проектами этой главы. В частности, вашему вниманию будут предложены два проекта: MoveSprite и MoveSpriteArray. В первом проекте MoveSprite нашей целью будет перемещение спрайта по экрану сверху вниз. При этом как только спрайт будет достигать нижней части кромки экрана, то он будет исчезать из нашего поля зрения, поскольку его теку( щая позиция станет уже за пределами нашей зоны видимости. Чтобы корректно обрабатывать данную ситуацию, мы создадим простой механизм, который дол( жен будет следить за спрайтом на экране, и как только объект исчезнет с экрана, ему будет назначаться новая позиция в верхней части дисплея и перемещение во( зобновится снова в том же ключе. Таким образом, спрайт в игре будет двигаться циклично сверху вниз. Во втором проекте MoveSpriteArray задача несколько усложнится, и в игру добавятся еще четыре дополнительных спрайта. В итоге в игре задействуются сразу пять различных объектов. Для всех пяти объектов будут организованы ме( ханизм обработки ситуации выхода за пределы экрана в нижней части дисплея и установка на новые позиции. Теперь перейдем к новому проекту MoveSprite, который вы должны создать на базе последнего проекта предыдущей главы.
112
Проект MoveSprite
Движение спрайтов в пространстве
8.1. Проект MoveSprite Прежде всего в новом сформированном проекте MoveSprite в классе Sprite необходимо добавить новую переменную speedSprite, которая будет задавать скорость движения спрайта в пикселях. public Vector2 speedSprite = new Vector2(0, 6);
В этом коде мы задаем скорость по оси X, равную нулю, а скорость по оси Y – равную 6 пикселям. Это означает, что по оси X положение спрайта будет оставать( ся всегда неизменным, а вот по оси Y на каждой новой итерации или одном прохо( де метода Update() класса Game1 текущее положение спрайта будет увеличи( ваться на 6 пикселей. Теперь перейдем к исходному коду класса Game1 проекта MoveSprite. По( смотрим на весь исходный код этого класса в листинге 8.1, после чего приступим к его детальному анализу. Исходный код проекта MoveSprite находится на ком( пакт(диске в папках Code\Chapter8\MoveSprite. //========================================================================= /// <summary> /// Ëèñòèíã 8.1 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 8 /// Ïðîåêò: MoveSprite /// Êëàññ: Game1 /// Äâèæåíèå ñïðàéòà ïî ýêðàíó /// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace MoveSprite { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; SpriteBatch spriteBatch;
113
Sprite sprite; private Texture2D background1; private Texture2D background2; Random rand = new Random(); /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; sprite = new Sprite(12, 10); } /// <summary> /// Èíèöèàëèçàöèÿ /// <summary> protected override void Initialize() { sprite.spritePosition = new Vector2(300, -200); base.Initialize(); } /// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); sprite.Load(content, “Content\\Textures\\sprite”); background1 = content.Load(“Content\\Textures\\background1”); background2 = content.Load(“Content\\Textures\\background2”); } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } }
114
Проект MoveSprite
Движение спрайтов в пространстве
/// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit();
Системный класс Random позволяет в программах генерировать для заданной переменной какое(то новое случайное значение. В состав класса входит метод Next(), который при каждом новом вызове формирует последовательность слу( чайных чисел. Метод Next() имеет три следующие модификации: Random.Next () – возвращает положительное случайное число; Random.Next(int32) – возвращает положительное случайное число, где единственный параметр метода – это верхняя граница или максималь( ное число, выше которого не может быть сгенерировано новое значение. Например, если вызвать метод вот так: Next(20), – то сгенерированное число не будет выходить из диапазона от 0 до 20; Random.Next(int32, int32) – этот вид метода с двумя параметрами позволяет задавать диапазон от выбранного минимального числа до мак( симального числа. Например, вызов метода со значениями Next(15, 20) задаст диапазон для случайного числа в пределах от 15 до 20.
double elapsed = gameTime.ElapsedGameTime.TotalSeconds; sprite.UpdateFrame(elapsed); MoveSprite(); base.Update(gameTime); } /// <summary> /// Äâèæåíèå ñïðàéòà ïî âåðòèêàëè /// <summary> public void MoveSprite() { sprite.spritePosition += sprite.speedSprite; if (sprite.spritePosition.Y > Window.ClientBounds.Height) { sprite.spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width-300-sprite.spriteTexture.Width/12),-200); } } /// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(background1, new Vector2(0, 0), Color.White); sprite.DrawAnimationSprite(spriteBatch); spriteBatch.Draw(background2, new Vector2(0, 0), Color.White); spriteBatch.End(); base.Draw(gameTime); } } }
В самом начале исходного кода класса Game1 происходит объявление гло( бальных объектов и добавляется новая строка кода. Random rand = new Random();
115
Класс Random и метод Next() нам будут необходимы для задания новой по( зиции на экране монитора в методе MoveSprite() класса Game1. Это новый ме( тод, который добавляется в текущий проект и выглядит следующим образом: public void MoveSprite() { sprite.spritePosition += sprite.speedSprite; if (sprite.spritePosition.Y > Window.ClientBounds.Height) { sprite.spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width-300-sprite.spriteTexture.Width/12),-200); } }
В первой строке кода метода MoveSprite() мы изменяем позицию объекта по оси Y, а значит, перемещаем спрайт на экране сверху вниз. Если развернуть эту строку записи (чтобы вам было понятно), то исходный код движения спрайта бу( дет выглядеть следующим образом: sprite.spritePosition.X += sprite.speedSprite.X; // ãäå ñêîðîñòü ïî X ðàâíà 0 sprite.spritePosition.Y += sprite.speedSprite.Y; // ãäå ñêîðîñòü ïî Y ðàâíà 6
Такая запись также может иметь место в исходном коде метода MoveSprite(), но представленный первый вариант несколько профессиональнее и быстрее ра( ботает. Оставшиеся три строки кода метода MoveSprite() обрабатывают ситуацию выхода спрайта за пределы экрана с нижней стороны дисплея. Как только пози( ция спрайта оказывается больше, чем максимальный размер высоты экрана, то в исходном коде метода MoveSprite() происходит вход в конструкцию if/ else, где определяется новая точка вывода спрайта на экран.
116
Проект MoveSpriteArray
Движение спрайтов в пространстве
117
sprite.spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width-300-sprite.spriteTexture.Width/12),-200);
Здесь мы используем метод Next(), с помощью которого при определении координаты по оси X задаем случайное значение в диапазоне от 10 пикселей до Window.ClientBounds.Width – 300-sprite.spriteTexture.Width/12 или в числовом эквиваленте 1024–300-îäèí ôðåéì àíèìàöèîííîé ïîñëåäîâàòåëüíîñòè пикселей, что позволяет нам при каждом новом определении значения точки вывода спрайта на экран по оси X задавать новое значение в пре( делах видимой области рамки фона. С выбором случайных значений для новой позиции спрайта игрок не будет знать, в каком месте должен выводиться спрайт на экран, и впоследствии не смо( жет приспособиться к вашей игре. Новое место будет всегда неожиданным как для игрока, так, собственно, и для вас, потому что используется метод Next() с двумя параметрами, задающими определенный диапазон значений. Дополни( тельные 300 пикселей, которые мы отняли от ширины дисплея, отводятся на ин( формационную рамку игры, а также мы отнимаем ширину одного фрейма, дабы спрайт не заходил за саму рамку. Что касается задания координаты по оси Y, то она, как вы заметили, отрица( тельна. Это позволяет рисовать спрайт в верхней части дисплея за пределами ви( димости экрана и затем перемещать спрайт вниз (рис. 8.2). Таким образом дости( гается эффект плавного появления, или выезда, спрайта из верхней части экрана и всего игрового пространства. Это красиво, эффектно, и если этого не делать и рисовать спрайт в положительных значениях оси Y, то объекты будут появляться резко и как бы ни с того ни с сего. Так делать нельзя, по крайней мере, в нашей игре точно. В других играх могут быть свои задачи, поэтому этот подход имеет право на жизнь. После создания метода MoveSprite() необходимо поместить вызов этого метода в метод Update(GameTime gameTime) класса Game1. Тогда на каждой итерации игрового цикла будет происходить вызов метода MoveSprite() и со( ответственно обновление состояния спрайта, а также обработка ситуации с выхо( дом спрайта из зоны видимости. Исходный код проекта вы найдете на компакт( диске в папке Code\Chapter8\MoveSprite, а мы переходим к рассмотрению следующего проекта MoveSpriteArray.
8.2. Проект MoveSpriteArray Задача этого проекта заключается в том, чтобы добавить в игру дополнительные объекты. Сделать это можно несколькими способами, но самый «продвинутый» и профессиональный способ сводится к организации простого массива данных. По( этому в проекте MoveSpriteArray мы реорганизуем способ объявления и созда( ния спрайта на создание массива данных, состоящего из пяти спрайтов. Для этого в исходном коде класса Game1 проекта MoveSpriteArray вместо одиночного объявления спрайта появляется объявление массива спрайтов.
Рис. 8.2. Назначение спрайту новой позиции в игровом пространстве
Sprite[] sprite = new Sprite[5];
Здесь мы объявляем массив данных, исчисляемый пятью спрайтами. Все эти пять спрайтов, а точнее каждый из спрайтов будет загружать в игру свое уникаль( ное изображение и представлять тот или иной объект. Каждый спрайт использует анимационную последовательность для создания динамики в игре, посмотрите на рис. 8.3, где представлены пять изображений, загружаемых в игру. Все изображения, загружаемые в игру, размещаются в рабочем каталоге про( екта в папках Content\Textures. Не забывайте, что все пять рисунков необходимо явно добавить в проект посредством команд Add ⇒ Exiting Item, как мы это дела( ли в предыдущих главах. Далее в конструкторе класса Game1 происходит создание пяти объектов. Для этих целей используется обычный цикл for. for (int i = 0; sprite.Length > i; i++) { sprite[i] = new Sprite(12, 10); }
118
Проект MoveSpriteArray
Движение спрайтов в пространстве sprite[0].Load(content, sprite[1].Load(content, sprite[2].Load(content, sprite[3].Load(content, sprite[4].Load(content,
119
“Content\\Textures\\0”); “Content\\Textures\\1”); “Content\\Textures\\2”); “Content\\Textures\\3”); “Content\\Textures\\4”);
Названия для изображений я специально задал в виде цифровых значений, чтобы был понятен механизм загрузки графики для каждого элемента массива данных. Кстати, поскольку имена изображений совпадают с данными массива, то можно реорганизовать вышеприведенный код в цикл, но эти действия выполняй( те самостоятельно – домашнее задание ;) Теперь перейдем к первичной установке всех спрайтов на свои игровые пози( ции, которые реализованы в методе Initialize(). protected override void Initialize() { j = 0; for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width - 500), j = j - 300); } base.Initialize(); }
Рис. 8.3. Изображения, загружаемые в игру
Единственное условие в этой конструкции кода заключается в том, чтобы все изображения имели одинаковую анимационную последовательность, то есть ко( личество фреймов всех изображений должно быть одинаково (как у нас в игре). Если одно и более изображений имеют различное количество фреймов, то придет( ся загружать каждый спрайт по отдельности, сделать это можно, например, сле( дующим образом: sprite[0] sprite[1] sprite[2] sprite[3] sprite[4]
= = = = =
new new new new new
Sprite(5, 3); Sprite(8, 2); Sprite(2, 2); Sprite(3, 1); Sprite(15, 2);
После создания массива данных переходим к методу LoadGraphicsContent() и к загрузке в массив данных графических изображений.
Для инициализации используется цикл. В качестве координаты по оси X для каждого спрайта применяется механизм случайного выбора позиции по ширине игровой области экрана. Этот механизм мы подробно рассмотрели в предыдущем проекте. А вот установка точки отсчета по оси Y проходит в несколько другом ключе с использованием переменной j = j - 300. Здесь мы используем промежуточную переменную j, которая инициализиру( ется нулевым значением. На каждой итерации цикла значение этой переменной уменьшается на 300 пикселей, что позволяет установить все спрайты как бы друг за другом на расстоянии 300 пикселей, и они не накладываются один на другой (рис. 8.4). Этот простой алгоритм позволяет нам избежать наложений спрайтов друг на друга. Далее в методе MoveSprite() переделываем код для движения массива объектов. public void MoveSprite() { for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition += sprite[i].speedSprite; if (sprite[i].spritePosition.Y > Window.ClientBounds.Height) {
120
Проект MoveSpriteArray
Движение спрайтов в пространстве
121
protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit(); double elapsed = gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; sprite.Length > i; i++) { sprite[i].UpdateFrame(elapsed); } MoveSprite(); base.Update(gameTime); }
Для обновления анимационной последовательности методом UpdateFrame() используется цикл. Точно такой же цикл мы применяем и в методе Draw() для рисования спрайтов на экране. Полный исходный код класса Game1 приведен в листинге 8.2, а полный код проекта находится на компакт(диске в папках Code\Chapter8\MoveSpriteArray.
Рис. 8.4. Установка спрайтов на игровые позиции в момент первого запуска игры
sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[i].spriteTexture.Width / 12), -500); } } }
Обработка ситуации с выходом спрайта из зоны видимости реализована так же, как и в предыдущем проекте, но по оси Y задается значение в –500 пикселей. Делается это для того, чтобы уже исчезнувшие с экрана спрайты ставились на но( вые позиции повыше (подальше) и дали возможность пройти всем предыдущим спрайтам, еще не достигнувшим конца экрана. Затем в методе Update() класса Game1 мы вызываем метод UpdateFrame() класса Sprite для обновления анимационной последовательности спрайтов и метод MoveSprite() для движения спрайтов по экрану.
//========================================================================= /// <summary> /// Ëèñòèíã 8.2 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 8 /// Ïðîåêò: MoveSpriteArray /// Êëàññ: Game1 /// Ìàññèâ ñïðàéòîâ /// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace MoveSpriteArray { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics;
122
Проект MoveSpriteArray
Движение спрайтов в пространстве
ContentManager content; KeyboardState keyboardState; SpriteBatch spriteBatch; Sprite[] sprite = new Sprite[5]; private Texture2D background1; private Texture2D background2; Random rand = new Random(); int j; /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; for (int i = 0; sprite.Length > i; i++) { sprite[i] = new Sprite(12, 10); } } /// <summary> /// Èíèöèàëèçàöèÿ /// <summary> protected override void Initialize() { j = 0; for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width - 500), j = j - 300); } base.Initialize(); } /// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); background1 = content.Load(“Content\\Textures\\background1”); background2 = content.Load(“Content\\Textures\\background2”); sprite[0].Load(content, “Content\\Textures\\0”); sprite[1].Load(content, “Content\\Textures\\1”); sprite[2].Load(content, “Content\\Textures\\2”); sprite[3].Load(content, “Content\\Textures\\3”);
sprite[4].Load(content, “Content\\Textures\\4”); } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } } /// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit(); double elapsed = gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; sprite.Length > i; i++) { sprite[i].UpdateFrame(elapsed); } MoveSprite(); base.Update(gameTime); } /// <summary> /// Äâèæåíèå ñïðàéòà ïî âåðòèêàëè /// <summary> public void MoveSprite() { for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition += sprite[i].speedSprite; if (sprite[i].spritePosition.Y > Window.ClientBounds.Height) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[i].spriteTexture.Width / 12), -500); } } } /// <summary>
123
124
Движение спрайтов в пространстве
/// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(background1, new Vector2(0, 0), Color.White); for (int i = 0; sprite.Length > i; i++) { sprite[i].DrawAnimationSprite(spriteBatch); } spriteBatch.Draw(background2, new Vector2(0, 0), Color.White); spriteBatch.End(); base.Draw(gameTime); } } }
Глава 9 Устройства ввода В этой главе рассматриваются два проекта: Platform и PauseGame. В первом проекте мы разберем обработку событий, получаемых с устройства ввода или кла( виатуры. В игру у нас добавится новый элемент – платформа, которая будет ло( вить объекты, падающие с неба. Для того чтобы двигать платформу в определен( ном направлении, мы будем использовать некоторые клавиши клавиатуры. Второй проект этой главы – PauseGame – реализует механизм паузы в игре. Требования к этому механизму чрезвычайно просты: необходимо по нажатии од( ной из клавиш остановить работу игры, а потом по нажатии все той же клавиши запустить игру вновь. При этом игра должна продолжать работать именно с того места, на котором была остановлена. Теперь перейдем к исходным кодам и начнем работать над первым проектом Platform.
9.1. Проект Platform По задумке в нашей игре мы обязаны ловить всех людей, падающих с неба в про( пасть, с помощью платформы. Дополнительно с неба, кроме людей, также падают различные запчасти сгоревшего самолета, и от этих частей нам необходимо увора( чиваться. Платформа находится в нижней части экрана и ездит горизонтально по тросу слева направо на одном уровне. Чтобы управлять такой платформой, можно использовать компьютерную клавиатуру, джойстик или мышь. В игре мы будем использовать клавиатуру (работать с мышью вы научитесь в главе 12). В качестве управляющих клавиш на клавиатуре используются клави( ши с командами Up (Вверх), Down (Вниз), Left (Влево) и Right (Вправо) с нане( сенными на них стрелками по направлению движения. Нам понадобятся только команды Влево и Вправо, поскольку платформа будет всегда находиться в ниж( ней части экрана и двигаться только горизонтально на одном уровне. Изображение платформы состоит из одного фрейма, а это значит, что приме( няется неанимированный спрайт. Платформу можно нарисовать как угодно, но она была нарисована в виде тележки с матрацем, как показано на рис. 9.1. Сам рисунок располагается в рабочем каталоге проекта в папках Content\Textures, механизм добавления графического изображе( ния в проект остается прежним.
Рис. 9.1. Платформа
126
Проект Platform
Устройства ввода
Начнем работу над проектом Platform и добавим в исходный код класса Game1 в область глобальных переменных объявления нового объекта platform класса Sprite.
platform.spritePosition.X -= 10; else if (keyboardState.IsKeyDown(Keys.Right)) platform.spritePosition.X += 10; // Îáðàáîòêà ñòîëêíîâåíèé ñ ëåâîé è ïðàâîé ÷àñòüþ ýêðàíà if(platform.spritePosition.X < 10) platform.spritePosition.X = 10; else if(platform.spritePosition.X > Window.ClientBounds.Width - 400) platform.spritePosition.X = Window.ClientBounds.Width - 400;
Sprite platform;
Объект platform не будет анимированным, поэтому при создании объекта используется неанимированный конструктор класса Sprite. platform = new Sprite();
Затем в методе LoadGraphicsContent() класса Sprite происходит за( грузка изображения платформы в игру. platform.Load(content, “Content\\Textures\\platform”);
Далее платформе в методе Initialize() класса Sprite задается позиция на экране монитора. platform.spritePosition = new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height - 90);
По оси X выбирается место примерно посередине экрана, в целом это положе( ние по оси может быть любым. Другое дело – положение платформы по оси Y. Нам нужно, чтобы платформа располагалась четко на тросе, который нарисован на фоновом изображении, поэтому от нижней части дисплея отнимается нужное количество пикселей и платформа встает точно на трос. Эта операция несложна – просто когда рисовался задний фон, а вместе с ним и трос, то было сразу просчита( но количество необходимых пикселей. В любом случае, если вы ошибетесь и плат( форма не станет четко на трос, ее всегда можно подвинуть, изменив отнимаемое значение от высоты экрана. При этом если бы я делал коммерческую игру из этого проекта, то обязательно добавил бы дополнительных красивостей. Например, платформу можно аними( ровать или приделать ей колесики, которые будут крутиться при движении. Сам трос можно сделать отдельным спрайтом и тоже анимировать, или отказаться от платформы и троса и создать мост, рельсы, помпу, лодку на воде, тележку, ковер( самолет, летающий в воздухе, или что(то еще, фантазировать на самом деле в этом плане можно до бесконечности. Для реализации движения платформы на экране в классе Game1 создается метод MovePlatform(), код которого выглядит следующим образом. public void MovePlatform() { // Äâèæåíèå ïëàòôîðìû âëåâî è âïðàâî if(keyboardState.IsKeyDown(Keys.Left))
127
}
Метод MovePlatform() состоит из двух независимых блоков исходного кода. В первом блоке происходит обработка нажатий клавиш с командами Влево и Вправо. Для обработки событий, получаемых с клавиатуры, используются кон( струкция кода if/else и метод IsKeyDown(). В переводе на русский язык эта конструкция обозначает следующее: Åñëè íàæàòà êëàâèøà Âëåâî, òî: Ïîçèöèÿ ïëàòôîðìû ïî îñè Õ óìåíüøàåòñÿ íà 10 ïèêñåëåé À åñëè íàæàòà êëàâèøà Âïðàâî, òî: Ïîçèöèÿ ïëàòôîðìû ïî îñè Õ óâåëè÷èâàåòñÿ íà 10 ïèêñåëåé
Уменьшение или увеличение позиции по оси X приводит к движению плат( формы по горизонтали влево и вправо. Заданная скорость в размере 10 пикселей за один кадр тестировалась много раз в игре и оказалась наиболее удачным значе( нием. Второй блок исходного кода обрабатывает ситуацию в столкновении плат( формы с левой и правой частями экрана. Если такое столкновение имеет место, то по оси X спрайту присваиваются значения, равные соответственно левой и правой кромкам экрана (в правой части мы не забываем о размере рамки). То есть спрайт при столкновении с двумя крайними точками экрана остается на месте без движе( ния. Делается это для того, чтобы платформа не уезжала из нашей области види( мости и все время оставалась на экране. Метод MovePlatform() вызывается в методе Update() класса Game1, где происходит постоянное обновление состояния игры. В конце исходного кода класса Game1 в методе Draw() платформа рисуется на экране. Поскольку пози( ция платформы platform.spritePosition постоянно меняется, то в методе Draw() платформа перерисовывается постоянно в новом месте. Полный исход( ный код класса Game1 представлен в листинге 9.1. На компакт(диске рассмотрен( ный пример находится в папке Code\Chapter9\ Platform. //========================================================================= /// <summary> /// Ëèñòèíã 9.1 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 9
128
Проект Platform
Устройства ввода
/// Ïðîåêò: Platform /// Êëàññ: Game1 /// Äîáàâëÿåì ïëàòôîðìó /// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace Plarform { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; SpriteBatch spriteBatch; Sprite[] sprite = new Sprite[5]; private Texture2D background1; private Texture2D background2; Random rand = new Random(); int j; Sprite platform; /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; for (int i = 0; sprite.Length > i; i++) { sprite[i] = new Sprite(12, 10); } platform = new Sprite(); } /// <summary> /// Èíèöèàëèçàöèÿ /// <summary> protected override void Initialize() { for (int i = 0; sprite.Length > i; i++)
129
{ sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width - 500), j = j - 300); } platform.spritePosition = new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height - 90); base.Initialize(); } /// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); background1 = content.Load(“Content\\Textures\\background1”); background2 = content.Load(“Content\\Textures\\background2”); platform.Load(content, “Content\\Textures\\platform”); sprite[0].Load(content, “Content\\Textures\\0”); sprite[1].Load(content, “Content\\Textures\\1”); sprite[2].Load(content, “Content\\Textures\\2”); sprite[3].Load(content, “Content\\Textures\\3”); sprite[4].Load(content, “Content\\Textures\\4”); } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } } /// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit(); double elapsed = gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; sprite.Length > i; i++) { sprite[i].UpdateFrame(elapsed); } MoveSprite(); MovePlatform();
130
Проект PauseGame
Устройства ввода
131
} }
base.Update(gameTime); } } /// <summary> /// Äâèæåíèå ñïðàéòà ïî âåðòèêàëè /// <summary> public void MoveSprite() { for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition += sprite[i].speedSprite; if (sprite[i].spritePosition.Y > Window.ClientBounds.Height) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[i].spriteTexture.Width / 12), -500); } } } /// <summary> /// Äâèæåíèå ïëàòôîðìû ïî ýêðàíó /// <summary> public void MovePlatform() { if (keyboardState.IsKeyDown(Keys.Left)) platform.spritePosition.X -= 10; else if (keyboardState.IsKeyDown(Keys.Right)) platform.spritePosition.X += 10; if (platform.spritePosition.X < 10) platform.spritePosition.X = 10; else if (platform.spritePosition.X>Window.ClientBounds.Width - 400) platform.spritePosition.X = Window.ClientBounds.Width - 400; } /// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(background1, new Vector2(0, 0), Color.White); for (int i = 0; sprite.Length > i; i++) { sprite[i].DrawAnimationSprite(spriteBatch); } spriteBatch.Draw(background2, new Vector2(0, 0), Color.White); platform.DrawSprite(spriteBatch); // âåðõíèé ñëîé íàä òðîñîì spriteBatch.End(); base.Draw(gameTime);
После того как вы откомпилируете и запустите проект Platform, вы увидите в нижней части платформу, которую можно передвигать клавишами Влево и Вправо. С неба на вас будут падать различные предметы и люди, которые вы долж( ны ловить платформой, но на этом этапе, естественно, поймать вам ничего и нико( го не удастся, поскольку в игре еще не описан механизм игровых столкновений. Этим мы займемся в следующей главе, а здесь добавим еще несколько строк кода, для того чтобы сделать в игре режим паузы.
9.2. Проект PauseGame Вводим в проект PauseGame две новые булевы переменные, объявление которых происходит в области глобальных переменных, чтобы переменные были доступ( ны на любом участке исходного кода класса Game1. private bool paused = false; private bool pauseKeyDown = false;
Эти две переменные будут отражать соответственно состояние паузы в игро( вом процессе и состояние нажатия заданной клавиши. На каждой новой итерации игрового цикла (метод Update()) мы будем опрашивать клавиатуру и узнавать, нажата определенная клавиша или нет. Для паузы избрана стандартная в этом случае клавиша с изображением английской буквы «P». Если клавиша «P» нажата, то переменной paused присваивается значение true. В этом случае создается код на проверку условия, чему именно равна пере( менная paused, и если ее значение равно true, то, значит, выполнение игрового цикла пропускается, если false, то игра продолжает свою работу (рис. 9.2). По( смотрите на исходный код класса Game1 проекта PauseGame в листинге 9.2. //========================================================================= /// <summary> /// Ëèñòèíã 9.2 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 9 /// Ïðîåêò: PauseGame /// Êëàññ: Game1 /// Ðåàëèçóåì â èãðå ïàóçó /// <summary> //========================================================================= #region Using Statements using System;
132
Проект PauseGame
Устройства ввода
133
graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; for (int i = 0; sprite.Length > i; i++) { sprite[i] = new Sprite(12, 10); } platform = new Sprite(); }
Рис. 9.2. Схема работы паузы в игре using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace PauseGame { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; SpriteBatch spriteBatch; Sprite[] sprite = new Sprite[5]; private Texture2D background1; private Texture2D background2; Random rand = new Random(); int j = 0; Sprite platform; private bool paused = false; private bool pauseKeyDown = false; /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() {
/// <summary> /// Èíèöèàëèçàöèÿ /// <summary> protected override void Initialize() { for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width - 500), j = j - 300); } platform.spritePosition = new Vector2(Window.ClientBounds.Width / 2, Window.ClientBounds.Height - 90); base.Initialize(); } /// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); background1 = content.Load(“Content\\Textures\\background1”); background2 = content.Load(“Content\\Textures\\background2”); platform.Load(content, “Content\\Textures\\platform”); sprite[0].Load(content, “Content\\Textures\\0”); sprite[1].Load(content, “Content\\Textures\\1”); sprite[2].Load(content, “Content\\Textures\\2”); sprite[3].Load(content, “Content\\Textures\\3”); sprite[4].Load(content, “Content\\Textures\\4”); } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } }
134
Проект PauseGame
Устройства ввода
/// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) {
else if (platform.spritePosition.X >Window.ClientBounds.Width -400) platform.spritePosition.X = Window.ClientBounds.Width - 400; } /// <summary> /// Ïàóçà â èãðå /// <summary> public void Pause() { if (keyboardState.IsKeyDown(Keys.P)) { pauseKeyDown = true; } else if (pauseKeyDown) { pauseKeyDown = false; paused = !paused; } }
keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit(); Pause(); if (paused == false) { double elapsed = gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; sprite.Length > i; i++) { sprite[i].UpdateFrame(elapsed); }
/// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
MoveSprite(); MovePlatform(); } base.Update(gameTime); }
spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(background1, new Vector2(0, 0), Color.White); for (int i = 0; sprite.Length > i; i++) { sprite[i].DrawAnimationSprite(spriteBatch); } spriteBatch.Draw(background2, new Vector2(0, 0), Color.White); platform.DrawSprite(spriteBatch); spriteBatch.End();
/// <summary> /// Äâèæåíèå ñïðàéòà ïî âåðòèêàëè /// <summary> public void MoveSprite() { for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition += sprite[i].speedSprite; if (sprite[i].spritePosition.Y > Window.ClientBounds.Height) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[i].spriteTexture.Width / 12), -500); } } } /// <summary> /// Äâèæåíèå ïëàòôîðìû ïî ýêðàíó /// <summary> public void MovePlatform() { if (keyboardState.IsKeyDown(Keys.Left)) platform.spritePosition.X -= 10; else if (keyboardState.IsKeyDown(Keys.Right)) platform.spritePosition.X += 10; if (platform.spritePosition.X < 10) platform.spritePosition.X = 10;
135
base.Draw(gameTime); } } }
Из блока кода, направленного на обработку паузы в состоянии игры, видно, что мы сначала определяем состояние переменной paused, и если ее значение равно false (нет паузы), то выполняется содержимое блока if. Если состояние булевой переменной paused изменилось на true (пауза в игре), то выполнение блока if пропускается, что равносильно необновлению состояния игры, то есть игра замирает. Чтобы выйти из этого состояния, достаточно второй раз нажать клавишу «Р», и в соответствии с исходным кодом метода Pause() значение буле( вой переменной paused изменяется на противоположное. Заметьте, что вызов метода Pause() в методе Update() совершается до обработки событий, полу( ченных с клавиши «Esc» – выход из программы. Делается это намеренно, чтобы пользователь в режиме паузы мог свободно выйти из игры и закрыть программу.
Структура BoundingBox
Глава 10 Игровые столкновения В играх объекты могут пересекаться друг с другом или сталкиваться между со( бой. Такой вид пересечения объектов называется игровым столкновением. Для определения столкновения объектов пишут исходный код, который создает своего рода детектор столкновений объектов. Если столкновение имеет место, тогда необходимо производить определенные действия. Обработка ситуации при столкновении двух и более объектов между собой в играх может быть раз( личной. В одних играх при столкновении вам придется уничтожать объекты, в других играх необходимо будет оттолкнуться от этого объекта и т. д. Все зави( сит именно от логики игры. В нашей игре все объекты падают с неба (сверху вниз). В нижней части дис( плея курсирует платформа, которая обязана ловить людей и уворачиваться от других падающих объектов. В связи с этим нам нужно реализовать механизм, ко( торый должен определять, коснулся ли один из объектов платформы или нет. После касания объекта и платформы мы будем устанавливать объект на новую позицию, за верхней кромкой экрана, и перемещать его опять вниз. На первый взгляд, проблема сложна, но на самом деле все решается достаточно легко и про( сто, в чем вы сейчас и убедитесь.
чивающие прямоугольники, надетые на спрайты, или нет. Если пересеклись, то объекты столкнулись, а значит, необходимо выполнять определенные события, если нет, то столкновение не имело места и ничего делать не нужно. Просто, не правда ли? Создать переменную структуры BoundingBox очень просто, как и просто оп( ределить размер ограничивающего прямоугольника, смотрим на приведенный пример блока кода. // Ñîçäàåì ñòðóêòóðíóþ ïåðåìåííóþ public BoundingBox bb; // Çàäàåì ðàçìåð ïðÿìîóãîëüíèêà bb.Min = new Vector3(10, 20, 0); bb.Max = new Vector3(60, 80, 0);
Структурные переменные Min и Max определяют две точки в пространстве, на основе которых происходит построение ограничивающего прямоугольника. Точ( ка Min задает левый верхний угол ограничивающего прямоугольника, что впо( следствии будет соответствовать левому верхнему углу изображения. В свою очередь, точка Max определяет правый нижний угол прямоугольника, что уже соот( ветствует правому нижнему углу изображения. На основе этих данных сервисами XNA в пространстве строится прямоугольник. Если этот прямоугольник подо( гнать к размеру спрайта, то мы получаем тот самый ограничивающий прямоу( гольник, представленный структурой BoundingBox (рис. 10.1).
10.1. Структура BoundingBox В XNA Framework детектором столкновений между объектами служит структура BoundingBox. Дословный перевод этой структуры звучит как ограничивающий прямоугольник. Идея определения столкновений структурой BoundingBox со( стоит в следующем. В игре специально создается структурная переменная структуры BoundingBox. Структура BoundingBox в своей сущности описывает невидимый прямоуголь( ник, а значит, структурная переменная и есть тот самый невидимый прямоуголь( ник. Размер прямоугольника вы определяете сами с помощью элементов структу( ры. Далее вы надеваете это прямоугольник на определенный спрайт, максимально подгоняя размер прямоугольника под размер спрайта. То есть фактически раз( мер ограничивающего прямоугольника, представленный переменной структуры BoundingBox, должен соответствовать размеру одного фрейма изображения спрайта. Такую операцию необходимо проделать с каждым спрайтом, участвующим в обработке событий по столкновению объектов. Затем встроенными механизма( ми структуры BoundingBox вы определяете, пересекаются между собой ограни(
137
Рис. 10.1. Создание ограничивающего прямоугольника
138
Проект Collision
Игровые столкновения
Чем плотнее вы наденете ограничивающий прямоугольник на спрайт, тем точнее будет обрабатываться столкновение между объектами. Дополнительно можно искусственно уменьшать или увеличивать ограничивающий прямоуголь( ник, чтобы соответственно уменьшить или увеличить зону столкновения. Здесь все зависит от игровых задач. Например, прямоугольник для платформы мы спе( циально уменьшим, чтобы столкновения были реалистичными. Единственное, о чем еще следует сказать, – это то, что элементы Min и Max структуры BoundingBox задаются трехмерным вектором, состоящим из трех ко( ординат осей X, Y и Z. Vector3(10, 20, 0);
В двухмерном пространстве ось Z присутствует, но она работает несколько иначе, чем в трехмерных играх. Поэтому для простых двухмерных спрайтов тре( тий параметр вектора ставится всегда в ноль и не используется. В трехмерных иг( рах ось Z используется, а в векторе обязательно задается значение по оси Z. Тогда мы получим уже не ограничивающий прямоугольник, а ограничивающий куб (все модели объемные), но об этом вы узнаете в свое время и в своей главе.
10.2. Проект Collision Итак, приступим к работе над проектом Collision, в котором мы добавляем об( работку столкновений между платформой и падающими с неба объектами. В лис) тинге 10.1 представлен полный программный код класса Game1. Сначала давайте посмотрим на весь исходный код этого класса, а затем перейдем к его подробному анализу. Остальные классы проекта Collision остаются неизменными, как и в предыдущих проектах игры. //========================================================================= /// <summary> /// Ëèñòèíã 10.1 /// Èñõîäíûé êîä ê êíèãå: /// “Ïðîãðàììèðîâàíèå êîìïüþòåðíûõ èãð ïîä Windows â XNA Game Studio Express” /// Àâòîð êíèãè: Ãîðíàêîâ Ñ. Ã. /// Ãëàâà 10 /// Ïðîåêò: Collision /// Êëàññ: Game1 /// Èãðîâûå ñòîëêíîâåíèÿ /// <summary> //========================================================================= #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Storage; #endregion namespace Collision { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; ContentManager content; KeyboardState keyboardState; SpriteBatch spriteBatch; Sprite[] sprite = new Sprite[5]; private Texture2D background1; private Texture2D background2; Random rand = new Random(); int j = 0; Sprite platform; private bool paused = false; private bool pauseKeyDown = false; public BoundingBox bbplatform; public BoundingBox[] bb = new BoundingBox[5]; /// <summary> /// Êîíñòðóêòîð /// <summary> public Game1() { graphics = new GraphicsDeviceManager(this); content = new ContentManager(Services); graphics.PreferredBackBufferWidth = 1024; graphics.PreferredBackBufferHeight = 768; graphics.PreferMultiSampling = false; graphics.IsFullScreen = true; for (int i = 0; sprite.Length > i; i++) { sprite[i] = new Sprite(12, 10); } platform = new Sprite(); } /// <summary> /// Èíèöèàëèçàöèÿ /// <summary> protected override void Initialize() { for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width - 500), j = j - 300); } platform.spritePosition = new Vector2(Window.ClientBounds.Width / 2,
139
140
Проект Collision
Игровые столкновения MovePlatform(); Collisions();
Window.ClientBounds.Height - 90); base.Initialize(); } /// <summary> /// Çàãðóçêà êîìïîíåíòîâ èãðû /// <summary> protected override void LoadGraphicsContent(bool loadAllContent) { if (loadAllContent) { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); background1 = content.Load(“Content\\Textures\\background1”); background2 = content.Load(“Content\\Textures\\background2”); platform.Load(content, “Content\\Textures\\platform”); sprite[0].Load(content, “Content\\Textures\\0”); sprite[1].Load(content, “Content\\Textures\\1”); sprite[2].Load(content, “Content\\Textures\\2”); sprite[3].Load(content, “Content\\Textures\\3”); sprite[4].Load(content, “Content\\Textures\\4”); } } /// <summary> /// Îñâîáîæäàåì ðåñóðñû /// <summary> protected override void UnloadGraphicsContent(bool unloadAllContent) { if (unloadAllContent == true) { content.Unload(); } } /// <summary> /// Îáíîâëÿåì ñîñòîÿíèå èãðû /// <summary> protected override void Update(GameTime gameTime) { keyboardState = Keyboard.GetState(); if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit(); Pause(); if (paused == false) { double elapsed = gameTime.ElapsedGameTime.TotalSeconds; for (int i = 0; sprite.Length > i; i++) { sprite[i].UpdateFrame(elapsed); } MoveSprite();
141
} base.Update(gameTime); } /// <summary> /// Äâèæåíèå ñïðàéòà ïî âåðòèêàëè /// <summary> public void MoveSprite() { for (int i = 0; sprite.Length > i; i++) { sprite[i].spritePosition += sprite[i].speedSprite; if (sprite[i].spritePosition.Y > Window.ClientBounds.Height) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[i].spriteTexture.Width / 12), -500); } } } /// <summary> /// Äâèæåíèå ïëàòôîðìû ïî ýêðàíó /// <summary> public void MovePlatform() { if (keyboardState.IsKeyDown(Keys.Left)) platform.spritePosition.X -= 10; else if (keyboardState.IsKeyDown(Keys.Right)) platform.spritePosition.X += 10; if (platform.spritePosition.X < 10) platform.spritePosition.X = 10; else if (platform.spritePosition.X>Window.ClientBounds.Width - 400) platform.spritePosition.X =Window.ClientBounds.Width - 400; } /// <summary> /// Ïàóçà â èãðå /// <summary> public void Pause() { if (keyboardState.IsKeyDown(Keys.P)) { pauseKeyDown = true; } else if (pauseKeyDown) { pauseKeyDown = false; paused = !paused; }
142
Проект Collision
Игровые столкновения
Window.ClientBounds.Width 300 - sprite[4].spriteTexture.Width / 12), -500);
} /// <summary> /// Ñòîëêíîâåíèÿ /// <summary> public void Collisions() { bbplatform.Min = new Vector3(platform.spritePosition.X, platform.spritePosition.Y + 25, 0); bbplatform.Max = new Vector3(platform.spritePosition.X + platform.spriteTexture.Width, platform.spritePosition.Y + 25 + platform.spriteTexture.Height, 0);
} } /// <summary> /// Ðèñóåì íà ýêðàíå /// <summary> protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(background1, new Vector2(0, 0), Color.White); for (int i = 0; sprite.Length > i; i++) { sprite[i].DrawAnimationSprite(spriteBatch); } spriteBatch.Draw(background2, new Vector2(0, 0), Color.White); platform.DrawSprite(spriteBatch); spriteBatch.End();
for (int i = 0; bb.Length > i; i++) { bb[i].Min = new Vector3(sprite[i].spritePosition.X, sprite[i].spritePosition.Y, 0); bb[i].Max = new Vector3(sprite[i].spritePosition.X + sprite[i].spriteTexture.Width / 12, sprite[i].spritePosition.Y+ sprite[i].spriteTexture.Height, 0); }
base.Draw(gameTime);
if (bbplatform.Intersects(bb[0])) { sprite[0].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[0].spriteTexture.Width / 12), -500); }
}
if (bbplatform.Intersects(bb[1])) { sprite[1].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[1].spriteTexture.Width / 12), -500); }
public BoundingBox bbplatform; public BoundingBox[] bb = new BoundingBox[5];
if (bbplatform.Intersects(bb[2])) { sprite[2].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[2].spriteTexture.Width / 12), -500); } if (bbplatform.Intersects(bb[3])) { sprite[3].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[3].spriteTexture.Width / 12), -500); } if (bbplatform.Intersects(bb[4])) { sprite[4].spritePosition = new Vector2(rand.Next(10,
143
} }
Первое, что необходимо сделать в исходном коде класса Game1 – это создать переменные структуры BoundingBox.
В первой строке этого кода создается переменная bbplatform структуры BoundingBox, отвечающая за создание ограничивающего прямоугольника вокруг платформы. Во второй строке блока исходного кода происходит создание массива переменных структуры BoundingBox, состоящего из пяти элементов. Как вы уже догадались, эти пять ограничивающих прямоугольников, или пять структурных переменных bb[0]-bb[4], назначены для имеющихся в игре пяти спрайтов. После создания переменных структуры BoundingBox необходимо опреде( лить размеры ограничивающих прямоугольников для каждого спрайта игры. То есть нужно наложить один конкретный ограничивающий прямоугольник на один конкретный спрайт, четко подогнав его под прямоугольный размер самого спрай( та. Более того, необходимо также неотступно следовать за спрайтом, а значит, пе( ремещать ограничивающий прямоугольник в соответствии с изменившимися ко( ординатами спрайта, поскольку у нас все спрайты динамичны и двигаются по экрану.
144
Проект Collision
Игровые столкновения
Для этих целей создается отдельный метод Collisions(), который мы бу( дем вызывать непосредственно в игровом цикле. Метод большой и несколько за( путанный, поэтому я сейчас его прокомментирую и дополнительно разверну не( которые строки исходного кода с использованием простого языка. Смотрим, что получилось.
145
300 - sprite[0].spriteTexture.Width / 12), -500); } if (bbplatform.Intersects(bb[1])) { sprite[1].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[1].spriteTexture.Width / 12), -500); }
public void Collisions() { /* Ïåðâàÿ ÷àñòü */
if (bbplatform.Intersects(bb[2])) { sprite[2].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[2].spriteTexture.Width / 12), -500); }
// Ñîçäàåì îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê äëÿ ïëàòôîðìû bbplatform.Min = new Vector3(platform.spritePosition.X, platform.spritePosition.Y + 25, 0); bbplatform.Max = new Vector3(platform.spritePosition.X + platform.spriteTexture.Width, platform.spritePosition.Y + 25 + platform.spriteTexture.Height, 0);
if (bbplatform.Intersects(bb[3])) { sprite[3].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[3].spriteTexture.Width / 12), -500); }
// Ñîçäàåì îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê äëÿ sprite[0] bb[0].Min = new Vector3(sprite[0].spritePosition.X, sprite[0].spritePosition.Y, 0); bb[0].Max = new Vector3(sprite[0].spritePosition.X + øèðèíà îäíîãî ôðåéìà, sprite[0].spritePosition.Y + âûñîòà îäíîãî ôðåéìà, 0);
if (bbplatform.Intersects(bb[4])) { sprite[4].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[4].spriteTexture.Width / 12), -500); }
// Ñîçäàåì îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê äëÿ sprite[1] bb[1].Min = new Vector3(sprite[1].spritePosition.X, sprite[1].spritePosition.Y, 0); bb[1].Max = new Vector3(sprite[1].spritePosition.X + øèðèíà îäíîãî ôðåéìà, sprite[1].spritePosition.Y + âûñîòà îäíîãî ôðåéìà, 0); } // Ñîçäàåì îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê äëÿ sprite[2] bb[2].Min = new Vector3(sprite[2].spritePosition.X, sprite[2].spritePosition.Y, 0); bb[2].Max = new Vector3(sprite[2].spritePosition.X + øèðèíà îäíîãî ôðåéìà, sprite[2].spritePosition.Y + âûñîòà îäíîãî ôðåéìà, 0); // Ñîçäàåì îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê äëÿ sprite[3] bb[3].Min = new Vector3(sprite[3].spritePosition.X, sprite[3].spritePosition.Y, 0); bb[3].Max = new Vector3(sprite[3].spritePosition.X + øèðèíà îäíîãî ôðåéìà, sprite[3].spritePosition.Y + âûñîòà îäíîãî ôðåéìà, 0); // Ñîçäàåì îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê äëÿ sprite[4] bb[4].Min = new Vector3(sprite[4].spritePosition.X, sprite[4].spritePosition.Y, 0); bb[4].Max = new Vector3(sprite[4].spritePosition.X + øèðèíà îäíîãî ôðåéìà, sprite[4].spritePosition.Y + âûñîòà îäíîãî ôðåéìà, 0); /* Âòîðàÿ ÷àñòü */ // Ïðîâåðÿåì ïåðåñå÷åíèå âñåõ íàçíà÷åííûõ ïðÿìîóãîëüíèêîâ if (bbplatform.Intersects(bb[0])) { sprite[0].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width -
Исходный код метода Collisions() делится на два блока, или две разные по своим функциям части. В первой части мы для каждого объекта (платформа и пять спрайтов) создаем ограничивающие прямоугольники. Каждый прямоуголь( ник соответствует размеру конкретно взятого спрайта, возьмем, например, плат( форму и переменную Min. bbplatform.Min = new Vector3(platform.spritePosition.X, platform.spritePosition.Y + 25, 0);
В этих строках мы задаем начальную точку отсчета для платформы, что соот( ветствует у нас левому верхнему углу изображения платформы, а затем помещаем это значение в переменную Min. Дополнительно прибавляется еще 25 пикселей к значению по оси Y, чтобы уменьшить ограничивающий прямоугольник, а точнее опустить границу столкновения с платформой. Сделано это потому, что платфор( ма в своей центральной части имеет углубление, и чтобы столкновение было реа( листичным, мы уменьшаем прямоугольник (рис. 10.2). Затем необходимо дорисовать оставшуюся часть ограничивающего прямо( угольника, для этого достаточно задать координату для нижнего правого угла прямоугольника и сохранить ее значение в переменой Max.
146
Проект Collision
Игровые столкновения
147
if (bbplatform.Intersects(bb[0])) { sprite[0].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[0].spriteTexture.Width / 12), -500); }
Рис. 10.2. Ограничивающий прямоугольник для платформы bbplatform.Max = new Vector3(platform.spritePosition.X + platform.spriteTexture.Width, platform.spritePosition.Y + 25 + platform.spriteTexture.Height, 0);
Вторая координата ограничивающего прямоугольника соответствует право( му нижнему углу изображения платформы. Все оставшиеся стороны прямоуголь( ника дорисовываются автоматически на основе этих двух точек. В итоге получа( ется, что мы надели на спрайт ограничивающий прямоугольник размером в сам спрайт, а поскольку метод Collisions() постоянно вызывается в итерациях иг( рового цикла, то и координаты прямоугольника будут постоянно изменяться и соответствовать текущему положению платформы на экране. Такой подход по( зволяет нам не только создавать ограничивающие прямоугольники, но и отслежи( вать положение спрайтов на экране монитора. Создание ограничивающих прямоугольников для падающих объектов проис( ходит аналогичным образом, но уже применительно к каждому элементу массива данных. // Ñîçäàåì îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê äëÿ sprite[0] bb[0].Min = new Vector3(sprite[0].spritePosition.X, sprite[0].spritePosition.Y, 0); bb[0].Max = new Vector3(sprite[0].spritePosition.X + øèðèíà îäíîãî ôðåéìà, sprite[0].spritePosition.Y + âûñîòà îäíîãî ôðåéìà, 0);
При задании значений для переменных Max каждого из прямоугольников спрайта используются не числовые значения, а размер текстуры. Единственное, что нужно помнить, – это то, что при получении ширины изображения вам будет выдан весь фактический размер анимационной последовательности спрайта (все 12 фреймов), поэтому полученное значение необходимо поделить на количество имеющихся фреймов анимации. Вторая часть метода Collisions() направлена на определение столкно( вений между платформой и каждым спрайтом, падающим с неба. Для этого ис( пользуется метод Intersects() структуры BoundingBox. Дословный перевод названия метода обозначает пересечение. То есть этот метод определяет, пересек( лись между собой прямоугольники или нет, что равносильно определению столк( новения между двумя спрайтами.
Дословно на русском языке строка исходного кода if(bbplatform. Intersects(bb[0])) обозначает следующее: если ограничивающий прямо( угольник платформы будет пересекаться с ограничивающим прямоугольником спрайта под номером ноль, то необходимо выполнить заданные действия. В ка( честве заданных действий в столкновении мы определяем, что нужно убрать пря( моугольник с экрана и поставить его на новую игровую позицию в верхней части дисплея. После чего метод MoveSprite() заставит перемещаться этот спрайт в направлении сверху вниз или падать с неба. Если столкновение или пересечение ограничивающих прямоугольников не происходит, то спрайт уходит за пределы экрана и затем опять устанавливается на новую игровую позицию. В приведенном выше исходном коде для каждого спрайта происходит вызов метода Intersects() и соответственно идет слежение за пересечением всех прямоугольников между собой. Но поскольку мы имеем дело с массивом данных, то весь этот исходный код можно поместить в цикл, как первую часть, так и вто( рую часть метода Collisions(). В итоге мы значительно уменьшим и упростим исходный код данного метода. Помещаем все в цикл и смотрим, что получилось. public void Collisions() { /* Ïåðâàÿ ÷àñòü */ // Ñîçäàåì îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê äëÿ ïëàòôîðìû bbplatform.Min = new Vector3(platform.spritePosition.X, platform.spritePosition.Y, 0); bbplatform.Max = new Vector3(platform.spritePosition.X + platform.spriteTexture.Width, platform.spritePosition.Y + platform.spriteTexture.Height, 0); // Ñîçäàåì îãðàíè÷èâàþùèå ïðÿìîóãîëüíèêè äëÿ ñïðàéòîâ for (int i = 0; bb.Length > i; i++) { bb[i].Min = new Vector3(sprite[i].spritePosition.X, sprite[i].spritePosition.Y, 0); bb[i].Max = new Vector3(sprite[i].spritePosition.X + sprite[i].spriteTexture.Width / 12, sprite[i].spritePosition.Y+ sprite[i].spriteTexture.Height, 0); } /* Âòîðàÿ ÷àñòü */ // Ïðîâåðÿåì ñòîëêíîâåíèÿ ìåæäó ïëàòôîðìîé è ñïðàéòàìè for (int i = 0; bb.Length > i; i++)
148
Игровые столкновения
{ if (bbplatform.Intersects(bb[i])) { sprite[i].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[i].spriteTexture.Width / 12), -500); } } }
И напоследок один небольшой секрет. Когда вы работаете с циклом for, то, как правило, вызов этого цикла происходит следующим образом: for(int i = 0; 5 > i; i++) {…}
Но есть и другой, более интересный способ создания цикла for: for(int i = 5; —i >= 0;) {…}
Главным в этой записи является то, что подобная формулировка цикла позво( ляет работать циклу примерно на 10–13% быстрее, чем его стандартная запись. В компьютерных играх (мощные системные ресурсы) это, конечно, не столь ак( туально, но вот в мобильных играх я постоянно использую только такую запись. Теперь вы можете запустить рассматриваемый в этой главе проект и с по( мощью платформы ловить падающие с неба объекты. Постепенно все становится похожим на действительно полноценную игру. Единственное, что сейчас можно несколько видоизменить, – так это уменьшить ограничивающий прямоугольник для платформы. В частности, сделать его по высоте не в размер всей текстуры платформы, а, скажем, всего в пару пикселей (рис. 10.3). Полный размер высоты платформы кажется несколько большим, и иногда достаточно коснуться падаю( щего объекта боком – и считается, что вы его поймали. Если уменьшить площадь ограничивающего прямоугольника, а именно его боковую часть, то игровой про( цесс станет более реалистичным. bbplatform.Max = new Vector3(platform.spritePosition.X + platform.spriteTexture.Width, platform.spritePosition.Y + 25 + 2, 0);
Рис. 10.3. Уменьшение ограничивающего прямоугольника платформы
Глава 11 Подсчет очков и вывод текста на экран В этой главе мы добавим в игру несколько дополнительных игровых элементов, которые позволят улучшить качество игрового процесса, да и сама игра приобре( тет некоторую цель. Что касается цели, то по сюжету игры пользователь обязан ловить и не ловить различные предметы, падающие с неба. В связи с этим в игре необходимо реализовать механизм, который будет вести простой подсчет набран( ных очков. Путей реализации этой задачи может быть несколько. Прежде всего вы должны проработать стратегию начисления очков игроку. Вариантов здесь много, все зависит от самой цели игры. Например, можно под( считывать очки только от тех предметов, которые действительно нужно ловить, а за пойманный предмет, который ловить было не нужно, – уменьшать баллы или даже отнимать жизнь у игрока. Можно также весь игровой процесс поставить на счетчик и отводить игроку определенное количество времени, за которое игрок должен набрать как можно больше очков. В этом случае за каждый пойманный предмет, который ловить было не нужно, можно отнимать у игрока очки или уменьшать игровое время. Система начисления очков в целом определяет всю иг( ровую стратегию и саму цель игры. В демонстрационном примере вам просто будет показано, как именно ведется подсчет очков. Мы будем подсчитывать каждый пойманный объект, организовав для каждого предмета свой счетчик. Но в своих играх вам нужно придумать более изощренную систему подсчета очков, чтобы пользователю было действительно интересно играть в вашу игру, поскольку каждый игровой процесс должен иметь определенную конечную цель. Во второй части главы мы рассмотрим проект Font, где выведем на экран все набранные пользователем очки. Для этих целей будет использоваться специаль( ный класс для работы со шрифтом, благодаря которому вы в своих играх сможете организовать вывод любого текста на экран. Класс BitmapFont доступен для свободного использования в любых программах, создан он программистом Gary Kacmarcik, работающим в корпорации Microsoft. Подход в использовании клас( сов или библиотек, написанных сторонними программистами, значительно облег( чает создание игры, уменьшая затраты – как временные, так и денежные. А вы на конкретном примере сможете ознакомиться с тем, как можно использовать сто( ронний код в своих программах.
150
Работа с текстом
Подсчет очков и вывод текста на экран
11.1. Подсчет очков Продолжаем улучшать игру, а в качестве базового проекта используем последний проект из десятой главы. В этой главе не будет создано два отдельных проекта по каждой теме, поскольку схематика подсчета очков вносит незначительные изме( нения в общий исходный код игры. Поэтому был сделан всего один проект под общим названием Font. Все изменения при подсчете очков касаются только ос( новного класса Game1. В работе с текстом добавится дополнительный класс BitmapFont, но об этом подробнее вы узнаете во второй части этой главы. Как всегда, исходный код этого проекта вы найдете на компакт(диске в папке Code\Chaper11\Font. Как мы уже выяснили, любой подсчет очков определяет всю игровую страте( гию. В нашем случае за каждый пойманный объект будет начисляться одно очко, иначе говоря, мы будем вести простой подсчет пойманных предметов. Для под( счета очков по каждому падающему объекту нам понадобится отдельная целочис( ленная переменная. Переходим к работе над проектом и в классе Game1 в области глобальных пе( ременных класса Game1 объявим пять новых переменных. int score0, score1, score2, score3, score4;
Все пять переменных пока не имеют никаких значений, и нам необходимо инициализировать их. В методе Initialize() зададим каждой переменной ну( левое значение, поскольку игрок еще не поймал ни одного объекта.
151
В этом коде видно, что как только платформа сталкивается с объектом или происходит пересечение двух ограничивающих прямоугольников, значение пере( менной score2 увеличивается на единицу. Для этого используется упрощенный вариант увеличения значения на единицу, но если развернуть эту строку кода, то она выглядит вот так: score2 = score2 + 1;
Точно такой же подход в подсчете очков необходимо использовать и для ос( тавшихся четырех объектов (см. листинг 11.1). Кстати, если вашей целью являет( ся только подсчет падающих предметов, то в коде можно создать массив данных для подсчета очков (если нужно считать очки по каждому объекту) или выделить одну переменную для общего подсчета всех очков. … // î÷êè ïî êàæäîìó îáúåêòó â ìàññèâå äàííûõ score[i] += 1; // ïîäñ÷åò îáùåãî êîëè÷åñòâà î÷êîâ äëÿ âñåõ îáúåêòîâ ðàçîì score += 1;
Здесь все определяет общая стратегия игры. Например, при столкновении платформы и объекта (который ловить не нужно) можно отнимать набранные баллы как по каждой переменной отдельно, так и по общему количеству очков. Score2 -= 1;
или // Îáíóëÿåì èìåþùèåñÿ î÷êè score0 = 0; score1 = 0; score2 = 0; score3 = 0; score4 = 0;
Теперь мы имеем пять переменных для каждого падающего с неба объекта. Чтобы начислить одно очко за каждый пойманный объект или подсчитать коли( чество пойманных объектов, нам необходимо при столкновении платформы с од( ним из объектов увеличить соответствующую перемену. Посмотрите на исход( ный код обработки столкновения для падающего с неба объекта под номером два. if (bbplatform.Intersects(bb[2])) { sprite[2].spritePosition = new Vector2(rand.Next(10, Window.ClientBounds.Width 300 - sprite[2].spriteTexture.Width / 12), -500); score2 += 1; }
score -= 1;
При этом если вы ведете подсчет жизней игрока, то за каждое ненужное столк( новение платформы и объекта, кроме уменьшения очков, можно также уменьшать игровую жизнь пользователя или даже уменьшать время, отведенное на игру. По( вторяюсь, все определяет игровая стратегия, которую необходимо очень тщатель( но продумать. Все, что касается подсчета очков, мы выяснили, и, как оказалось, дело это совсем не сложное, поэтому переходим ко второй части главы, цель которой со( стоит в том, чтобы научиться выводить на экран набранные пользователем очки и другую сопутствующую информацию.
11.2. Работа с текстом Рисовать текст на экране монитора в приложениях можно различными способа( ми. Самый простой способ – это использование системных классов XNA Frame( work, но тогда весь текст, а точнее шрифт будет иметь стандартный вид, и в краси(
152
Работа с текстом
Подсчет очков и вывод текста на экран
во оформленный интерфейс игры он может элементарно не вписываться и даже портить общее впечатление от игры. Поэтому обычно для игр рисуются отдель( ные шрифты, которые впоследствии загружаются в программный код. Графическое изображение шрифта представляет собой набор всех или почти всех букв алфавита, чисел, знаков препинания, специальных символов, выполнен( ных в одной стилистике. Каждый символ рисуется в отдельном фрейме, а затем все символы компонуются в единое графическое изображение (рис. 11.1). Затем в игре пишется определенный класс, который загружает изображение шрифта в про( граммный код. При выводе текста на экран происходят автоматическое считывание нарисованных символов с изображения (которые, естественно, совпадают с симво( лами выводимого текста) и показ строки текста, предложения, числовых значений на экране уже новым красиво оформленным шрифтом. Мы поступим точно так же, но сами делать ничего не будем, а используем готовое решение.
153
статочно его просто усовершенствовать. Такие решения могут предо) ставляться бесплатно, а могут предоставляться и на базе платных ли) цензионных отчислений (игровые движки). Соберите нужную информа) цию, уточните все детали и приступайте к работе. Сейчас на руках мы имеем готовое решение, и море благодарностей человеку по имени Gary Kacmarcik. Приступаем к работе над проектом, в котором будем вести подсчет очков и полученные результаты выводить на экран монитора. Прежде чем перейти к исходному коду, давайте сначала посмотрим, что представ( ляет собой готовое решение XNAExtras.
11.2.1. Готовое решение XNAExtras Библиотека XNAExtras (наверное, ее можно так назвать, пусть она и небольшая) содержит следующий набор функциональных элементов.
Рис. 11.1. Набор цифр, нарисованных в графическом редакторе
Работа программиста сводится не только к написанию исходного кода, но и к поиску уже готовых решений, и хорошо, если эти решения будут бесплатными. Поэтому я решил потратить немного рабочего времени на поиск уже готового ре( шения. Спустя буквально десять минут поиска было найдено абсолютно бесплат( ное решение с названием XNAExtras от программиста Gary Kacmarcik, работаю( щего в корпорации Microsoft. Это решение позволяет формировать изображения символов на основе установленных у вас в системе шрифтов. В новой версии XNA Game Studio Express, которая должна появиться в ап) реле)марте 2006 года, работа с текстом будет осуществляться встроен) ными механизмами студии. Аналогичная книга по программированию игр для Xbox 360, будет писаться по завершению этой книги, поэтому в рабо) те с текстом мы задействуем уже новые механизмы XNA Game Studio Express. Если вы желаете работать со встроенными средствами, то обра) титесь к книге «Программирование игр для приставки Xbox 360 в XNA Game Studio Express». В этой же книге для работы с текстом задейству) ется сторонняя библиотека, которая ко всему прочему еще и покажет вам технику подключения в игру сторонних наработок. Дополнительно в папке XNAExtras еще находился ряд интересных примеров по выводу текста в рамках, столкновению объектов в попиксельном режиме и многому другому. Все это добро вы можете найти на компакт(диске в папке Soft\XNAExtras. Прежде чем начинать разработку любого проекта, поищите готовые ре) шения по необходимой вам тематике. Не нужно изобретать велосипед, до)
Класс BitmapFont – этот класс реализует загрузку шрифтов в программу, вы( вод текста на экран с возможностью его форматирования. Загрузка шрифтов в программу производится из XML(файла, который, в свою очередь, загружа( ет графическое изображение с любым нарисованным шрифтом. XMLфайлы – поставляется несколько XML(файлов с решением XNAExtras для различных языков мира. Файл XML регулирует загрузку шрифта в игру, а также занимается его непосредственным форматированием, а точ( нее создает спецификацию, на основе которой класс BitmapFont работает с текстовыми строками и числами. Изображения – готовые изображения шрифтов для нескольких языков, включая даже японский. В примере используется достаточно простой систем( ный шрифт. Для того чтобы сгенерировать необходимый шрифт из графиче( ского изображения, необходимо воспользоваться утилитой BMFontGen, ко( торая есть в составе XNAExtras. BMFontGen – эта утилита, входящая в состав XNAExtras, позволяет гене( рировать на основе имеющихся у вас графических изображений подборку шрифтов и компоновать шрифт в единичный DDS(файл, который с по( мощью класса BitmapFont вы можете загрузить в игру.
11.2.2. Как это работает? Чтобы воспользоваться готовым решением XNAExtras, необходимо создать в проекте дополнительный класс BitmapFont и скопировать в него исходный код оригинального класса BitmapFont библиотеки XNAExtras. Затем добавить в ваш проект графическое изображение шрифта, а к нему – дополнительный XML(файл. Каждый XML(файл описывает свойства созданного шрифта. После этих манипуляций вы переходите к классу Game1, где создаете объект нового до( бавленного класса BitmapFont, и далее пользуетесь методами этого класса для
154
Подсчет очков и вывод текста на экран
вывода текста на экран, особо не заботясь о том, как устроен и как работает этот самый класс BitmapFont. Классическое решение черного ящика и объектно(ори( ентированного подхода реализации проектов на базе сторонних библиотек.
11.2.3. Как создать шрифт? Чтобы создать шрифт, необходимо воспользоваться командной строкой. Выпол( ните на компьютере в меню Пуск команды Все программы ⇒ Стандартные ⇒ Командная строка. Откроется окно командной строки. По умолчанию путь в окне командной строки указывает на папку Document and Settings\Имя пользовате ля. Чтобы не прописывать в командной строке путь к утилите BMFontGen, про( сто перенесите ее в эту папку с компакт(диска, и именно в этой папке в дальней( шем вы будете получать готовые шрифты. Для того чтобы создать шрифт, необходимо использовать определенные коман( ды, например следующая строка текста создает шрифт Courier New размером в 12 pt. bmfontgen -name “Courier New” -size 12 -output “courier”
Первая команда bmfontgen запускает работу программы BMFontGen. Вторая команда -name «Courier New» будет создавать изображение сим( волов из установленного в системе шрифта Courier New. Третья команда -size 12 задает размер создаваемого шрифта. И последняя часть строки – (output «courier» – определяет выходное название файла изображения и XML(файла. Используя такую запись, вы можете создать любой набор символов из установленных у вас в системе шрифтов. Все шрифты создаются белого цвета на черном фоне в формате PNG. В дальнейшем в своем исходном коде можно закрашивать шрифт любым цветом. Еще одна интересная команда с названием -range 0020-007e позволяет задавать диапазон символов, участвующих в создании шрифта, например:
Работа с текстом
155
11.2.4. Добавляем в проект необходимые компоненты Теперь давайте, чтобы ничего не упустить, в пошаговом режиме добавим в проект все перечисленные компоненты, а уже потом перейдем к работе с исходным ко( дом. Создайте в Visual C# новый проект или модифицируйте последний рассмот( ренный проект из десятой главы и переходите к выполнению следующих шагов (в примере у нас проект Font). 1. Сформируйте в проекте новый класс под названием BitmapFont. Для этого щелкните правой кнопкой мыши на названии проекта и в появившемся кон( текстном меню изберите команды Add ⇒ New Item. Откроется диалоговое окно Add New Item Font. В списке Templates выберите курсором шаблон Class, а в поле Name задайте имя классу как BitmapFont (рис. 11.2). Затем откройте на компакт(диске папку Soft\XNAExtras\XNA Samples\ Common, найдите файл BitmapFont.cs и скопируйте его содержимое в буфер обмена, вставив далее скопированный код в ваш новый класс с од( ноименным названием. 2. Далее необходимо явно добавить в проект файл XML и графическое изоб( ражение шрифта. И то и другое находится также на компакт(диске в папке Soft\XNAExtras\XNASamples\BitmapFontDemo\Content. В этой папке проект XNAExtras содержи