Сергей Андрианов 11.09.2001
Встречаются и такие тексты программ, где рисунки спрайтов вводятся в массив числовых констант прямо с клавиатуры. Один курсор мыши создать так, конечно, можно, а вот спрайт размером, скажем, 128x128 точек — весьма проблематично. Таким образом, для изготовления спрайтов следует пользоваться не текстовым, а графическим редактором. Самое простое — изучить формат BMP-файлов и «читать» спрайты из них. Но сперва давайте побыстрее получим первый результат. Для этого поступим следующим образом: возьмем редактор Paint, зададим размер изображения 20x20 точек (пункты «Рисунок•Атрибуты») и нарисуем что-нибудь на белом фоне, а потом в файл с именем sprt01.bmp запишем это изображение, причем обязательно в режиме 256 цветов, иначе это будет неправильно воспринято нашей программой. Первые 1078 байт полученного файла займет заголовок, содержащий информацию о размерах изображения, используемых цветах и т. д. Сначала размер изображения мы зададим в программе жестко, а цвета будем игнорировать.
Перед тем как выводить спрайт, надо сохранить находящееся под ним изображение, чтобы фон не испортился, когда спрайт «уйдет». Поэтому считаем информацию с того места экрана, куда будет помещен спрайт. Непосредственно перед выводом спрайта восстановим изображение, а затем снова сохраним фон из нового места, куда будет помещен спрайт.
Реальные спрайты редко имеют прямоугольную форму, поэтому выводить следует только отдельные точки считанного изображения, включив для этого понятие «прозрачный цвет». В данном случае мы выбираем белый цвет с кодом 255 ($FF). При выводе спрайта рисуются лишь те точки, код которых не равен 255 (для «прозрачного цвета» можно взять любое значение: 0, 5, 31 — как вам будет удобно).
Чтобы скорость работы программы не зависела от производительности компьютера, применим синхронизацию с обратным ходом кадровой развертки. Она также избавит и от мерцания. В более сложных программах эти две функции реализуются с помощью различных механизмов независимо друг от друга.
Текст созданной нами программы см. в листинге. Чтобы описать спрайт, определяем запись SpriteType. Для прямого доступа к видеопамяти используем массив Mem, а для вызова функции VideoBIOS — процедуру intr. Итак, что же получилось?
Кстати, попробуйте отключить (закомментировать) ожидание обратного хода и посмотрите, что из этого получится.
На приведенном здесь примере показано, как перемещать спрайт по экрану. Кроме того, чередуя несколько фаз (кадров) изображения, можно заставить спрайт «махать руками», «топать ногами» и т. п., даже находясь в одном месте экрана. Для этого нужно либо сопоставить файл каждому кадру, либо поместить все кадры в один файл (последнее предпочтительнее, но требует большей работы), а затем по очереди выводить изображение спрайта из того или иного кадра на экран (в буфер).
Видеопамять — память, аналогичная оперативной, но только установленная в видеоконтроллере. В нее записывается изображение, которое мы видим на мониторе. Доступ к данной памяти требует гораздо большего времени, чем к ОЗУ.
Видеорежимы. Есть несколько режимов работы видеоконтроллера, различающихся способом отображения на экран содержимого видеопамяти. Они, в свою очередь, подразделяются на текстовые и графические. При текстовых экран разбивается на знакоместа (обычно размером 9x16 точек), а в видеопамять вписываются коды символов. По коду видеоконтроллер берет и выводит на экран готовую «картинку» символа. Нарисовать произвольное изображение в текстовом режиме нельзя, и потому приходится довольствоваться лишь заранее определенным набором символов. При графических режимах можно управлять каждой точкой экрана, но для этого требуется гораздо больший объем видеопамяти, да и работа займет много времени.
Графические режимы могут различаться разрешением (320x200, 640x480, 800x600 точек и др.) и числом цветов (2, 4, 16, 256, 65 тыс., 16 млн.).
Обратный ход кадровой развертки. При отображении картинки на мониторе луч в ЭЛТ последовательно пробегает по всем строчкам сверху вниз, а затем выключается и возвращается в начало экрана. Данный процесс и называется обратным ходом кадровой развертки; он происходит периодически (70 раз в секунду в режиме 320x200 точек и 256 цветов). В это время изображение на дисплее не формируется, и потому в видеопамяти можно делать изменения, не опасаясь, что они приведут к появлению помех на экране.
Спрайт (sprite) — небольшое изображение, свободно перемещающееся по монитору. В первоначальном смысле слова этот термин применялся только для аппаратно выводимых изображений. Собственно, лишь один настоящий спрайт можно встретить на IBM PC — аппаратный курсор мыши. При архитектуре х86 под спрайтом принято понимать программно выводимое изображение, которое может иметь сложную форму и передвигаться поверх фона, не затирая его.
VideoBIOS. BIOS — базовая система ввода-вывода, обеспечивающая проведение элементарных операций по обслуживанию периферии ПК: дисковых накопителей, клавиатуры, монитора и др. Она представляет собой микросхему ПЗУ, находящуюся на системной плате компьютера. VideoBIOS — расположенная на видеоконтроллере часть BIOS, выполняющая базовые функции по работе с монитором.
program Sprite; {простейшая демонстрация работы со спрайтами} uses dos, {для работы с прерыванием VideoBIOS} crt; {для работы с клавиатурой} const Xsize = 20; {размеры спрайта, точек} Ysize = 20; TransparentColor = $FF; {"прозрачный" цвет} type SpriteArrayType = array[0..Ysize-1,0..Xsize-1]of byte; {массив равный по размеру спрайту} SpriteType = record x,y : word; {текущие координаты спрайта} dx,dy : integer; {приращения координат спрайта} Img : ^SpriteArrayType; {для массива с изображением спрайта} Back : ^SpriteArrayType; {для массива, хранящего фон под спрайтом} end; ScreenType = array[0..199,0..319]of byte; {для экрана} var Sprt : SpriteType; {спрайт} r : registers; {для вызова прерывания BIOS} Scr : ^ScreenType; {экран} procedure GetBuffer; {сохранение фона под спрайтом в буфере} var i,j : word; {переменные цикла} begin for j := 0 to Ysize-1 do for i := 0 to Xsize-1 do with Sprt do Back^[j,i] := Scr^[j+y,i+x]; end; procedure PutBuffer; {восстановление фона} var i,j : word; {переменные цикла} begin for j := 0 to Ysize-1 do for i := 0 to Xsize-1 do with Sprt do Scr^[j+y,i+x] := Back^[j,i]; end; procedure PutSprite; {вывод спрайта на экран} var i,j : word; {переменные цикла} begin for j := 0 to Ysize-1 do for i := 0 to Xsize-1 do with Sprt do if Img^[j,i] <> TransparentColor then {ставим только точки,} {цвет которых отличается от "прозрачного"} Scr^[j+y,i+x] := Img^[j,i]; end; procedure PutBackground; {создание фона на экране} var i,j : word; {переменные цикла} begin for j := 0 to 199 do for i := 0 to 319 do Scr^[j,i] := lo(i+j*8); end; procedure CreateSprite(s:string; x,y,dx,dy:integer); {"создание" спрайта} var f : file; {файл с изображением спрайта} begin getmem(Sprt.Img,sizeof(SpriteArrayType)); {выделяем память для спрайта} getmem(Sprt.Back,sizeof(SpriteArrayType)); {выделяем память для буфера} assign(f,s); {bmp-файл размерами Xsize на Ysize} reset(f,1); {открываем файл со спрайтом} seek(f,1078); {пропускаем заголовок} blockread(f,Sprt.Img^,Xsize*Ysize); {читаем изображение} close(f); Sprt.x := x; Sprt.y := y; { задаем начальные значения } Sprt.dx := dx; { координат и приращений } Sprt.dy := dy; end; procedure DestroySprite; {"уничтожение" спрайта} begin { возвращаем память } freemem(Sprt.Back,sizeof(SpriteArrayType)); freemem(Sprt.Img,sizeof(SpriteArrayType)); end; procedure CalcSpritePosition; {вычисление координат} begin {спрайта и их приращений} {по достижении границы экрана делаем,} { чтобы спрайт "отразился" от нее} with Sprt do begin if (x + Xsize + dx) >= 319 then dx := -dx; {вычисляем новые приращения} if (x + dx) <= 0 then dx := -dx; {реализующие "отражение"} if (y + Ysize + dy) >= 199 then dy := -dy; {спрайта от стенок} if (y + dy) <= 0 then dy := -dy; x := x+dx; { вычисляем новые } y := y+dy; { координаты спрайта } end; end; begin CreateSprite('sprt01.bmp',0,0,1,1); r.ax := $13; { устанавливаем режим } intr($10,r); { 320х200х256 цветов } Scr := ptr(SegA000,0); {адрес видеопамяти} PutBackGround; {рисуем фон} GetBuffer; {сохраняем фон под спрайтом} PutSprite; {и рисуем на его месте спрайт} repeat {теперь спрайт будет двигаться по экрану} {до тех пор, пока мы не нажмем на клавишу} PutBuffer; {восстанавливаем фон} CalcSpritePosition; GetBuffer; {сохраняем фон} PutSprite; {рисуем спрайт} while (port[$3da] and 8) = 0 do; {ожидаем обратный ход луча кадровой развертки} until keypressed; readkey; {чистим буфер клавиатуры} r.ax := $3; intr($10,r); {возвращаемся в текстовый режим} DestroySprite; end.