Введениев программированиетрехмерных игрВ этой части
Скачать 0.7 Mb.
|
ЧАСТЬ I Введение в программирование трехмерных игр В этой части... Глава Основы программирования трехмерных игр 29 Глава Краткий курс Windows и Глава Виртуальный компьютер для программирования трехмерных игр ГЛАВА 1 Основы программирования трехмерных игр В этой главе... • Краткое введение 30 • Элементы двумерных и трехмерных игр 31 • Общие советы по программированию игр 36 • Использование инструментов 40 • Пример трехмерной игры Raiders 3D 48 Для того чтобы разогреть ваш интерес, в этой главе мы рассмотрим некоторые общие темы программирования игр, такие как игровые циклы и различия между двумерными и трехмерными играми. В конце мы создадим небольшую трехмерную игру, чтобы правильно настроить компилятор и DirectX. Если вы уже прочитали мою первую книгу Программирование игр для Windows. Советы профессионала, то, наверное, можете бегло просмотреть эту главу и сосредоточиться на материале, изложенном в самом конце. Если же нет, вам определенно стоит прочитать ее — даже если вы игровой программист среднего или высокого уровня. Итак, вот содержание главы: • краткое введение в программирование игр; • элементы двумерных и трехмерных игр; • общие советы по программированию игр; • использование инструментов; • пример игры для Windows: Raiders 3D. ЧАСТЬ I. ВВЕДЕНИЕ В ПРОГРАММИРОВАНИЕ ТРЕХМЕРНЫХ ИГР Краткое введение Эта книга на самом деле представляет собой второй том серии (вероятно, трехтомной, посвященной программированию двумерных и трехмерных игр. В первом томе Программирование игр для Windows. Советы профессионала рассматриваются в первую очередь следующие темы. • Программирование для Интерфейс прикладного программирования (API) Основы Искусственный интеллект • Основы физического моделирования • Звук и музыка • Алгоритмы • Программирование игр • Двумерная растровая и векторная графика Эта книга продолжает предыдущую. Тем не менее, я попытался написать ее так, что если вы не читали первую, то все равно сможете получить изданной книги массу информации по трехмерной графике реального времени и ее применению для создания трехмерных игр. Следовательно, идеальный читатель данной книги — это тот, кто прочел первую книгу серии и интересуется программированием трехмерных игр, либо тот, кто уже умеет делать двумерные игры и стремится освоить трехмерные методы сточки зрения написания программ и алгоритмов Помня об этом, я собираюсь сосредоточиться в этой книге на трехмерной математике и графике, ив меньшей степени касаться всего, что относится к программированию игр. Я исхожу из того, что это вы уже умеете, — если нет, я в очередной раз советую вам прочитать Программирование игр для Windows. Советы профессионала (либо любую другую книгу по программированию игр) и посидеть за компьютером (до ощущения усталости), изучая Windows, DirectX и игровое программирование в целом С другой стороны, даже если вы не читали предыдущую книгу и ничего не знаете о программировании игр, вы все равно кое что почерпнете для себя из этой книги — хотя бы потому, что здесь много иллюстраций. Мы собираемся заниматься от главы к главе созданием трехмерного игрового процессора, однако чтобы сэкономить время (и около страниц, мы начнем с базового процессора DirectX, разработанного впервой книге. Конечно, этот процессор использовал DirectX версий 7 и 8; сейчас DirectX изменился ив версии 8.0+ поддержка двумерных приложений стала сложнее, поскольку DirectDraw теперь объединен с Direct3D. Мы собираемся продолжить работу с интерфейсами DirectX и 8, но компилировать наши приложения с DirectX Это книга о программном обеспечении и алгоритмах, а не о DirectX. Поэтому я мог бы написать программу поди такой материал был бы вполне уместен. Нам необходимы только в меру сложная графика, система ввода/вывода и работа со звуком для этих целей более чем достаточно DirectX 8.0+. Другими словами, если вы приверженец Linux, перенос игрового процессора под SDL пройдет без проблем Таким образом, если вы уже прочитали предыдущую книгу, игровой процессор и его функции уже знакомы вам. Если нет, его следует считать черным ящиком (код кото НА ЗАМЕТКУ ГЛАВА 1. ОСНОВЫ ПРОГРАММИРОВАНИЯ ТРЕХМЕРНЫХ ИГР 31 рого, впрочем, прилагается, поскольку в данной книге мы будем лишь добавлять трехмерный аспект к тому, что уже было сделано ранее Не беспокойтесь, в следующей главе мы полностью рассмотрим API и структуру двумерного игрового процессора, а также все функции, которые он выполняет. Я также покажу вам ряд демонстрационных программ, чтобы заинтересовать вас в использовании базового процессора DirectX, созданного впервой книге В этой книге я хочу попытаться представить максимально общую, виртуальную точку зрения на графическую систему. Хотя код будет основан на игровом процессоре из первой книги, главное в том, что этот игровой процессор не делает ничего, кроме настройки графической системы с двойной буферизацией со стандартной линейной адресацией Таким образом, если вы хотите перенести такой код на платформу Mac или Linux, он должен нормально заработать — после нескольких часов работы (максимум — до недели. Моя цель — научить вас работать с трехмерной графикой и математикой в целом. Так сложилось, что доминирующей вычислительной системой в настоящий момент является на платформе Windows, поэтому именно на ней я и построил низкоуровневую обработку. И мы посвятим наше время рассмотрению именно этих концепций верхнего уровня. Мне уже приходилось писать о двумерных и трехмерных игровых процессорах, и у меня на жестком диске есть соответствующий материал. Однако когда я пишу книгу, я предпочитаю создавать новый игровой процессор, те. я пишу игровой процессор для книги, а не книгу для игрового процессора. В этой книге я не использую трехмерный игровой процессор повторно, а создаю его. Поэтому я не вполне уверен, что мне удастся эта книга Мне даже интересно узнать, что же выйдет из этой затеи. Вы узнаете все необходимое для того, чтобы создать собственный игровой процессор Quake, ноя могу походу дела переключиться на процессор для игр на открытом воздухе или какой то еще — кто знает, куда меня занесет Я исхожу из того, что работа над процессором гораздо полезнее, чем код с примечаниями автора Наконец, читатели моей первой книги могут заметить, что часть материала одинакова в обеих книгах. В самом деле, я не могу сразу начать с 3D графики без использования материала из предыдущей книги. Яне могу рассчитывать, что у каждого есть моя первая книга и не могу заставить купить ее. В любом случаев первых нескольких главах материал будет несколько перекликаться с первой книгой — по крайней мере, в отношении, игрового процессора и Windows. Элементы двумерных и трехмерных игр Для начала давайте посмотрим, чем видеоигра отличается от любого другого вида программ. Видеоигры — очень сложный вид программ, писать которые труднее всего. Конечно, написать что нибудь типа MS Word труднее, чем игру типа Asteroids, но написать что то вроде Unreal, Quake Arena или Halo труднее, чем любую программу, которую можно себе представить, включая военную программу управления вооружениями Это означает, что вам нужно изучить новый способ программирования, который более подходит для написания моделирующих приложений реального времени, чем программ, управляемых событиями, или последовательных логических программ. Видеоиг ра — это непрерывный цикл, в котором исполняется логический сценарий и рисуется картинка на экране — обычно с частотой не менее 30 60 кадров в секунду. Это напоминает фильм, но фильм, которым можно управлять Давайте начнем с рассмотрения упрощенной схемы игрового цикла (рис. 1.1). Вот краткое описание стадий цикла ЧАСТЬ I. ВВЕДЕНИЕ В ПРОГРАММИРОВАНИЕ ТРЕХМЕРНЫХ ИГР Завершение работы Синхронизация экрана Инициали зация Вход в игровой цикл Получение данных от игрока Выполнение программы искусственного интеллекта и игровой логики Визуализация следующего кадра Возврат в операционную систему Выход Продолжение Задний буфер Основной буфер Копирование Задний буфер FPS 60 Рис. 1.1. Общая архитектура цикла игры Стадия 1. Инициализация На этой стадии выполняются стандартные операции, характерные для любой программы, такие как выделение памяти, получение ресурсов, загрузка данных с диска и т.п. Стадия 2. Вход в игровой цикл На этой стадии выполнение кода достигает входа в игровой цикл. Здесь начинается действие игры, и оно продолжается, пока пользователь не выйдет из основного цикла Стадия 3. Получение данных от игрока На этой стадии данные, введенные игроком, обрабатываются и/или заносятся в буфер для дальнейшего использования на стадии исполнения программы искусственного интеллекта и логики Стадия 4. Выполнение программы искусственного интеллекта и игровой логики Эта стадия содержит основную часть игрового кода. Исполняются программы искусственного интеллекта, физических систем, общей игровой логики. Результаты используются для формирования следующего кадра изображения ГЛАВА 1. ОСНОВЫ ПРОГРАММИРОВАНИЯ ТРЕХМЕРНЫХ ИГР Стадия 5. Визуализация следующего кадра На этой стадии данные, полученные от игрока, и результаты исполнения программы искусственного интеллекта и логики используются для формирования следующего кадра игрового изображения. Это изображение обычно формируется во внеэкранной буферной зоне. Его визуализацию невозможно увидеть. Затем происходит быстрое копирование изображения на экран, в результате чего возникает анимационный эффект. В случае трехмерного программного игрового процессора происходит визуализация тысяч (вне которых случаях миллионов) многоугольников, формирующих игровую сцену. В случае трехмерного игрового процессора с аппаратным ускорением, основанного на или Direct3D, значительная часть работы перекладывается на аппаратный ускоритель Стадия 6. Синхронизация экрана Скорость, с которой компьютер исполняет игровой код, зависит от уровня сложности игры. Например, если на экране находится 1 000 движущихся объектов, нагрузка на центральный процессор будет значительно больше, чем в случае только с 10 объектами. Следовательно, частота кадров в игре будет меняться, что неприемлемо. Поэтому нужно обеспечить постоянную частоту кадров в игре с помощью функций для работы со временем и функций ожидания. В настоящее время 30 fps (кадров в секунду, frame per считается минимально допустимым значением, а 60 fps — идеальным. Частоты выше вряд ли оправданны, поскольку мозг с трудом обрабатывает информацию, поступающую с такой высокой скоростью Хотя в ряде игр и достигается идеальная скорость 30 60 кадров в секунду, она может упасть с увеличением уровня сложности визуализации. Тем не менее, с помощью контролируемых повремени расчетов в разделах игровой логики, физики и искусственного интеллекта можно, по крайней мере, попытаться обеспечить согласование сцены во времени, те, например, при меньшей частоте обновления кадров объекты будут проходить большее расстояние между двумя кадрами Стадия 7. Начало нового цикла Эта стадия довольно проста — всего лишь возврат к началу игрового цикла и повторение его заново Стадия 8. Завершение работы Это конец игры, в том смысле, что пользователь выходит из основного раздела кода или игрового цикла и желает возвратиться в операционную систему. Однако, прежде чем сделать это, необходимо освободить все ресурсы и очистить систему также, как это делается в случае исполнения любой программы. Следует отметить, что приведенное объяснение немного упрощенное, однако оно верно отображает суть происходящего. В действительности игровой цикл в большинстве случаев является конечным автоматом, содержащим ряд состояний. Например, ниже приведен более подробный код, иллюстрирующий, как может выглядеть игровой цикл в реальном C/C++ коде GAME_INIT // #define GAME_MENU // #define GAME_START // #define GAME_RUN // #define GAME_RESTART // #define GAME_EXIT // НА ЗАМЕТКУ ЧАСТЬ I. ВВЕДЕНИЕ В ПРОГРАММИРОВАНИЕ ТРЕХМЕРНЫХ ИГР game_state = GAME_INIT; // int error = 0; // // // // - main void main() { // while (game_state!=GAME_EXIT) { // switch(game_state) { case GAME_INIT: // { // Init(); // game_state = GAME_MENU; } break; case GAME_MENU: // { // // game_state = Menu(); // : // RUN } break; case GAME_START: // { // , // // // Setup_For_Run(); // game_state = GAME_RUN; } break; case GAME_RUN: // { // // // Clear(); // ГЛАВА 1. ОСНОВЫ ПРОГРАММИРОВАНИЯ ТРЕХМЕРНЫХ ИГР Get_Input(); // // Do_Logic(); // , // // Render_Frame(); // Wait(); // // - // // , , } break; case GAME_RESTART: // { // // Fixup(); // game_state = GAME_MENU; } break; case GAME_EXIT: // { // , // // Release_And_Cleanup(); // error = 0; // : // , . . // // } break; default: break; } // switch } // while // return(error); } // main ЧАСТЬ I. ВВЕДЕНИЕ В ПРОГРАММИРОВАНИЕ ТРЕХМЕРНЫХ ИГР Хотя этот код нефункционален, я думаю, что изучив его, вы поймете идею структуры реального цикла игры. Все циклы игр — не имеет значения, трехмерных или двумерных довольно точно повторяют приведенную структуру. На рис. 1.2 показана диаграмма переходов между состояниями игрового цикла. Как видно, переходы между состояниями довольно последовательны. Game_Init Состояние Состояние Состояние Состояние Состояние Состояние Возврат в ОС Рис. 1.2. Диаграмма переходов между логическими состояниями цикла игры Общие советы по программированию игр Следующее, о чем я хочу поговорить, — это общепринятые методы и философия программирования игр, о которых вам следует подумать и которые следует принять (если, конечно, вы это сможете. Они значительно облегчают процесс программирования Начнем с того, что видеоигры — это компьютерные программы, требующие высокой производительности компьютера. Это означает, что для разделов кода с высоким требованием к машинному времени и памяти больше нельзя использовать высокоуровневые API. В большинстве случаев следует самому писать все, что касается внутреннего цикла игрового кода, иначе игра будет работать ужасающе медленно. Конечно, это не означает, что нельзя полагаться на такие API, как DirectX, поскольку DirectX специально написан так, чтобы быть максимально быстрым при минимальном размере кода. Нов целом следует избегать вызовов высокоуровневых функций Win32 API. Например, вы можете решить, что функция memset() достаточно быстрая, однако она заполняет память побайто во. Значительно лучше использовать версию функции, которая заполняет память сразу по четыре байта (одно слово. Вот пример функции на встроенном ассемблере, которую я использую для заполнения памяти по четыре байта void Mem_Set_QUAD(void *dest, UINT data, int count) { // 32 . count — // ГЛАВА 1. ОСНОВЫ ПРОГРАММИРОВАНИЯ ТРЕХМЕРНЫХ ИГР _asm { mov edi, dest ; edi mov ecx, count ; 32- mov eax, data ; 32- rep stosd ; } // asm } // Mem_Set_QUAD А вот версия для двухбайтового слова void Mem_Set_WORD(void *dest, USHORT data, int count) { // 16 . count — // _asm { mov edi, dest ; edi mov ecx, count ; 16- mov ax, data ; 16- rep stosw ; } // asm } // Mem_Set_WORD Эти несколько строк кода могут в некоторых случаях ускорить игру в два или четыре раза Поэтому вам обязательно следует знать, что находится внутри функции API, если вы собираетесь ее использовать. Кроме того, Pentium III, 4 и последующие версии поддерживают SIMD команды Instruction Multiple Data — один поток команд и множество потоков данных), которые позволяют распараллеливать обработку простых математических операций, поэтому здесь открывается большое поле деятельности для оптимизации базовых математических операций, таких как векторная математика и умножение матриц. К этому мы вернемся позднее Давайте взглянем на перечень приемов, о которых следует помнить вовремя про граммирования. Не бойтесь использовать глобальные переменные. Многие видеоигры не используют передачу параметров в критических повремени выполнения функциях, вместо этого применяя глобальные переменные. Рассмотрим, например, следующую функцию Plot(int x, int y, int color) { // video_buffer[x + y*MEMORY_PITCH] = color; } // В данном случае тело функции выполняется гораздо быстрее, чем ее вызов, вследствие необходимости внесения параметров в стеки снятия их оттуда. В такой ситуации более эффективным может оказаться создание соответствующих глобальных переменных и передача информации в функцию путем присвоения им соответствующих значений. НА ЗАМЕТКУ СЕКРЕТ ЧАСТЬ I. ВВЕДЕНИЕ В ПРОГРАММИРОВАНИЕ ТРЕХМЕРНЫХ ИГР gx, gy, gcolor; // void Plot_G(void) { // video_buffer[gx + gy*MEMORY_PITCH] = gcolor; } // Используйте встраиваемые функции. Предыдущий фрагмент можно еще больше улучшить с помощью директивы inline, которая полностью устраняет код вызова функции, указывая компилятору на необходимость размещения кода функции непосредственно вместе ее вызова. Конечно, это несколько увеличивает программу, но скорость для нас гораздо важнее void Plot_I(int x, int y, int color) { // video_buffer[x + y*MEMORY_PITCH] = color; } // Заметим, что здесь не используются глобальные переменные, поскольку компилятор сам заботится о том, чтобы для передачи параметров не использовался стек. Тем не менее, глобальные переменные могут пригодиться, если между вызовами изменяется только один или два параметра, поскольку при этом не придется заново загружать старые значения. Всегда используйте 32 битовые переменные вместо 8 или 16 битовых. Pentium и более поздние процессоры — 32 битовые, а это означает, что они хуже работают со словами данных размером 8 и 16 битов и их использование может замедлить работу в связи с эффектами кэширования и другими эффектами, связанными с адресацией памяти. Например, вы можете создать структуру, которая выглядит примерно следующим образом CPOINT { short x, y; unsigned char c; } // Хотя использование такой структуры может показаться стоящей идеей, на самом деле это вовсе не так Эта структура имеет размер 5 байтов char)) = 5. Это не очень хорошо в силу особенностей адресации памяти у 32 битовых процессоров. Гораздо лучше использовать следующую структуру CPOINT { int x, y; int c; } // Такая структура гораздо лучше. Все ее элементы имеют одинаковый размер — 4 байта, а следовательно, все они выровнены в памяти на границу DWORD. Несмотря на выросший размер данной структуры, работа с ней осуществляется эффективнее, чем с предыдущей Теоретически возможна ситуация, когда из за использования встроенных функций внутренний цикл может вырасти и перестать полностью помещаться в кэше процессора, что приведет к снижению производительности по сравнению с использованием обычных функций. Злоупотреблять встроенными функциями не следует, и не только по этой причине. — Прим. ред. |