![]() |
![]() |
![]() |
главная страница ![]() ![]() ![]() ![]() |
![]() |
![]() |
![]() |
![]() |
Георгий Мошкин Часть 1 - Часть 2 - Часть 3 - Часть 4 - Часть 5 - Часть 6 - Часть 7 РАССА (Viverricula indica) или малая цивета, для получения мускуса интродуцирована на острова Сокотра, Филиппины, Мадагаскар. Ее естественный ареал приурочен к Юго-Восточной Азии от Индии и Южного Китая до Цейлона и Малайского архипелага. Этот нарядно окрашенный зверек мельче предыдущих видов (масса —2—4 кг), лишен стоячей гривы, уши у него взаимно сближены. Расса настолько наземный вид, что, даже спасаясь от преследования, предпочитает скрыться в зарослях, чем забраться на дерево. Питается она разнообразной животной и растительной пищей, включая падаль. Часто уничтожает больных животных. Размножается круглый год, принося по 2—5 детенышей. Как-то раз мне пришло письмо от Димы с предложением сделать игру про древнюю Русь. В своём письме он предлагал мне посетить его сайт http://varyag.h12.ru/ и ознакомиться с готовыми спрайтами для игры. По словам Димы проект был давно заброшен, а графика осталась не востребована. Поэтому идея сотрудничества заключалась в том, чтобы я спрограммировал какой-нибудь движок на основе предложенных спрайтов. Я решил сделать движок с открытым исходным кодом (open source), а для разработки игры использовать язык программирования Delphi. В качестве графического API будет OpenGL. В этой статье я буду последовательно описывать процесс разработки движка для игры "Варяг" - стратегии в реальном времени (RTS). Также будут даны некоторые пояснения для новичков. Итак, что же будет представлять собой игра "Варяг"? Мне она видится типичной RTS наподобие WarCraft 2. Как известно, процесс создания стратегических игр, да и любых игр вообще достаточно прост. Главное - это не ничего не усложнять. Для начала посмотрим что за спрайты мне прислал Дима по электронной почте.
Для вывода точки на экран нам понадобится какая-то "основа". Эту основу часто наывают framework (фреймворк), или просто "заготовка". По сути дела эта заготовка представляет собой исходный код, который после компиляции открывает чёрное окно OpenGL. Как только у нас появится такая "основа", то мы сможем на это чёрное окно вывести белую точку. Для простоты за основу возмём исходники framework.zip (9kb). В этом файле я поместил заготовки исходников для написания игры. Если вы внимательно посмотрите эти исходники, то найдёте в файле unit1.pas процедуру glDraw(). В эту процедуру мы будем добавлять команды для вывода точки на экран. Посмотрим внимательно на эту процедуру:
Эта процедура будет периодически запускаться во время работы программы. Каждый раз при запуске этой процедуры стирается картинка и z-buffer, находящиеся в заднем буфере экрана (процедура glClear), обнуляются произведённые ранее повороты и перемещения (процедура glLoadIdentity), происходит отрисовка с цены (между glLoadIdentity и SwapBuffers), передний и задний буфер меняются местами (SwapBuffers). Такая схема с применением переднего и заднего буферов необходима для предотвращения мерцания при перерисовке кадров и носит название "Double Buffering". Смысл этой схемы прост: перерисовка идёт в невидимом (заднем) буфере, а на экран отображается только передний буфер. Когда задний буфер готов к отображению, он замещает передний. Вернёмся к проблеме вывода точки на экран. Наш фреймворк (framework) для работы с OpenGL инициализирует 2D режим. Для этого используется несколько OpenGL-евских процедур вместе с glOrtho, которые вызываются из процедуры FormResize:
Здесь не нужно особо во что-то вникать. Сейчас главное заключается в следующем: у нас есть готовый фреймворк для вывода 2D графики на экран компьютера с помощью OpenGL. Также мы знаем кудадобавлять OpenGL-евские команды для того, чтобы на экране что-то появилось. Попробуем запустить наш фреймворк. Для этого откроем файл varyag.dpr из Delphi 6 и нажмём кнопку F9. После запуска вы увидите чёрное окно программы, на которое ничего не выводится. Окно программы состоит из трёх панелей: панель 1 - крупный заколовок окна, панель 2 - прямоугольное поле с левой стороны, панель 3 - чёрный прямоугольник для вывода графики с помощью OpenGL. Середина поверхности имеет координату x=Panel3.Width/2 и y=Panel3.Height/2. Для вывода точки на экран в этой координате воспользуемся процедурой glVertex2f:
Есть и другие типы примитивов, подробнее о которых вы можете узнать из других источников (книги и интернет сайты по тематике OpenGL). На основе типа примитива, который мы укажем процедуре glBegin, OpenGL будет соответствующим образом интерпретировать данные, поступающие от glVertex2f. Например, чтобы нарисовать треугольник, достаточно написать следующий код:
Заменив константу GL_TRIANGLES на GL_POINTS, вместо треугольника увидим три вершины:
Таким образом, для GL_POINTS нужна минимум одна точка, для GL_LINES - минимум две точки, для GL_TRIANGLES - минимум три точки, а для GL_QUADS - минимум четыре точки. Если вы выводите пять треугольников в блоке glBegin(GL_TRIANGLES), то это будет 3*5=15 точек. Если 100 четырёхугольников, то glVertex2f будет вызван из блока glBegin(GL_QUADS) 400 раз (100*4=400 точек). Эти пояснения даны для тех, кто мало знаком с OpenGL. Особо долго вникать в это не следует. Стоит лишь отметить три вещи:
Посмотрим повнимательнее на спрайты, с которыми нам предстоит работать. Ниже показана часть содержимого файла "peasant with gold.png". В одном файле находятся сразу несколько спрайтов с разными кадрами и углом поворота юнита. Размеры файла по ширине и высоте составляют 360 на 936. При этом в строке 5 спрайтов, а всего строк 13. Отсюда следует, что размер спрайта по ширине составляет 360 / 5 = 72 пикселя. По высоте тоже 72 пикселя (936 / 13 = 72). Итак, спрайты живых юнитов имеют размер 72 x 72.
То есть можно загружать 1x1024, 16x16, 512x256 и т.п.. Но при загрузке 50x50 или 936x936 могут возникнуть проблемы, так как эти числа не являются числом 2, возведённым в целую степень. Программа при этом может работать с ошибками. Поэтому мы дополним текстуру пустыми белыми областями справа и снизу так, чтобы её размеры увеличились от 360 x 936 до 512 x 1024. Обратите внимание, что я не делаю так называемый resize (ресайзинг), а лишь увеличиваю пустое место. Весь процесс можно представить следующим образом: на пустую картинку размером 512 x 1024 я помещаю картинку размером 360 x 936. При этом размещаю её таким образом, чтобы у этих картинок совпал левый верхний угол. Результат будем записывать в формат BMP, так как для него легко найти хороший загрузчик текстур в OpenGL. Посмотрим один из способов проведения этой манипуляции. Мы можем взять бесплатный графический редактор i.Mage (http://www.memecode.com/image.php), загрузить файл "peasant with gold.png" и выполнить последовательно следующие действия:
В файле "summer2.png" находятся травка и всякие деревья, а также пенёчки от срубленных крестьянами деревьев. Детальный осмотр этих каряг показал, что размеры картинки по ширине составляют 512 пикселей, а по высоте - 160 пикселей. Размеры тайла (tile) составляют 32 x 32 пикселя. В одной строке помещается 16 тайлов (512 / 32 = 16). Всего 5 строк (160 / 32 = 5). С этим файлом мы поступаем аналогичным образом, подбирая ближайшие большие значения из ряда чисел 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, ... По ширине изменений не требуется, так как 512 - это число из ряда, совместимого с OpenGL. А вот по высоте необходимо добавить пустого места. 128<160, поэтому выбираем следующее значение - 256. Таким образом, файл "summer2.png" из 512 x 160 переделываем в 512 x 256 и записываем в 24-битный "summer2.bmp". Аналогичные манипуляции проделываем со всеми графическими файлами. В итоге все файлы у нас готовы к загрузке в любой OpenGL программе. Небольшое отступление. Вообще необходимость в данных манипуляциях связана лишь с тем, что мы делаем игру уже на основе готовых спрайтов. А у готовых спрайтов есть некоторые особенности:
То есть требования к движку предъявлены исходя из особенностей спрайтов (сначала сделали спрайты, а потом движок). Обычно всё наоборот: у движка есть свои особенности, и требования предъявляются к спрайтам (сначала сделали движок, а потом спрайты). Вернёмся к разработке движка для RTS "Варяг". Напомню вам о том, что было сделано выше. Во-первых, мы взяли framework для работы с OpenGL и определили, как вывести точку на экран. Во-вторых, привели графические файлы со спрайтами к такому виду, что при их загрузке и использовании не возникнет ошибок OpenGL.
Как видите, загрузка текстур в OpenGL с помощью библиотеки BMP.pas очень проста: мы добавили BMP в раздел uses, задали переменную для хранения идентификатора текстуры MoyaTextura типа GLUINT. И вызвали процедуру загрузки текстуры LoadTexture. Стоит отметить, что данная библиотека может
загружать только 24-битные BMP файлы, поэтому выше мы сконвертировали
8-битные PNG файлы в 24-битные BMP с помощью программы i.Mage. Также мы
изменили размер текстур таким образом, чтобы их размер по ширине и высоте
был из ряда 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 и т.д. Итак, мы подготовили всё необходимое для загрузки текстур: у нас есть framework для инициализации OpenGL окна, есть библиотека BMP.pas для загрузки 24-битных BMP файлов и текстуры в формате BMP. Приступим к написанию кода для загрузки текстур. Создадим главный модуль rtsmain.pas и добавим его в USES к нашего framework. В Delphi нажимаем File -> New -> Unit. Затем сохраняем под именем rtsmain.pas. Для этого нажимаем File -> Save As... -> вводим rtsmain.pas -> Сохранить. Содержимое нового модуля будет выглядеть следующим образом:
Продолжим написание кода для загрузки текстур. Перейдём в окно модуля rtsmain и добавим в раздел USES имя BMP. Как только мы добавим это имя в раздел USES модуля rtsmain, у нас появится возможность использовать процедуру LoadTexture. Модуль rtsmain будет выглядеть так:
Также добавим в раздел uses модуля rtsmain имя "opengl":
Прежде чем продолжить написание загрузки текстур остановимся немного подробнее на принципе работы нашего framework, да и вообще любых программ для Windows. В статье "Всё о создании компьютерных игр" я уже рассматривал основную идею многозадачности в операционной системе Microsoft Windows. Там была дана упрощенная модель для быстрого понимания.
Таким образом, каждые N миллисекунд (Timer1 -> Object Inspector -> Properties -> Interval) вызывается главная процедура рисования в OpenGL окне. Этим обеспечивается работа "вечного" цикла (главного цикла) нашей программы. Только вместо операторов for или while используется событие OnTimer. Поэтому нужно чётко понимать, что все внутренние переменные процедуры glDraw не сохраняют своё значение между кадрами. Например, такая запись будет неправильной:
Переменная abc не будет принимать значения 0,1,2,3,4,5... так как при достижении end значение abc теряется. Из-за этого abc будет принимать значения 1,1,1,1,1 и т.д.. Чтобы передавать значения между последовательными вызовами процедуры glDraw, необходимо выносить их за пределы данной процедуры. Правильная реализация передачи данных между кадрами будет выглядеть так:
Здесь программа будет работать правильно, так как переменная abc стала "глобальной" и существует вне зависимости от процедуры glDraw. Поэтому при работе главного цикла программы значение abc не пропадает. Если вы посмотрите на исходники OpenGL фреймворков без использования VCL (визуальных компонент), то в них явно можно найти главный цикл программы. Например, основа (фреймворк) демок на сайтах http://nehe.gamedev.net и http://www.sulaco.co.za/ имеет главный цикл, который задаётся явно с помщью while:
Это главный цикл программы. В этом цикле осуществляется обработка сообщений, поступающих от Windows. Обратите внимание, что здесь процедура glDraw вызывается из цикла. Если вы посмотрите исходники визуальных компонент, которые поставляются вместе с Delphi, то обнаружите подобный цикл. А так как мы используем фреймворк, основанный на VCL библиотеках (визуальные компоненты), то нам не нужно задавать этот цикл. Благодаря использованию готовых VCL компонент мы избавляемся от необходимости писать громоздкий код на WinAPI (Windows Advanced Programming Interface) по инициализации окна и обработке сообщений от Windows. Более глубоко рассмотреть этот вопрос вы можете позже, а сейчас главное чётко понять что мы имеем. Во-первых, у нас есть фреймворк (framework), который позволяет использовать OpenGL. Чтобы выводить на экран точки, линии, треугольники и другие фигуры нам достаточно добавить соответствующие команды в процедуру glDraw нашего framework-а. Также у нас есть несколько текстур в формате BMP. Для загрузки текстур мы подключили модуль BMP.pas к нашему фреймворку. Для этого мы добавили его в раздел USES нашего фреймворка. Основной исходный код игры мы решили поместить в модуль rtsmain.pas. Для загрузки текстуры дополним модуль rtsmain процедурой Zagruz, а для вывода на экран - процедурой Kvadrat. Пока что это будет предварительный вариант, чтобы посмотреть как всё работает:
Файлы к статье: dev002src.rar - исходники (144kb), dev002exe.rar - исходники + exe (329kb). Часть 1 - Часть 2 - Часть 3 - Часть 4 - Часть 5 - Часть 6 - Часть 7 |
![]() |
![]() |
![]() |