Из журнала ZX Power #3, Харьков, 1998 АВТОМАТИЧЕСКОЕ СОЗДАНИЕ МАСКИ ДЛЯ СПРАЙТОВ Безуглый Андрей (Ticklish Jim/BIS),1997 _________________________________________ Привет всем, кто решил потратить нем- ного своего времени на чтение этой не- большой статьи. Ее тема, думаю, будет ин- тересна всем тем, кто причастен к созда- нию игр, будь то художник либо програм- мист. Итак... Маска для спрайтов. Как много сейчас можно найти всеразличных спрайтовых ре- дакторов, но в скольких из них есть про- цедура создания МАСКИ для спрайта без участия художника? Лично я еще не видел ни одного (это, кстати, одна из причин почему я вообще занялся данной пробле- мой). Вполне возможно, конечно, что я просто-напросто не знаю о таком редакторе (не художник ведь в конце-концов). Впро- чем, я надеюсь, что после ознакомления с данной статьей, наши кодеры, которые вплотную занимаются созданием программ для работы с графикой (только что-то не видно новых графических редакторов. Неу- жели ART STUDIO так и останется единственным, которым более-менее удобно пользоваться?!) в новые версии своих программ введут функцию AUTOMASK. Навер- няка их версии нижеописанного алгоритма будут выглядеть намного привлекательнее моей (я не программист). Для начала стоит ответить на два воп- роса. Первый: что такое МАСКА и для чего она нужна? Отвечать почти не надо. Те, кто делает игры и так все знают, а те, кто не делает, могут прочитать либо в ИН- ФОРКОМ'овских изданиях, либо посмотреть на DIZZY и догадаться, почему это он не сливается с фоном, а отделен тонкой кай- мой. Второй вопрос звучит: для чего (и для кого) нужна АВТОМАТИЧЕСКАЯ МАСКА? От- вет примерно такой: во-первых, у художни- ков перестает болеть голова, когда перед ними является кодер с воплями "Почему твой колобок справа кажется продолжением стула?!?!", из-за вполне человеческой невнимательности (еще бы, создать маску намного зануднее и кропотливее, чем нари- совать face бравого Santa Clause либо об- русевшего Dizzy); во-вторых, когда под рукой нет художника, на которого можно "наехать" за "продолжение стула", а маска нужна позарез, то вся проблема решается за считанные секунды; в-третьих и в-пос- ледних, представьте следующую ситуацию: у вас есть грандиозный план ввести в игру режим захождения за предметы, или же вам хочется, чтобы активные предметы (ну, те, которые Dizzy постоянно то берет, то выб- расывает) не "выкусывали" где попало по половине знакоместа фона, однако места под доп. графику почти не осталось (т.к. есть поддержка всех мыслимых и немыслимых "примочек";-). Вот тут и пригодится прог- раммка, которая будет динамично создавать маску только для тех спрайтов, которые, например, задействованы только в этом эк- ране (да-да, именно в этом ;). Существует три алгоритма (если, конеч- но, кто придумает еще, то пусть сообщит, запишем в реестр :-). Третий, самый прос- той и самый быстрый, будет описан более подробно в конце сего повествования. Пер- вые два будут лишь кратко описаны ввиду малой целесообразности их применения (после чтения попробуйте примерно подсчи- тать во сколько раз 2-й алгоритм будет работать быстрее 1-го, а 3-й - быстрее 2-го. Это так, ради разнообразия). 1. Метод "выкусывания". Как оказалось, это был самый очевид- ный вариант. Принцип его работы очень прост. Берется спрайт, создается равный ему по величине буфер маски, который мы заполняем #FF. Нашли, создали, заполнили и начали заниматься самым интересным де- лом: сканированием базы спрайта. Т.е. бе- рем байт спрайта и проверяем 7-й бит (ес- ли сканирование идет слева направо). Если бит выключен, то переходим к следующему биту без обращения к буферу маски. В про- тивном случае мы находим соответствующий бит в шаблоне маски и гасим его, а заодно сбрасываем все биты вокруг найденного и обезвреженного (только учтите, что надо проверять где именно находится данный бит. Не буду перечислять все возможные варианты, лишь скажу, что их всего 9). После этого возвращаемся к спрайту и пе- реходим на следующий бит. И так до самого победного конца. В результате мы получаем прекрасную маску и гигантскую паузу во время работы программы. Так что отбросьте эту дикую идею, реализовать данный алго- ритм, лучше почитайте о следующем, кото- рый, в принципе, является модификацией (качественной) предыдущего. 2. Метод "выкусывания #2". Похож немножко на 1-ый. Только буфер мы заполняем #00. Опять сканируем. Только здесь, если мы нашли включенный бит, сра- зу проверяем округу (те же 9 условий, ни- куда они не делись :) на предмет от- сутствия других единичек. Если хоть одна имеется, то бросаем это дело и переходим к дальнейшему линейному сканированию спрайта. Но в том случае, когда вокруг 1 оказалась толпа 0 (в количестве от 3 до 8), то мы сразу бежим в буфер маски, на- ходим соответствующее место и ставим кучу единиц (от 4 до 9). После полного прохода спрайта мы берем буфер маски и инвертиру- ем его содержимое. В результате у нас прекрасная маска и немного меньшая пауза (т.е. чай вы уже не успеете попить) в ра- боте. Зато как интересно наблюдать за ра- ботой BASIC-программы, создающей эту пресловутую маску... Да еще без компиля- тора. Представляете себе? Нет, не надо обманывать, вот когда сами испытаете это, тогда я поверю. И вот мы подошли к самому лучшему ал- горитму. 3. Метод сдвигов. Все очень просто. Резервируем буфер под маску. Берем часть спрайта (без верхней линии изображения) и бросаем его в буфер - это равносильно сдвигу вверх на точку (именно для спрайта в памяти, а не изображения на экране, т.к. в таком слу- чае возникают доп. проблемы, которые ни- кому не нужны). Затем берем другую часть спрайта (без нижней линии, т.е. минус ши- рина) и делаем OR с содержимым буфера, причем адрес буфера увеличиваем на ту же величину, на какую мы уменьшали длину спрайта в первых двух манипуляциях (нап- ример, ширина=18 знакомест, общая длина спрайта=1728 байт, тогда перекидываем мы и в 1-м, и во 2-м случаях 1728-18=1710 байт). В результате в буфере мы получили сдвинутое изображение вверх и вниз на один пиксель. Осталось только обозначить боковые границы. Тут совсем просто, берем байт спрайта, сдвигаем его в ту сторону, куда нам больше нравится и OR'им его с соотв. байтом буфера маски, полученный результат заносим в буфер. Затем делаем все то же, но только в другую сторону и сразу же после OR CPL'им, чтобы маска приняла свой законный вид. В результате в буфере находится качественная маска для любых сложных и не очень спрайтов, а пау- за... Где же она?.. В качестве примера я привожу пусть еще не совершенную, но вполне работоспособную и протестированную программу, снабженную комментариями. Все очень просто, думаю, неясностей возник- нуть не должно. Программа АВТОМАТИЧЕСКОГО создания МАСКИ для спрайтов AUTOMASK v1.02 (C)1997 BROKIMSOFT (C)1997 Idea by TICKLISH JIM, STEELER (C)1997 Code by TICKLISH JIM ВНИМАНИЕ! Данная версия программы предназначена только для демонстрации работы алгоритма АВТОМАСКИ, поэтому перед началом работы необходимо очистить буфер МАСКИ. Кто хо- чет, конечно, может переделать процедуры формирования границ, благо возможность есть. ORG 40000 ;ТОЛЬКО ДЛЯ ПРИМЕРА INSERT "MONAST" SPR_PL EQU 40000 ;АДРЕС НАЧАЛА СПРАЙТА SPR_BUF EQU 30000 ;АДРЕС БУФЕРА МАСКИ ORG 25000 LD HL,SPR_PL ;НАЧАЛО СПРАЙТА LD (SPR2+1),HL LD A,(X_SIZE) ;X_SZ LD (L1+1),A LD (L2+1),A LD (L3+1),A LD B,A LD A,(Y_SIZE) ;Y_SZ ADD A,A ADD A,A ADD A,A LD HL,0 LD D,0 LD E,A MULT ADD HL,DE DJNZ MULT ;В HL ДЛИНА ОБРАБАТЫВАЕМОГО СПРАЙТА LD (X1+1),HL ; LD (X0-2),HL ;ДЛЯ DEMO LD (L4+1),HL ;ЗАНЕСЛИ ДЛИНУ В BC LD HL,SPR_BUF PUSH HL PUSH HL PUSH HL EXX POP DE SPR2 LD HL,SPR_PL PUSH HL L2 LD A,0 ADD A,E LD E,A JR NC,L1_1 INC D ;HL'=SPR_PL, DE'=SPR_BUF+X_SIZE L1_1 EXX POP HL POP DE L1 LD A,0 ADD A,L LD L,A JR NC,X1 INC H ;HL=SPR_PL+X_SIZE, DE=SPR_BUF X1 LD BC,0 LOOP DEC BC DEC A JR NZ,LOOP ;BC'=ДЛИНА СПРАЙТА-X_SIZE SCROLL PUSH BC LD A,(DE) EXX OR (HL) EXX OR (HL) LD (DE),A EXX LD A,(DE) EXX OR (HL) INC HL INC DE EXX OR (HL) LD (DE),A INC HL INC DE POP BC DEC BC LD A,B OR C JR NZ,SCROLL ;ЭТА ЧАСТЬ ПРОГРАММЫ ОБОЗНАЧИЛА ВЕРХНЮЮ И ;НИЖНЮЮ ГРАНИЦЫ СОЗДАВАЕМОЙ МАСКИ. Написано далеко не идеально и не оптима- льно, но для демонстрации вполне подхо- дит, да и место для применения собствен- ных идей остается. LD HL,10072 ;ВОССТАНОВИЛИ HL' EXX ;ПЕРЕКЛЮЧИЛИСЬ НА ОСНОВНОЙ НАБОР РЕГИСТ- ;РОВ POP HL L4 LD BC,0 PUSH BC EX AF,AF' XOR A JR ROLL_RT+1 ROLL_RT EX AF,AF' LD E,(HL) RR (HL) EX AF,AF' LD A,E OR (HL) LD (HL),A INC HL DEC BC LD A,B OR C JR NZ,ROLL_RT ;ОБОЗНАЧИЛИ ПРАВУЮ ГРАНИЦУ МАСКИ DEC HL POP BC EX AF,AF' XOR A JR ROLL_LT+1 ROLL_LT EX AF,AF' LD E,(HL) RL (HL) EX AF,AF' LD A,E OR (HL) CPL ;ИНВЕРСИЯ БАЙТА МАСКИ LD (HL),A DEC HL DEC BC LD A,B OR C JR NZ,ROLL_LT ;ЭТА ЧАСТЬ ОБОЗНАЧАЕТ ПОСЛЕДНЮЮ, ЛЕВУЮ, ;ГРАНИЦУ МАСКИ И ОДНОВРЕМЕННО ИНВЕРТИРУЕТ ;БАЙТ, ЧТОБЫ В ИТОГЕ ПОЛУЧИТЬ РЕАЛЬНЫЙ ;БАЙТ-МАСКУ. ;Эти две процедуры можно совместить в ;одну, что заметно ускорит работу ;программы. К тому же инверсию байтов ;маски можно делать уже непосредственно ;при выводе изображения на экран. RET ;ПЕРЕМЕННЫЕ, УКАЗЫВАЮЩИЕ НА РАЗМЕРЫ ;СПРАЙТА (В ЗНАКОМЕСТАХ) X_SIZE DB 18 Y_SIZE DB 12 ;ДЕМОНСТРАЦИЯ МАСКИ ПО OR В РЕЗУЛЬТАТЕ ;ВЫПОЛНЕНИЯ ЭТОЙ ПОДПРОГРАММЫ С АДРЕСА ;SPR_BUF БУДЕТ РАСПОЛОЖЕН СПРАЙТ,ВПЕЧА- ;ТАННЫЙ В СОЗДАННУЮ МАСКУ. ;DEMO LD HL,SPR_BUF ;SPR3 LD DE,SPR_PL ; LD BC,0 ;X0 LD A,(DE) ; OR (HL) ; LD (HL),A ; INC HL ; INC DE ; DEC BC ; LD A,B ; OR C ; JR NZ,X0 ; RET Как видите, в качестве примера данная программа годится. Однако ее применение можно считать более-менее целесообразным разве что в каком-то редакторе спрайтов, но уж никак не в игровой программе, где требуется высокая скорость работы проце- дуры при относительно небольшом объеме памяти, используемом под всеразличные бу- феры. В общем, на звание REALTIME эта процедура явно не тянет. Нижеприведенная программа, смею надеяться, окажется более полезной именно с практической точки зре- ния. Как обычно, комментарии прилагаются. Алгоритм работы такой же, какой был ис- пользован в предыдущей программе. Естественно, что я не претендую на ис- тину в последней инстанции (повторяю: я не программист, а всего лишь хакер, кото- рого вынудили погрязнуть в кодерстве); уверен - при желании все можно сделать лучше, чем есть, поэтому и говорю, что это всего только пример. Разбирайте, экспериментируйте и используйте, ведь об- ласть применения данной программы весьма широка, особенно в играх жанра ARKADE-AD- VENTURE. При работе используется два буфера по 8 байт каждый (для спрайтов шириной 8 знакомест; можно, конечно, больше, если надо), т.е. 16 байт всего. ORG 40000 ; .INCBIN tree ;Файл 'tree' - это обрабатываемый спрайт ORG 25000 JR START WD DEFB 5 ;ширина спрайта в знакоместах HG DEFB 7 ;высота спрайта в знакоместах PL DEFW 40000 ;адрес начала спрайта START LD IX,(PL) LD A,(HG) ADD A,A ADD A,A ADD A,A ;умножили высоту на 8, ;т.е. перевели знакоместа в ;пиксели (линии) LD (M1-1),A LD A,(WD) LD (M2+2),A LD (M2-1),A LD (BF0-1),A LD HL,16384;адрес на экране, куда ;будет выводится спрайт ;с маской DEC A LD E,A LD D,0 ADD HL,DE ;увеличили его на (ши- ;рина-1),т.к.печать ве- ;дется справа налево LD (PR_LN+1),HL ; LD HL,18432 ;MASK_RESTORE_BUFER ; LD (PR_LN+4),HL ;Указываем адрес буфера,куда заносится ;фон под спрайтом. В данном случае я его ;просто не использую. XOR A LD (TT1),A ;Блокировали очистку буфера (при запуске ;он должен быть очищен) CALL REAL_MASK ;Вызвали непосредственно программу печати ;спрайта с маской. RET REAL_MASK LD B,0 ;счетчик линий спрайта M1 PUSH BC EX AF,AF' XOR A ;сбросили флаг C в F' EX AF,AF' LD HL,BUF0 ;адрес буфера для фор- ;мирования первой час- ;ти маски (сдвиг ;вверх-вниз) EXX LD HL,BUF1 ;адрес буфера для ;окончательной маски ;(вправо-влево и ин- ;версия) EXX LD B,0 ;счетчик байтов (шири- ;на) M2 LD A,(IX+0) OR (IX+0) INC IX LD E,A OR (HL) LD (HL),E ;заносим в BUF0 резуль- ;тат сдвига вверх-вниз INC HL EXX LD E,A LD D,A EX AF,AF' RR E ;заодно сдвигаем впра- ;во,накладываем на пре- ;дыдущий результат и ;заносим полученный ;байт в BUF1 EX AF,AF' LD A,E OR D LD (HL),A INC HL EXX DJNZ M2 LD (SPR+1),IX ;IX указывает на на- ;чало следующей линии ;спрайта LD (BF0+1),HL ;HL указывает на ;следующий байт буфера ;BUF0 CALL PR_LN ;напечатаем линию ;спрайта с маской POP BC LD A,B DEC A CP 1 JR NZ,STOP ;если у нас осталась необработанной пос- ;ледняя линия спрайта, то включаем ;очистку основного буфера (BUF1), для ;последующей работы и блокируем прираще- ;ние IX, чтобы не вылезти за пределы нуж- ;ного нам спрайта. XOR A LD (M2+2),A LD A,#36 LD (TT1),A STOP DJNZ M1 EXX LD HL,10072 ;восстановили HL' EXX RET PR_LN LD HL,0 ;здесь будет адрес в ;экране, куда печатаем ;спрайт(справа-налево) ; LD DE,0 ;адрес буфера для фона ;(здесь не исп.) PUSH HL EXX LD B,0 ;ширина BF0 LD DE,0 ;DE'=BUF0+ширина, ;HL'=BUF1+ширина XOR A ;сбросили флаг C в F T1 DEC HL ;HL' установили на по- ;следний байт исполь- ;зуемой части буфера ;BUF1 DEC DE ;то же самое для BUF0 LD A,(HL) RL (HL) ;сдвинули влево PUSH AF OR (HL) ;наложили на первона- ;чальное EX DE,HL OR (HL) ;наложили на сдвинутое ;вправо TT1 LD (HL),0 ;очищаем буфер (если ;последняя линия) EX DE,HL CPL ;инвертируем байт мас- ;ки EXX LD C,(HL) ;сохранили байт фона ;(не исп.) AND (HL) ;использовали байт ;маски PUSH HL SPR LD HL,0 ;HL указывает на нача- ;ло сл. линии спр. DEC HL ;теперь на конец обра- ;ботанной OR (HL) ;наложили байт изобра- ;жения LD (SPR+1),HL ;изменили указатель ;адреса спр. POP HL LD (HL),A ;вывели на экран байт ;с маской DEC HL ;уменьшили адрес печа- ;ти ; LD A,C ; LD (DE),A ;занесли в буфер байт ;фона ; INC DE EXX POP AF ;перешли к печати сле- ;дующего байта спр. DJNZ T1 EXX POP HL CALL NEXT_L ;следующая линия в эк- ;ране LD (PR_LN+1),HL ;запомнили ; LD (PR_LN+4),DE ;запомнили сл. ад- ;рес буфера RET ;процедура вычисления сл. линии в экране, ;объяснять никому ничего, думаю, не надо. NEXT_L INC H LD A,H AND 7 RET NZ LD A,L ADD A,32 LD L,A RET C LD A,H SUB 8 LD H,A RET BUF0 DEFS 8 ;основной буфер BUF1 DEFS 8 ;вспомогательный буфер ;Величина обоих равна максимально возмож- ;ной ширине печатаемых спрайтов. ;И помните, что при печати фон сохраняет- ;ся ЗАДОМ НАПЕРЕД - не запутайтесь (если ;будете использовать, конечно, именно мою ;процедуру) при восстановлении. Если кому ;непонятно, то объясняю: например, ширина ;спрайта равна 3, тогда фон будет сохра- ;няться моей программой не стандартно ;1,2,3, 4,5 и т.д.,а по-другому - т.е. ;3,2,1,6,5, 4,9,8,7 и т.д. Я не убирал ;это небольшое неудобство потому, что оно ;действительно небольшое. . . . И напоследок еще несколько общих фраз. Данная статья была начата еще в 1997 го- ду, а последняя редакция специально для журнала ZX-POWER датирована апрелем этого года. Вполне естественно, что материал мог устареть. Однако у меня до сих пор так и не появилось человеческого spri- te-editor'a, который по возможностям мож- но было бы сравнить со SPREDIT2.0 (1993!), не говоря уже о более совершен- ной программе (я слышал, что SPRITE MAS- TER v5.0 является чем-то сногсшиба- тельным, но увы - не видел. Если кому не жалко, то поделитесь программкой, мой ад- рес указан в конце статьи). Поэтому мы как пользовались, так и продолжаем пользоваться собственной процедурой. Если кому будет интересно, то могу по- советовать покопаться в чешской игрушке TOWDIE. Там также используется автомаска, причем по скорости она явный лидер - 11 тактов на байт. Правда, есть одно "но" - маска получается не очень качественная. Скажем, для спрайтов шириной 2 байта (не больше) маска получится удовлетвори- тельной, если, конечно, вас не смущает то, что персонаж будет сливаться с фоном сверху и снизу. Впрочем, просто загрузите TOWDIE и внимательно посмотрите, все сра- зу станет ясно. Далее, для иллюстрации работы вышеопи- санной программы в приложении ZX-POWER'a записаны два файла: "scr1" и "scr2". Пер- вый - пример построения игрового экрана без использования маски для спрайтов, второй - с использованием. Но лучше всего загрузить "CSCDV_D2" - там все комнаты построены именно с ис- пользованием процедуры автомаски. Конеч- но, быстродействие было и остается больным вопросом, но за все, как из- вестно, надо платить. Хотя для аркадных адвентюр скорость построения экрана не является определяющим фактором, верно? Для интересующихся могу сказать, что мак- симальное время, которое требуется моей программе для печати игрового экрана из 99и спрайтов с маской (на практике, ко- нечно, так много не нужно) составляет 2 сек. Кстати, на этом же диске должен быть записан файл "REAL". Это готовая програм- ма, которая создает блок данных по прин- ципу "байт маски - байт спрайта". REAL написана в ассемблере STORMv1.1 (интерес- но, он только у меня "убивал" диски при записи, или как? На мой взгляд ребятам из X-TRADE все-таки стоит учитывать, что не у всех стоят TEAC'и), а затем экспортиро- вана в формат TASM2.0. На этом позвольте закончить. A для тех, кто захочет связаться со мной, вот мои координаты: тел: (0472) 43-37-97 (с 18:00 до 22:00) адрес: 257005, Украина, г.Черкассы, ул.Шевченко, д.367/1, кв.37, Безуглому Андрею Анатольевичу 20.04.98. TJ/BIS _________________________________________