Turbo Pascal

Программы
Софт
Библиотека
Статьи
Гостевая
Авторы

 

;
Основы спрайтовой анимации

Сергей Андрианов

11.09.2001

 

Довольно часто в конференциях Fidonet, посвященных программированию и разработке компьютерных игр, встречаются вопросы по спрайтовой анимации. Начинающие программисты тоже, наверное, хотели бы почитать что-нибудь на эту тему, а еще лучше — просто посмотреть, как действует подобная простенькая программа. Может ли быть что-то более интересное для школьника, чем самому написать компьютерную игру.На уроках информатики или факультативных занятиях, где рассматриваемый материал выходит за рамки школьной программы, учащимся, как правило, предлагали работать с процедурами модуля Graph. Для учебных целей или создания статической графики это, возможно, и неплохой вариант, но вот для анимированной он явно не годится из-за экранного режима и набора процедур модуля Graph. Для анимации 16-цветные режимы не слишком подходят, так как требуют довольно сложной структуры видеопамяти, а также постоянного обращения к регистрам видеоадаптера, что значительно замедляет работу. Поэтому для нашего примера мы не будем пользоваться ни модулем Graph, ни стандартными для Borland Pascal видеорежимами.Итак, примем режим 256 цветов и разрешение 320x200 точек. Этот стандартный видеорежим — самый простой с точки зрения организации видеопамяти. В данном случае она представляет собой большой байтовый массив размером 320x200 точек, где каждый байт соответствует определенной точке. Таким образом, точка с координатами (x, y) находится в видеопамяти со смещением x + 320y. Для удобства опишем экран как двумерный массив, любой байт которого может принимать 256 различных значений, так что на экране одновременно может появляться до 256 цветов, причем всякому значению байта соответствует свой цвет, определяемый регистрами палитры. Значит, записывая байт в установленное место видеопамяти, мы «зажигаем» точку.Так как стандартные драйверы экрана не поддерживают выбранный нами режим, установим его напрямую через VideoBIOS. Кроме того, не подключены графические драйверы, а потому нет и стандартных функций, позволяющих рисовать точку, линию, круг, прямоугольник, — правда, точку можно изобразить через тот же VideoBIOS, но делается это очень медленно. Впрочем, в спрайтовой анимации такие функции и не нужны, жаль только, что пропадают те из них, которые обеспечивают вывод текста. В принципе можно воспользоваться и поддерживающими 256 цветов библиотеками (к сожалению, в стандартную поставку Вorland/Turbo Рascal они не входят), однако они годятся лишь для вывода текста или расчерчивания рамок. В конце концов, вывод текста — отдельный разговор, и к нему мы еще вернемся.

Теперь о спрайтах

Встречаются и такие тексты программ, где рисунки спрайтов вводятся в массив числовых констант прямо с клавиатуры. Один курсор мыши создать так, конечно, можно, а вот спрайт размером, скажем, 128x128 точек — весьма проблематично. Таким образом, для изготовления спрайтов следует пользоваться не текстовым, а графическим редактором. Самое простое — изучить формат BMP-файлов и «читать» спрайты из них. Но сперва давайте побыстрее получим первый результат. Для этого поступим следующим образом: возьмем редактор Paint, зададим размер изображения 20x20 точек (пункты «Рисунок•Атрибуты») и нарисуем что-нибудь на белом фоне, а потом в файл с именем sprt01.bmp запишем это изображение, причем обязательно в режиме 256 цветов, иначе это будет неправильно воспринято нашей программой. Первые 1078 байт полученного файла займет заголовок, содержащий информацию о размерах изображения, используемых цветах и т. д. Сначала размер изображения мы зададим в программе жестко, а цвета будем игнорировать.

Вывод на экран

Перед тем как выводить спрайт, надо сохранить находящееся под ним изображение, чтобы фон не испортился, когда спрайт «уйдет». Поэтому считаем информацию с того места экрана, куда будет помещен спрайт. Непосредственно перед выводом спрайта восстановим изображение, а затем снова сохраним фон из нового места, куда будет помещен спрайт.

Реальные спрайты редко имеют прямоугольную форму, поэтому выводить следует только отдельные точки считанного изображения, включив для этого понятие «прозрачный цвет». В данном случае мы выбираем белый цвет с кодом 255 ($FF). При выводе спрайта рисуются лишь те точки, код которых не равен 255 (для «прозрачного цвета» можно взять любое значение: 0, 5, 31 — как вам будет удобно).

Чтобы скорость работы программы не зависела от производительности компьютера, применим синхронизацию с обратным ходом кадровой развертки. Она также избавит и от мерцания. В более сложных программах эти две функции реализуются с помощью различных механизмов независимо друг от друга.

Текст созданной нами программы см. в листинге. Чтобы описать спрайт, определяем запись SpriteType. Для прямого доступа к видеопамяти используем массив Mem, а для вызова функции VideoBIOS — процедуру intr. Итак, что же получилось?

  • Цвета вышли не такие, какие нам хотелось бы. Что делать? По умолчанию в среде DOS и в Windows установлены различные палитры. Чтобы цвета были такими, какими нужно, следует прочитать заголовок BMP-файла, взять из него данные по цветам и записать их в регистры палитры.
  • Изображение спрайта получилось перевернутым. Так уж устроено, что на экране ось Y идет сверху вниз, а в BMP-файле — снизу вверх. Думаю, исправить это не составит большого труда.
  • Если сделать спрайт большего размера, то возникает мелькание в верхней части экрана. Чтение данных из видеопамяти происходит долго, поэтому этого лучше просто избегать. (Подробнее обо всем см. в следующих номерах журнала.)

Кстати, попробуйте отключить (закомментировать) ожидание обратного хода и посмотрите, что из этого получится.

На приведенном здесь примере показано, как перемещать спрайт по экрану. Кроме того, чередуя несколько фаз (кадров) изображения, можно заставить спрайт «махать руками», «топать ногами» и т. п., даже находясь в одном месте экрана. Для этого нужно либо сопоставить файл каждому кадру, либо поместить все кадры в один файл (последнее предпочтительнее, но требует большей работы), а затем по очереди выводить изображение спрайта из того или иного кадра на экран (в буфер).


Краткий глоссарий

Видеопамять — память, аналогичная оперативной, но только установленная в видеоконтроллере. В нее записывается изображение, которое мы видим на мониторе. Доступ к данной памяти требует гораздо большего времени, чем к ОЗУ.

Видеорежимы. Есть несколько режимов работы видеоконтроллера, различающихся способом отображения на экран содержимого видеопамяти. Они, в свою очередь, подразделяются на текстовые и графические. При текстовых экран разбивается на знакоместа (обычно размером 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.

 

 

Hosted by uCoz