DirectX. Приёмы работы с прямым доступом к памяти:
В журнале ][ я описал теорию прямого доступа к памяти, да и на этом сайте ты найдёшь статью по этой теме. Здесь же я хочу показать на практике некоторые приёмы анимации с помощью этого нехитрого приёма.
Возьмём пример из предыдущего номера ][, статьи "Первая анимация" и дополним его графическими эффектами. Открой тот пример в Delphi и найди в процедуре WindowProc код вывода фонового изображения на экран:
//Обнуляю структуру desk
ZeroMemory (@desc, SizeOf(desc));
desc.dwSize := SizeOf(desc);
//Блокирую поверхность
hRet:=FImageSurface.Lock(nil, desc, DDLOCK_WAIT, 0);
//Засыпаем поверхность белым цветом
for i:=0 to 10 do
PWord (Integer(desc.lpSurface) + random(450) * desc.lPitch + random(450)*2)^ := $FFFF;
//Разблокируем
FImageSurface.Unlock(nil);
//А вот теперь можно выводить на экран картинку
FBackgroundSurface.BltFast (175, 75, FImageSurface, nil, DDBLTFAST_WAIT;
В самом начале я обнуляю структуру desk, которая будет использоваться при блокировании поверхности. После этого я блокирую поверхность, которая хранит изображение фона, чтобы получить к ней прямой доступ.
Как только поверхность заблокирована, к ней можно спокойно обращаться прямым доступом. Я запускаю цикл из 10 шагов, на каждом шаге которого вывожу в случайной точке поверхности точку белого цвета. Таким образом я засыпаю экран белыми точками с максимально возможной скоростью.
Результат работы программы ты можешь увидеть на скрине выше, а исходник находиться в директории Source1.
Первый примерчик был достаточно простой, но малоэффективный на практике. Давай познакомимся с чем-нибудь более стоящим. Опять возьмём пример из предыдущего номера ][, статьи "Первая анимация" и дополним его более продвинутыми графическими эффектами.
Перед разделом var добавь раздел type и добавь туда объявление следующего типа:
{$R *.RES}
type
TByteArray = Array [0..900, 0..450] of Byte;
var
Мы тут просто объявили новый тип TByteArray как двухмерный массив из чисел типа Byte.
В разделе var объявим две новые переменные:
AnimePict : TByteArray;
AnimationState:Integer;
Первая переменная имеет тип TByteArray - массив целых чисел. Мы могли и не объявлять нового типа, а просто написать так:
AnimePict : Array [0..900, 0..450] of Byte;
результат был бы один и тот же, но я всё же люблю иногда объявить новый тип, чтобы было легче после этого работать с объявлениями.
Теперь переходим к кодингу. Перед запуском бесконечного цикла. Здесь добавь две строки:
AnimationState:=10;
GetAnimationBmp;
В первой мы задаём начальное значение переменной AnimationState. Эта переменная будет использоваться для хранения состояния анимации.
После этого я вызываю процедуру GetAnimationBmp, но она пока не существует, поэтому давай её напишем:
procedure GetAnimationBmp;
var
hRet:HRESULT;
desc:TDDSURFACEDESC2;
i, J:Integer;
begin
FillChar (desc, SizeOf(desc), 0);
desc.dwSize := SizeOf(desc);
hRet:=FImageSurface.Lock(nil, desc, DDLOCK_WAIT, 0);
if hRet <> DD_OK then exit;
for i := 0 to 900 do
for j := 0 to 450 do
begin
//Копирую содержимое текущей точки поверхности
AnimePict [i, j] := PBYTE (Integer (desc.lpSurface) +j * desc.lPitch + (i))^;
//Обнуляю содержимое текущей точки поверхности
PBYTE (Integer (desc.lpSurface) +j * desc.lPitch + (i))^:=$00;
end;
FImageSurface.Unlock(nil);
end;
Здесь я получаю уже знакомым способом прямой доступ к поверхности FImageSurface, в которой храниться изображение фона. Получив прямой доступ, я запускаю цикл от 0 до 900 и от 0 до 450 внутри которого копирую содержимое поверхности (картинку фона) в массив AnimePict и тут же обнуляю содержимое поверхности. Диапазон цикла выбран не случайно. Переменная i изменяется от 0 до 900 и будет показывать ширину картинки. Так как у нас 16 битный цвет, то для хранения каждой точки изображения используется 2 байта, а значит в памяти ширина картинки будет равна ширине изображения (у меня это 450 пикселей) умноженное на 2. В высоту картинка будет занимать такое же количество памяти, что и реальная высота - 450.
Скопировав всё необходимое в свой массив, поверхность можно разблокировать.
Теперь перейдём к тому месту, где происходит отображение поверхности (туда же, где мы корректировали в предыдущем примере) и напишем там следующее:
FillChar (desc, SizeOf(desc), 0);
desc.dwSize := SizeOf(desc);
hRet:=FImageSurface.Lock(nil, desc, DDLOCK_WAIT, 0);
if AnimationState<5000 then
for index:=0 to AnimationState do
begin
i:=Random(450)*2;
j:=Random(450);
PByte (Integer (desc.lpSurface) + (j) * desc.lPitch+i)^ := AnimePict [i, j];
PByte (Integer (desc.lpSurface) + (j) * desc.lPitch+i+ 1)^ := AnimePict [i+1, j];
end
else
for index:=5000 to AnimationState do
begin
i:=Random(450)*2;
j:=Random(450);
PByte (Integer (desc.lpSurface) + (j) * desc.lPitch+i)^ := $00;
PByte (Integer (desc.lpSurface) + (j) * desc.lPitch+i+ 1)^ := $00;
end;
FImageSurface.Unlock(nil);
if AnimationState>10000 then
AnimationState:=10;
AnimationState:=AnimationState+10;
FBackgroundSurface.BltFast (175, 75, FImageSurface, nil, DDBLTFAST_WAIT);
Здесь опять блокируем поверхность фона FImageSurface. Если AnimationState меньше 5000, то запускаем цикл от 0 до значения в переменной AnimationState. Внутри цикла случайным образом выбираем координаты точки и копируем из массива AnimePict (где у нас храниться копия поверхности) в память поверхности. Так как при создании копии поверхности мы её обнулили, то на первом этапе она будет пустой (залита чёрным цветом) и этим кодом мы будем случайными точками засыпать поверхность точками от изображения.
Если AnimationState больше 5000, то запускается цикл от 5000 до значения в переменной AnimationState. Внутри цикла также выбирается случайным образом любая точка и её мы обнуляем в поверхности картинки.
Всё, поверхность картинки можно разблокировать и выводить на экран. Если ты запустишь эту прогу, то увидишь эффект плавного появления по точкам изображения фона.
На этом наверно уже можно и закруглиться. На сегодня эффектов больше нет. Увидимся в следующем номере.