Доброго времени суток! По просьбе народа эта часть целиком и полностью будет посвящена анимации. Я пройдусь по трем методам ее создания на ПК, но подробнее опишу первый, который будет продемонстрирован в аттаче. Несмотря на то, что анимация редко используется в демокодинге, она все же используется в программировании игр. В этой части я не поскуплюсь ассемблерного кода.:) Включено же будет волшебно быстрый и безупречный PutPixel, ассемблерная смена экранов, ассемблерное помещение изображения, частичное транспонирование и еще несколько. Я детально объясню, как это работает. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ? Принципы анимации. Я уверен, что вы разок-другой видели компьютерную игру с анимацией. Есть несколько вещей, чтобы сделать анимацию как можно более реалистичной. Во-первых должно быть движение; предпочтительно использовать разные кадры (например, у идущего человека лучше сделать разные кадры у рук и ног в различных положениях). Во-вторых фон должен всегда оставаться целым. ?Это очевидно звучит достаточным, но могут возникнуть трудности при программировании, когда у вас нет идей о достижении определенной цели.? В этом уроке мы обсудим различные точки соприкосновения обеих целей. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ? Кадры и контроль объектов. Наверное понятно, что для успешной анимации у вас должно быть множество кадров объекта в различных позах (как гуляющий человек). Их циклическая прокрутка даст эффект натурального движения. Итак, как же нам их запомнить? Ладно, ладно, я слышу ваш крик. Вы правы, прием очевиден – запихнуть их в массив. После того, как кадр нарисован в Autodesk Animator и сохранен как *.cel файл, мы обычно используем следующий код для его загрузки. TYPE icon = Array [1..50,1..50] of byte; VAR tree : icon; Procedure LoadCEL (FileName : string; ScrPtr : pointer); var Fil : file; Buf : array [1..1024] of byte; BlocksRead, Count : word; begin assign (Fil, FileName); reset (Fil, 1); BlockRead (Fil, Buf, 800); { Read and ignore the 800 byte header } Count := 0; BlocksRead := $FFFF; while (not eof (Fil)) and (BlocksRead <> 0) do begin BlockRead (Fil, mem [seg (ScrPtr^): ofs (ScrPtr^) + Count], 1024, BlocksRead); Count := Count + 1024; end; close (Fil); end; BEGIN Loadcel ('Tree.CEL',addr (tree)); END. Сейчас в нашем массиве tree изображение 50x50 файла TREE.CEL. Мы можем как угодно обратиться к элементам этого массива (например, col := tree[25,30]). Если кадр большой или же у вас их очень много, используйте указатели (см. пред. части). Теперь, когда у нас есть картинка, как же нам ее контролировать? Что если мы хотим иметь множество объектов с разными свойствами? Решение – построить запись, хранящую информацию о каждом. Типичная структура данных может выглядеть так: TYPE Treeinfo = Record x,y:word; { Where the tree is } speed:byte; { How fast the tree is moving } Direction:byte; { Where the tree is facing } frame:byte { Which animation frame the tree is currently involved in } active:boolean; { Is the tree actually supposed to be shown/used? } END; VAR Forest : Array [1..20] of Treeinfo; Теперь у вас есть 20 деревьев, каждое со своими параметрами. Доступ к ним такой: Forest [15].x:=100; Абцисса 15-ого дерева установится в 100. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ? Восстановление стертого фона. Я опишу три метода, как это сделать. Нет единственно верного способа. Вы должны экспериментировать и решить, какой лучше для определенного типа программы. 1: 1. Создать две виртуальные страницы, Vaddr и Vaddr2. 2. Нарисовать фон в Vaddr2. 3. Поменять Vaddr2 с Vaddr. 4. Нарисовать все переднеплановые объекты в Vadd. 5. Поменять Vadd и VGA. 6. Повторить с 3-го. В ASCII это выглядит так… +---------+ +---------+ +---------+ | | | | | | | VGA | <======= | VADDR | <====== | VADDR2 | | | | (bckgnd)| | (bckgnd)| | | |+(icons) | | | +---------+ +---------+ +---------+ Преимущество этого метода в простоте, не нужно продолжительное чтение фона, нет мерцания и его легко реализовать. Неудобство – необходимость двух 64000 байтовых экранов и процедуры не такие быстрые из-за медленной скорости мерцания. 2: 1. Нарисовать фон в VGA. 2. Взять часть фона, на которую изображение будет выведено. 3. Поместить изображение. 4. Нарисовать фона из 2 поверх изображения. 5. Повторить, начиная с 2. В ASCII... +---------+ | +--|------- + Background restored (3) | * -|------> * Background saved to memory (1) | ^ | | +--|------- # Icon placed (2) +---------+ Преимущество же этого метода в низком объеме доп. памяти. Недостаток – медленная запись в VGA и может появиться мерцание. 3. 1. Установить один виртуальный экран, VADDR. 2. Нарисовать фон в VADDR. 3. Записать VADDR в VGA. 4. Нарисовать изображение в VGA. 5. Переместить часть из VADDR в VGA. 6. Повторить с 4. В ASCII... +---------+ +---------+ | | | | | VGA | | VADDR | | | | (bckgnd)| | Icon>* <|-----------|--+ | +---------+ +---------+ Преимущество в том, что запись из виртуальной памяти быстрее, чем из VGA и меньше мерцания, чем во втором способе, но при использовании 64000 байт мерцание вызвано использованием большого количества объектов. В прикрепленном коде – смесь третьего и первого методов. Он быстрее первого, и у него нет мерцания, в отличии от третьего. Я использую VADDR2 для фона, но перед записью в VGA перезаписываю измененный фон в VADDR. В примере вы можете видеть, что я восстанавливаю весь фон каждого из изображений, и только потом вывожу все изображения. Если бы я заменил фон, и только потом поместил изображения на каждый объект индивидуально при перекрывающих друг друга изображениях, одно будет практически перезаписано. Следующие разделы – описание работы различных ассемблерных процедур. Возможно, это будет ужасно скучно для тех, кто уже знает ассемблер, но должно помочь новичкам и любителям. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ? Ассемблерный PutPixel Для начала я объясню несколько ассемблерных переменных и функций: <ПОМНИТЕ, ЧТО ЭТО УЖАСНО УПРОЩЕННОЕ ОПИСАНИЕ АССЕМБЕЛЕРА! Существует множество книг, чтобы улучшить ваши знания!> Всего четыре регистра: AX,BX,CX,DX. Есть слова (четыре байта) с диапазоном от 0 до 65535. Вы можете получить доступ к старшим и младшим байтам, заменив X на H для старшего или L для младшего. Например, в AL может храниться число от 0 до 255. У вас также есть два указателя: ES:DI и DS:SI. Левая часть – сегмент, на который вы указываете (например, $a000), а справа – смещение, указывающее, насколько далеко в сегменте вы указываете. Turbo Pascal помещает переменную свыше 16k в базу сегмента, т.е DI или SI будет обнулен при инициализации переменной. Если вы хотите указать на пиксель за номером 3000 на VGA экране, ES должно быть равно $a000 и DI должно быть 3000. Вы можете легко сделать ES или DS смещениями виртуальных экранов. Вот несколько функций, которые вам необходимо знать: mov приемник, источник Копирует содержимое источника в приемник. Источник не изменяется (mov ax,50) add приемник, источник Арифметическое сложение источника и приемника, сумма помещается в приемник, не изменяя содержимое источника mul источник Перемножает AX и источник. Если источник занимает один байт, источник перемножается с AL, результат запишется в AX. Если же источник занимает два байта, источник перемножается с AX, результат запишется в DX:AX Movsw Копирует строку слов из DS:SI в ES:DI, увеличивает SI и DI Stosw Копирует AX в ES:DI. push регистр Запихивает в стек. Регистр можно изменять, ну а потом восстановить сохраненный. pop регистр Восстанавливает регистр из стека. rep команда Повторить команду столько раз, сколько в CX. shl приемник, кол-во Здесь нужно остановиться подробнее. Как вы знаете, компьютер думает нулями и единицами. Каждое число можно создать двумя операциями. Байт состоит из 8 бит, и может содержать число от 0 до 255. Слово состоит из 16 бит и может содержать 0..65535. Двойное слово состоит из 32 бит. shr приемник, кол-во Число 53 можно представить как 00110101. Попросите кого-нибудь объяснить вам перевод из десятичной системы счисления в двоичную :). Что случится, если вы сдвинете все влево? 00110101 = 53 <----- 01101010 = 106 Как видно, число удвоилось! Также, сдвинув вправо, вы получите половину числа! Это ОЧЕНЬ быстрый способ умножения или деления (целоисчисленное) на 2 (15 shr 1 = 7). Формат этой команды - shl приемник, кол-во. Она сдвигает приемник на указанное количество бит (1=*2, 2=*4, 3=*8, 4=*16 и т.д.). Кстати, сдвиг выполняется за 2 такта, тогда как mul может выполняться до 133. Чувствуете разницу? Сдвигать больше, чем на 1 могут только машины, старше 286. Вот поэтому такое вычисление координат работает очень медленно: mov ax,[Y] mov bx,320 mul bx add ax,[X] mov di,ax Но, увы, на 320 сдвинуть нельзя. Можно сдвигать лишь на степени двойки. Но есть очень хитрое решение. Смотрите. mov bx,[X] mov dx,[Y] push bx mov bx, dx {; bx = dx = Y} mov dh, dl {; dh = dl = Y} xor dl, dl {; Тоже самое, что и dx*256 } shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 {; bx = bx * 64} add dx, bx {; dx = dx + bx (т.е y*320)} pop bx {; вернем наш x} add bx, dx {; конечная координата} mov di, bx Чуть подробнее: bx=dx=y dx=dx*256 ; bx=bx*64 ( Note, 256+64 = 320 ) dx+bx=правильное значение Y. Просто прибавьте X! Как вы видите, самый короткий код в ассемблере не есть самый быстрый. Законченная процедура PutPixel: Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); { Помещает пиксель на экран прямой записью в память. } BEGIN Asm push ds {; ??Make sure these two go out the } push es {; ??same they went in } mov ax,[where] mov es,ax {; Указывает на сегмент экрана } mov bx,[X] mov dx,[Y] push bx {; and this again for later} mov bx, dx {; bx = dx} mov dh, dl {; dx = dx * 256} xor dl, dl shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 {; bx = bx * 64} add dx, bx {; dx = dx + bx (ie y*320)} pop bx {; вернем наш x } add bx, dx {; конечная позиция} mov di, bx {; di = смещение} {; es:di = where to go} xor al,al mov ah, [Col] mov es:[di],ah {; Поместим ah в ES:DI } pop es pop ds End; END; Действия с DI: mov di,50 Поместить DI в 50-ую позицию mov [di],50 Поместить 50 в место, на которое указывает DI. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ? Процедура транспонирования Это очень просто. Делаем так, чтобы ES:DI указывали на начало экрана, затем 32000 раз выполняем movsw. procedure flip(source,dest:Word); { копирует текущий экран из источника в приемник } begin asm push ds mov ax, [Dest] mov es, ax { ES = сегмент источника } mov ax, [Source] mov ds, ax { DS = сегмент источника } xor si, si { SI = 0 быстрее, чем mov si,0 } xor di, di { DI = 0 } mov cx, 32000 rep movsw { Повторим movsw 32000 раз } pop ds end; end; Процедура очистки экрана работает почти также, только помещает цвет в AX, потом rep stosw (см. программу). =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ? Заключение Ассемблерные процедуры, представленные здесь не являются идеалом. Большинство из них ASPHYXIA не использует. Но, как вы скоро увидите, они НАМНОГО быстрее паскалевских аналогов. В будущем я надеюсь дать вам больше ассемблерных процедур. Что же будет дальше? Простой 3d туториал? Вам он может не понравиться, потому что я не буду вдаваться в подробности его работы :). [ There they sit, the preschooler class encircling their mentor, the substitute teacher. "Now class, today we will talk about what you want to be when you grow up. Isn't that fun?" The teacher looks around and spots the child, silent, apart from the others and deep in thought. "Jonny, why don't you start?" she encourages him. Jonny looks around, confused, his train of thought disrupted. He collects himself, and stares at the teacher with a steady eye. "I want to code demos," he says, his words becoming stronger and more confidant as he speaks. "I want to write something that will change peoples perception of reality. I want them to walk away from the computer dazed, unsure of their footing and eyesight. I want to write something that will reach out of the screen and grab them, making heartbeats and breathing slow to almost a halt. I want to write something that, when it is finished, they are reluctant to leave, knowing that nothing they experience that day will be quite as real, as insightful, as good. I want to write demos." Silence. The class and the teacher stare at Jonny, stunned. It is the teachers turn to be confused. Jonny blushes, feeling that something more is required. "Either that or I want to be a fireman." ] Увидимся в следующий раз, - DENTHOR