Основа любой двухмерной графики - спрайты, т.е. простые графические картинки любого формата. Если ты хоть немного следишь за игровым миром, то должен уже слышать такое понятие как спрайтовая графика. Практически любая прога 2-х мерной графики использует графические файлы для вывода какого-нибудь изображения на экран.
Рисунок 1
До появления 3D ускорителей, спрайтовая графика была единственным способом создания игр. Даже самый знаменитый и на первый взгляд 3-х мерный Doom был полностью 2-х мерным и напичкан спрайтами. Все монстры - это просто заранее подготовленные картинки, а такие вещи как оружие или аптечки вообще не знали о существовании третьего измерения.
Работа со спрайтами похожа на мультипликацию, ты просто подготавливаешь изображение какого-нибудь предмета в разных плоскостях и потом выводишь их на экран в зависимости от ситуации.
Посмотри на рисунок 1. На нём показана вполне реальная последовательность спрайтов. Если последовательно воспроизвести каждый из спрайтов, то может возникнуть ощущение 3-х мерности самолёта, который наклоняется в твою сторону. А если ещё и постепенно двигать эти картинки по ходу движения самолёта, то можно окончательно обмануть твоё зрение. Хорошо прорисованные спрайты могут создать ощущение реального мира. Если ты хоть иногда используешь свой комп в для игр, то ты понимаешь, о чём я говорю.
Подготавливаем картинку
Сегодня мы пока ещё не будем создавать анимации, но простую статическую картинку выведем на экран. На рисунке 2 ты можешь увидеть подготовленное мной изображение.
Ты можешь создать любую другую картинку, но помни, что она должна умещаться в пределы экрана. Если хотя бы один пиксель не поместиться, то изображение не будет выведено на экран.
Для начала вспомним предыдущий урок. На нём мы создали первое полноэкранное DirectX приложение и научились создавать главную поверхность, на которой можно рисовать. Напоминаю, что поверхность - это просто область памяти. Эта область может находиться как в памяти видеокарты, так и в оперативке. Первичная поверхность всегда находиться в памяти видюхи, и всё что отображено на ней сразу отображается на мониторе. Запись в эту поверхность равносильна рисованию по самому экрану.
Ты можешь создавать дополнительные поверхности и хранить в них что угодно ведь это простая память. Так почему бы ни загрузить туда нашу картинку? В этом случае мы сможем обращаться к ней средствами DirectDraw, которые могут работать с ускорителями и выполнять любые операции над поверхностями на много быстрее.
Рисунок 2
Для начала открой проект, созданный в предыдущей статье, сейчас мы его доработаем. Перемещаемся в раздел var и радом с объявлением первичной поверхности описываем ещё одну:
FImageSurface: IDirectDrawSurface7;
Прежде чем использовать её, очень хорошим тоном было бы сначала обнулить нашу переменную. Для этого переходим туда, где начинается код нашей программы и присваиваем переменной FimageSurface значение nil.
А почему я присвоил nil, а не простой 0 (ноль)? Ноль мы можем присваивать числовым переменным, а переменные объекта - это не числа. На самом деле это указатели на область памяти, где расположен сам объект. Когда ты его создаёшь, то выделяется необходимая для хранения объекта память и переменной присваивается только ссылка. Когда ты уничтожаешь объект, память освобождается и ссылка обнуляется.
Теперь загрузим наше изображение из простого файла. По идее, сначала мы должны создать поверхность, потом открыть файл и считывая оттуда данные записывать их в поверхность. Но это только по идее, потому что если ты качал заголовочные файлы для DirectX с моего сайта то весь этот процесс выполняется за одну строчку.
Вместе с заголовочными файлами ты можешь найти файл ddutil.pas. В нём уже реализовано много стандартных операций, которые могут тебе понадобиться, в том числе и загрузка изображения. Сразу хочу сказать, что модуль написан не мной и не Васей Пупкиным, а всеми нелюбимой корпорашкой MS, так что его реализация есть и для языков С/С++.
Перейди в раздел uses и добавь туда имя этого модуля. Теперь можно загружать что угодно. После создания первичной поверхности и перед циклом обработки сообщений напиши следующее:
Здесь я вызываю функцию DDLoadBitmap, которая описана в модуле ddutil.pas. У неё четыре параметра:
1: Уже созданный объект DirectDraw
2: Имя (если надо, то и путь) файла. Если путь не указывать, то изображение должно лежать в той же директории, что и запускной файл.
3, 4: Последние два параметра - это желаемая ширина и высота картинки. Если ты укажешь здесь какие-нибудь значения, отличные от нуля, то картинка будет приведена именно к этим размерам. Если указать нули, то будут использоваться реальные размеры изображения.
Я специально не создавал поверхность FimageSurface вручную, потому что если это необходимо, то DDLoadBitmap сама проинициализирует её. Ручной труд тут уместен, только если ты хочешь создать что-то специфичное, а нас пока устроят и значения по умолчанию.
Изображение готово и можно приступать к его выводу на экран. Это желательно делать по событию системы WM_PAINT, которое происходит при необходимости прорисовать окно.
if msg=WM_PAINT then
begin
// Обнуляю структуру bltfx
ZeroMemory(@bltfx, SizeOf(bltfx));
//Заполняю её размер
bltfx.dwSize := sizeof(bltfx);
//Цвет заливки
bltfx.dwFillColor := 0;
//Закрашиваю фон
FPrimarySurface.Blt(nil, nil, nil, DDBLT_COLORFILL or DDBLT_WAIT, @bltfx);
//Вывожу на экран изображение
FPrimarySurface.BltFast (175, 75, FImageSurface, nil, DDBLTFAST_WAIT);
end;
Давай разберёмся с этим текстом. Первым делом я заполняю структуру bltfx, которая у меня объявлена в разделе var в виде типа TDDBLTFX. Это очень сложная структура, которая используется при вызове функции поверхности Blt, которая тоже в свою очередь достаточно сложная и имеет множество возможностей.
Сегодня мы познакомимся с одной из возможностей функции Blt - очистка экрана. Для этого в структуре bltfx надо заполнить только два значения - dwSize (размер структуры заполняется всегда) и dwFillColor (цвет заливки).
Когда структура готова, можно вызывать функцию (точнее сказать даже метод объекта поверхности) Blt. У метода есть 5 параметров:
1. Область приёмника, на которой надо рисовать (если nil, то рисуется на всей поверхности).
2. Поверхность источник (мы не копируем поверхность, а только закрашиваем, поэтому у меня nil).
3. Область источника, которая должна скопироваться (если nil, то копируется вся поверхность).
4. Флаги, которыми указывается, что должна сделать функция. Здесь я указываю DDBLT_COLORFILL - указывает на необходимость закрасить выбранную область указанным цветом. А так же DDBLT_WAIT - указывает на то, что если вывод сейчас невозможен, то подождать.
5. Структура, в которой хранятся необходимые параметры, в данном случае цвет заливки.
Среди параметров есть размеры источника и приёмника (откуда копируем и куда), а вот поверхность указывается только источника. Из-за этого может возникнуть вопрос: "А куда происходит копирование?". Всё очень просто, копироваться будет в ту поверхность, которая вызвала этот метод, в данном случае в первичную (FPrimarySurface.Blt).
После выполнения этой функции, наша первичная поверхность (FprimarySurface) очищается чёрным цветом. Теперь мы готовы теперь рисовать наше изображение из поверхности FImageSurface.
Если ты внимательно посмотрел на параметры метода Blt, то наверно заметил, что с помощью неё можно и копировать поверхности. Для этого в качестве второго параметра нужно только указать копируемую поверхность. Но я воспользовался более простой и более быстрой реализацией BltFast. Но это уже совершенно другая история, с которой мы разберёмся в следующий раз.