Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Листинг 7.4. Программа CIRCLES.С.
// ВКЛЮЧАЕМЫЕ ФАЙЛЫ //////////////////////////////////// #include < stdio.h> #include < math.h> #include < graph.h> #include < malloc.h> #include < memory.h> #include < string.h> // ОПРЕДЕЛЕНИЯ ////////////////////////////////// #define SCREEN_WIDTH (unsigned int)320 #define SCREEN_HEIGHT (unsigned int}200 // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ////////////////////////////////////// unsigned char far *video_buffer = (char far *)0xA0000000L; // указатель на видеобуфер unsigned char far *double_buffer = NULL; // ФУНКЦИИ ///////////////////////////////////////////////////////////// void Init_Double_Buffer(void) { double_buffer=(char far *)_fmalloc(SCREEN_WIDTH*SCREEN_HEIGHT+1); _fmemset(double_buffer, 0, SCREEK_WIDTH*SCREEN_HEIGHT+l); } // конец Init_Double_Buffer ///////////////////////////////////////////////////////////// void Show_Double_Buffer(char far *buffer) { // копирование дублирующего буфера в видеобуфер asm { push ds // сохранение регистра сегмента данных les di, video_buffer // вывод в видеобуфер... lds si, buffer //...из дублирующего буфера mov сх, 320*200/2 // количество перемещаемых слов cld rep movsw // перемещение pop ds // восстановление регистра сегмента данных } } // конец Show_Double_Buffer //////////////////////////////////////////////////////////// void Plot_Pixel_Fast_D(int x, int y, unsigned char color) { // вывод, пикселей в дублирующий буфер // используется тот факт, что 320*у = 256*у + 64*у = y< < 8 + у< < 6 double_buffer[((у< < 8) + (у< < 6)) + х] = color; } // конец Plot_Pixel_Fast_D //////////////////////////////////////////////////////////// void Circles(void) { // Эта функция рисует 1000 окружностей в дублирующем буфере // В реальной игре мы никогда не стали бы использовать столь // нелепый алгоритм рисования окружности, а применили бы что-нибудь // вроде таблицы выбора или другой эффективный способ. Здесь же нам // просто надо хоть что-то нарисовать в дублирующем буфере. int index, xo, yo, radius, x, у, color, ang; // рисуем 1000 окружностей в случайной позиции, // случайного цвета и размера for (index=0; index< 1000; index++) { // получаем параметры для следующей окружности хо = 20 + rand()%300; уо = 20 + rand()%180; radius = 1 + rand()%20; color = rand()%256i for (ang=0; ang< 360; ang++) { x = хо + cos(ang*3.14/180} * radius; у = уо + sin(ang*3.14/180} * radius; Plot_Pixel_Fast_D(x, y, (unsigned char)color}; } // конец внутреннего цикла } // конец внешнего цикла } // конец Circles // ОСНОВНАЯ ПРОГРАММА ////////////////////////////////////// void main(void) { // установка видеорежима 320х200х256 _setvideomode(_MRES256COLOR); // создание дублирующего буфера и очистка его Init_Double_Buffer(); _settextposition (0, 0); printf(" Drawing 1000 circles to double buffer. \nPlease wait..."); // построение окружностей в дублирующем буфере Circles (); printf(" Done, press any key."); //ожидание нажатия клавиши, прежде, чем перебросить // окружности на экран getch(); ShowDoubleBuffer(double_buffer); _settextposition(0, 0); printf(" That was quick. Hit any key to exit."); // ожидание нажатия клавиши getch (); // восстановление первоначального видеорежима _setvideomode(_DEFAULTMODE); } // конец функции main Конечно, эта программа не самая захватывающая в мире, но она дает четкое представление о том, что нас интересует. Обратите внимание, что функция вывода пикселя записывает его вместо видеобуфера в дублирующий буфер. Использование дублирующего буфера практически убирает мерцание экрана. Однако мы не можем гарантировать, что копирование данных в видеобуфер не начнется в момент обращения аппаратуры к экранной области памяти, и в этом случае на экране могут возникнуть искажения. В следующем разделе обсуждается то, как этого можно избежать. Использование сигнала вертикальной синхронизации Как вы знаете, образ на дисплее создается с помощью электронного луча, который рисует пиксель за пикселем до тех пор, пока не достигнет нижней границы экрана. Затем он возвращается по диагонали обратно к стартовой позиции и все повторяется с начала. В течение периода обратного вертикального хода луча память экрана недоступна аппаратной части, и если мы внесем изменения в видеобуфер, то не увидим их до следующего кадра. Нам это и нужно. Период обратного вертикального хода луча — самое подходящее время для обновления видеобуфера, так как в этот момент у аппаратной части нет к нему доступа. Этот период инициируется с помощью сигнала вертикальной синхронизации (vsync), посылаемого картой VGA. Мы должны определить этот импульс и использовать период, следующий сразу за ним, чтобы паша мультипликация в точности синхронизировалась с частотой обновления экрана. Чтобы достигнуть этого, мы должны разобраться, как определять начало периода обратного вертикального хода луча, и затем использовать наши знания для того, чтобы синхронизировать время обновления нашей графики с тем моментом, когда аппаратная часть временно забывает о существовании видеобуфера. К счастью, карты VGA имеют специальный регистр, который отражает обратный ход луча. Он называется регистром состояния VGA и его можно прочесть, обратившись к порту 0x3DA. Из 8 битов, содержащихся в этом регистре, нас интересует четвертый справа бит (3d): § Когда бит установлен в 1, происходит обратный вертикальный ход луча; § Когда бит сброшен в 0, происходит перерисовка экрана. Теперь мы можем, проверив этот бит, определить, происходит ли в данный момент обратный вертикальный ход луча. Однако если этот процесс идет, мы не можем точно узнать, находится ли он в середине, начале или конце цикла. Поэтому в таком случае мы должны дождаться его конца. Сделав это, мы будем уверены, что следующее изменение бита отметит начало цикла. Итак, мы можем написать подпрограмму, которая будет запрашивать статус бита и отслеживать начало процесса обратного вертикального хода луча. После этого она вернет управление основной программе, которая будет знать, что следующую 1/70-ю долю секунды для VGA (и 1/60-ю секунды для EGA), можно совершенно спокойно писать в видеобуфер. Я написал демонстрационную программу VSYNC.С, которая выполняет подобную синхронизацию и вычисляет количество периодов обратного вертикального хода луча как функцию от времени. Так как за секунду выполняется 70 циклов регенерации экрана, то после минутной работы программы результат должен оказаться равным 4200. Листинг 7.5. содержит исходный текст этой программы. Листинг 7.5. Подсчет количества циклов обратного вертикального хода луча (VSYNC.С). // ВКЛЮЧАЕМЫЕ ФАЙЛЫ //////////////////////////////////////////// #include < dos.h> #include < bios.h> #include < stdio.h> #include < math.h> #include < conio.h> #include < graph.h> // ОПРЕДЕЛЕНИЯ //////////////////////// #define VGA_INPUT_STATUS_1 0x3DA // регистр состояния VGA, // бит 3 - сигнал вертикальной синхронизации // 1 - происходит обратный вертикальный ход луча // 0 - нет обратного вертикального хода луча #define VGA_VSYNC_MASK 0х08 // маскировка неиспользуемых битов регистра // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ //////////////////// unsigned char far *video_buffer = (char far *)0xA0000000L; // указатель на видеопамять // ФУНКЦИИ ///////////////////////////////////////////////// void Wait_For_Vsync(void) { // эта функция ожидает начала обратного вертикального хода луча, а // если луч уже совершает обратный ход - ожидает следующего цикла while (_inp(VGA_INPUT_STATUS_1) & VGA_VSYNC_MASK) { // обратный вертикальный ход луча - ничего не делаем } // конец оператора while // ожидаем прихода сигнала vsync и возвращаем // управление вызвавшей функции while (! (_inp(VGA_INPUT_STATUS_l) & VGA_VSYNC_MASK)) { // ожидание начала обратного вертикального // хода луча - ничего не делаем } // конец оператора while // начался обратный вертикальный ход луча – // возвращаем управление вызвавшей функции } // конец Wait_For_Vsync // ОСНОВНАЯ ПРОГРАММА //////////////////////////////////////. void main(void) { long nuinber_vsyncs=0; // хранит количество циклов // обратного вертикального хода луча while(! kbhit()) { // ожидание vsync Wait_For_Vsync(}; // выводим графику или что-либо еще пока происходит // обратный вертикальный ход луча и мы имеем в своем // распоряжении 1/70 секунды! Обычно в это время выполняется // копирование дублирующего буфера в видеопамять // увеличивается количество циклов vsyncs number_vsyncs++; // вывод на экран _settextposition(0, 0); printf (" Number of vsync's = %ld ", number_vsyncs); } // конец оператора while } // конец функции main Существует миллион вещей, для которых можно использовать обратный вертикальный ход луча: § Мы можем использовать это время в качестве основы для некоторых процессов или событий; § Мы можем использовать это для того, чтобы игровой цикл выполнялся с частотой 70Гц на любой машине; § Наконец, мы можем использовать это для синхронизации процесса обновления графики с периодом обратного вертикального хода луча. Замечание Я хотел бы сказать вам еще и о том, чего вы делать не должны. Речь идет об использовании прерывания по обратному вертикальному ходу луча. Персональные...компьютеры имеют аппаратную поддержку вектора прерывания периода обратного вертикального хода луча. Однако такая поддержка не является стандартной для IBM-совместимых персональных компьютеров и не всегда может работать. К тому же, при этом используется INT2, который зарезервирован IBM для фатальных ошибок аппаратного обеспечения. Поэтому я не буду говорить об этом прерывании, так как оно зависит от марки компьютера и большинство программистов его не используют.
Перейдем к следующей интересной и захватывающей теме: мультипликации с помощью регистров цвета. Мультипликация с помощью регистров цвета Зачастую мы видим не то, что есть на самом деле. Поэтому многие эффекты и мультфильмы делаются не так, как вы могли бы подумать. Мультипликация с Помощью регистров цвета использует один из таких технических трюков, в результате которого можно очень просто получить то, что было бы чрезвычайно сложно или дорого при решении задачи «в лоб». Мультипликация с помощью регистров цвета основана на том факте, что образ — это набор цветов. Эти цвета, в свою очередь, определены значениями RGB в таблице выбора цвета. Например, представим, что мы имеем два идентичных объекта — два изображения дерева, каждое из которых состоит из пикселей восьми оттенков зеленого. Мы создаем два набора цветов, каждый из которых содержит одинаковые восемь оттенков. Затем, используя для каждого дерева свой набор, мы рисуем две картинки: то есть изображаем первое дерево, используя первый набор цветов, и второе — используя второй, как это показано на рисунке 7.5. Если мы передадим изображение этих деревьев на экран, то увидим два дерева, имеющих одинаковый вид, но нарисованных с применением различных Значений регистров цвета. Теперь перейдем к самому интересному: что если мы обнулим первый набор этих регистров? Первое дерево должно будет исчезнуть. На этом и основана мультипликация с использованием регистров цвета. Схематично это происходит так; § Каждое из интересующих нас изображений мы выполняем с помощью различных наборов регистров цвета с одинаковыми значениями RGB; § Затем делаем одно из изображений объекта невидимым, обнуляя соответствующие регистры цвета; § Потом заменяем регистры цвета «включенного» объекта значениями RGB «выключенного» изображения, оставляя другой банк регистров цвета обнуленным. Это создает ощущение движения объекта от одной позиции к другой, хотя на самом деле этого не происходит. И все это осуществляется изменением лишь нескольких регистров цвета. Такую технику можно использовать для создания объектов, изменяющих свой внешний вид, движущихся по экрану или исчезающих. Чтобы показать вам, как это может работать, я создал демонстрационную программу BIRDANI.C, которая рисует маленькую птичку, летающую по кругу, но на самом деле неподвижную. Я нарисовал птичку в графическом редакторе, использовав при этом 13 оттенков серого цвета. Затем, с помощью программного обеспечения для работы с PCX, описанного в пятой главе, «Секреты VGA-карт», загрузил это изображение. После чего обнулил все цветовые регистры, составляющие эти 13 оттенков серого цвета. Затем я поместил серый в первый цветовой регистр, потом обнулил его, поместив серый в следующий цветовой регистр и так далее- В результате все это выглядит так, как будто птица взмахивает крыльями и летит по кругу. Листинг 7.6 содержит текст этой программы. Замечание Отныне в этой книге я буду использовать перекомпилированные библиотеки, поэтому вы больше не увидите старых функций в листингах программ. Текст большинства из этих функций я поместил в файлы с именем GRAPHO.C. Соответственно, файл заголовка будет называться GRAPHO.H. Чтобы функции GRAPH0.C были доступны для наших программ мы должны сначала скомпилировать сам GRAPH0.C (только убедитесь, что вы используете модель памяти MEDIUM). Затем во время липковки скомпонуйте получившийся объектный файл с той программой, с которой вы сейчас работаете. Не забудьте включить в вашу программу файл заголовка GRAPH0.H, иначе функции из GRAPH0.C будут иедоступны. Вы могли бы также создать библиотеку графических функций, используя программу Microsoft's Library Manager, LIB- Правда у нас только один файл с исходным текстом библиотечных функций, поэтому данная программа для нас излишне мощна. Однако решать, конечно же, вам. Короче говоря, если код из GRAPH0.C, GRAPH0.H и функция установки видеорежима каким-либо образом включены в программы этой главы, то они должны работать. Листинг 7.6. Мультипликация с помощью изменения регистров цвета (BIRDANI.C). // ВКЛЮЧАЕМЫЕ ФАЙЛЫ //////////////////////////////////// #include < io.h> #include < conio.h> #include < stdio.h> #include < stdlib.h> #include < dos.h> #include < bios.h> #include < fcntl.h> #include < memory.h> #include < malloc.h> #include < math.h> #include < string.h> #include " graphl.h" // включаем нашу графическую библиотеку // ОПРЕДЕЛЕНИЯ ///////////////////////////////////////////// #define BIRD_START_COLOR_REG 16 #define BIRD_END_COLOR_REG 28 // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ /////////////////////////////////// unsigned int far *clock = (unsigned int far *)Ox0000046C; // указатель на внутренний таймер 18.2 " тик" /с pcx_picture birds; // ФУНКЦИИ ///////////////////////////////////////////////// void Timer(int clicks) { // эта функция использует внутренний таймер с частотой 18.2 " тик" /с. // 32-битовое значение этого таймера находится по адресу 0000: 046Сh unsigned int now; // получаем текущее время now = *clock; // Ожидаем до истечения указанного периода времени. // Заметьте, что каждый " тик" имеет длительность-примерно в 55 мс while(abs(*clock - now) < clicks){} } // конец Timer //////////////////////////////////////////////////////////// void Animate_Birds(void) { // эта функция перемещает птичку, изображенную 13-ю различными // цветами, последовательно включая один цвет и выключая остальные RGB_color color_l, со1оr_2; int index; // очистка каждого из цветовых регистров, // используемых в изображении птички color_l.red = 0; color_l.green = 0; color_l.blue = 0; color_2.red = 0; color_2.green = 63; color_2.blue = 0; // очистка всех цветов for (index=BIRD_START_COLOR_REG; index< =BIRD_END_COLOR_REG; index++) { Set_Palette_Register(index, (RGB_color_ptr)& color_l); } // конец цикла for // делаем первую птичку зеленой и затем // последовательно меняем цвета Set_Palette_Register(BIRD_START_COLOR_REG, (RGB_color_ptr)& color_2); // мультипликация цветами while(! kbhit()) { // меняем цвета Get_Palette_Register(BIRD_END_COLOR_REG, (RGB_color_ptr)& color_l); for (index=BIRD_END_COLOR_REG-l; index> =BIRD_START_COLOR_REG; index—) { Get_Palette_Register(index, (RGB_color_ptr)& color_2); Set_Palette_Register(index+l, (RGB_color_ptr)& color_2); } // конец цикла for Set_Palette_Register(BIRD_START_COLOR_REG, (RGB_color_ptr)& color__l); // небольшая пауза Timer(3); ) // конец оператора while } // конец Animate_Birds // ОСНОВНАЯ ПРОГРАММА ////////////////////////////////////// void main(void) { int index, done=0; // установка видеорежима 320х200х256 Set_Mode(VGA256); // инициализация PCX-файла, который содержит изображение птички PCX_lnit((pcx_picture_ptr)& birds); // загрузка файла PCX PCX__Load(" birds.pcx", (pcx_picture_ptr)& birds, 1); PCX_Show_Buffer((pcx_picture_ptr)& birds); PCX_Delete((pcx_picture_ptr)& birds); _settextposition(0, 0); printf(" Hit any key to see animation." }; getch() _settextposition(0, 0); print£ (" Hit any key to Exit. "); Animate_Birds(); // возврат в текстовый режим Set_Mode(TEXT_MODE); } // конец функции xnain Конечно, программа BIRDANI.C элементарна. Однако с помощью этой техники можно получить потрясающие эффекты. Один из них мы и обсудим в следующем разделе. Освещение ваших игр Освещение, тени и другие эффекты традиционно выполняются с помощью сложной математики, основанной на физических моделях. При построении этих моделей принимаются во внимание такие факторы как угол падения, расстояниеИ до источника света, тип источника света, тип материала, плотность атмосферы и т. д. (более подробно об этом рассказывается в шестой главе, «Третье измерение». Не удивительно, что для получения фотографически точных изображений тратятся часы или даже дни работы суперкомпьютеров типа Cray ХМР! Уравнения, которые надо для этого решить, да и количество операций, которое необходимо выполнить — все это слишком грандиозно для нашего маленького персонального компьютера. Однако мы попытаемся, используя смекалку и упрощенные модели освещения, реализовать несколько «достаточно хороших» для наших потребностей эффектов. Потрясающие световые эффекты знаменитой игры DOOM впечатляют, по, тем не менее, в них нет ничего невозможного. Как говорится, глаза боятся, а руки делают. В качестве примера представим, что мы нарисовали комнату, используя регистры цвета от 1 до 32. Эти регистры содержат оттенки серого (вместе с оттенками красного, которыми изображены ругательства, написанные на стенах). Что произойдет, если мы постепенно увеличим значение RGB в каждом из этих регистров? Вы увидите, что комната стала светлее. Если же вы уменьшите каждое значение RGB во всех регистрах цвета, в комнате станет темнее. Конечно, вы должны быть уверены в том, что сохранится процентное соотношение компонентов. Иначе цвет может измениться вместе с интенсивностью. Другими словами, вы должны увеличивать или уменьшать величины, составляющих каждого регистра цвета пропорционально их исходному значению. Следовательно, чтобы уменьшить интенсивность определенного регистра цвета, скажем, 26, вы должны вначале выяснить, в каком процентном соотношении он содержит красный, зеленый и синий цвета. Затем вам надо вычислить значение, которое должно быть добавлено или отнято от каждой составляющей для того, чтобы исходный цветовой баланс не изменился. Для примера, давайте будем уменьшать каждое значение RGB на 5 процентов за каждую итерацию. Чтобы сделать это, мы должны вычислить 5 процентов для каждого из значений RGB и вычесть их из каждого компонента. Таблица 7.2 показывает результаты этой процедуры за небольшой промежуток времени для некоторого произвольного цвета RGB. Таблица 7.2. Уменьшение интенсивности цвета при сохранении цветового баланса. (Цветовой баланс с использованием красного цвета в качестве эталона)
Как вы можете видеть, если пошагово уменьшать каждую из составляющих на пять процентов, цветовой баланс будет сохраняться. Конечно, вычисление пяти процентов каждого значения при каждой итерации слишком дорого. Чтобы избежать этого, мы можем попробовать просто вычитать постоянное значение из каждого компонента регистра цвета, игнорируя цветовой баланс. Однако когда интенсивность цвета существенно увеличится или уменьшится, станет явно заметным и изменение оттенка исходного цвета. Компромиссным вариантом может стать подсчет трех констант на основании исходного цветового баланса, а затем их прибавление или вычитание. Это уменьшит ошибку, вносимую в цветовой баланс. В нашем случае мы можем использовать 5 для красного компонента, 0.66х5 - для зеленого и 0.3х5 - для синего. Это дает приемлемые результаты. Используя этот метод, мы получим следующие значения RGB после первой итерации:
Ошибка» возникающая при расчете таким методом, пренебрежительно мала, и если уменьшение или увеличение компонентов RGB будет производиться всего несколько раз, изменение тона исходного цвета будет практически незаметным. Связь мультипликации с контекстом Как это ни удивительно, но на связь мультипликации с контекстом крайне редко обращают внимание в компьютерных играх. Контекст означает «связь с окружением или основой». В играх контекстом являются разнообразные дейст вия по ходу игры. Что бы ни делал персонаж в игре: умирал, прыгал, стрелял или еще что-нибудь, -- все это часть контекста игры. Мультипликация и различные эффекты, которые связаны с контекстом, придают игре дополнительную реалистичность и размах. Попробую пояснить на примере, что я имею в виду. Представьте, что мы имеем игру, в которой герой может гулять, прыгать, бегать и стрелять. Независимо от того, перепрыгивает ли он через паука или озеро, во время прыжка мультипликация обычно бывает всегда одна и та же. Но что если для каждого случая, или соответствующего контекста мы сделаем отдельный мультик? Например, когда герой перепрыгивает просто через лужу, то он не совершает ничего кроме собственно прыжка. А вот когда ему приходится перепрыгивать через паука, то он может, например, издать пронзительный крик ужаса и показать изумительную сноровку в прыжках. (И действительно, приземление на большого склизкого паука, возможно, не самый лучший опыт для маленького героя, которого заманили в виртуальный компьютерный мир.) Итак, контекстнозависимая мультипликация, используя набор соответствующих вариантов движений, учитывает тем самым окружение и внешние обстоятельства в каждый момент игрового действия, что дополнительно разнообразит зрительное восприятие игры. «Animotion» Animation (animation ( мультипликация )+ motion (движение)) — это придуманное мною слово, которое должно войти в словарь терминов компьютерных игр. (Дальше я тоже буду выдумывать слова, но они уже не будут так хороши как это.) Оно описывает точное слияние анимации (мультипликации) и движения. В игре Tombstone четвертой главы, " Механизмы двухмерной графики", маленький ковбой гуляет по улице. В действительности это больше похоже на то, что он одновременно ковыляет и совершает дикие прыжки (если такое вообще можно представить). Вся проблема заключается в принципе его движения, которое не было синхронизировано с мультипликационными кадрами. Многие объекты, в компьютерных играх имеют постоянную скорость. Такие игровые объекты как ракета и прочие летающие предметы — хороший тому пример, Однако к имеющим под собой опору (например, землю) объектам, которые должны выглядеть реально, нужно применять совсем другой подход. «Animotion» абсолютно необходим, если вы хотите, чтобы движение -ходьба, бег или прыжки — выглядели реалистично. Иначе мультипликационные объекты выглядят неестественно. Мы не можем просто в цикле менять мульти пликационный кадр и одновременно передвигать объект на произвольное расстояние. Мы должны рассмотреть каждый кадр и определить, на сколько в действительности следует перемещать объект для этого кадра. Затем мы создаем таблицу выбора для движения, в которой в качестве индекса используем номера кадров. В этой таблице будут содержаться величины перемещений, которые и будут использоваться при оживлении картинки. На рисунке 7.6 изображены мультипликационные кадры движения худого человечка, которого-мы назовем «человечек-палка». (У «человечка-палки» серьезные проблемы со зрением — он имеет только один глаз.) Для движения человечка у нас будет 12 мультипликационных кадров. Для каждого из кадров я прикинул и задал величину перемещения, при котором движение становится действительно похожим на прогулку. Немного помучавшись, я получил для каждого мультипликационного кадра значения, приведенные в таблице 7.3. Таблица 7.3. Таблица перемещений.
Чтобы показать на примере, насколько «animotion» превосходит по качеству постоянное движение, я создал программу в которой игрок, нажимая на клавишу пробел, может выбирать между ними. Нашим «подопытным кроликом» снова будет беззащитный «человечек-палка». Мы собираемся поместить его в действительно ужасный городской квартал и при этом у него не будет ничего, кроме пары ног, пары рук и пары глаз. (Ой, извините! Глаз-то всего один.) Теперь посмотрите, как он разгуливает по экрану. Затем нажмите клавишу пробела, чтобы перейти с постоянного движения к «animotion». Вы увидите большую разницу. Текст программы приведен в Листинге 7.7, она также использует функции из GRAPH0.C, поэтому не забудьте скомпоновать программу с этим модулем.
|