Из журнала ZX-Guide#4.5, Рязань, 08.2002 Быстрый вывод спрайтов  Alone Coder Данный материал повествует о способах вывода графики для игр в жанре стратегии реального времени (RTS). В таких играх очень важна динамичность, но не менее важно и богатство графики. Ти- пичная игра такого жанра содержит не менее 20 различных персонажей,рисунок каждого из которых содержит от 24 и более спрайтов с маской, что в сумме даёт как минимум две странички графики только для них... Поскольку персонажи двигаются попиксе- льно, необходимо найти быстрый способ их вывода с точностью до точки.Разумеется,при вышеупомянутом раскладе неприменимо разме- щение в памяти копий спрайтов, сдвинутых друг относительно друга на разное количес- тво бит. Одновременно нужно выгодно решить зада- чу о количестве используемой видеопамяти. (Здесь я рассмотрю вариант с видеобуфером, имеющим линейную адресацию.) Вывод спрайтов с точностью до пиксела может быть реализован,во-первых,через обы- чные сдвиги; во-вторых,через таблицу сдви- нутых байт (8*256 или 8*2*256 байт разме- ром); в-третьих,через таблицу подпрограмм. Я выбрал третий вариант, потому что он экономнее. Необходимо хранить всего восемь небольших кусков кода, каждый из которых сдвигает спрайт на соответствующее число бит и выводит результат на теневой экран. При этом можно урезать спрайты,попавшие головой или ногами за границы экрана, по высоте простыми арифметическими операция- ми. Будет ли строиться спрайт сверху вниз или снизу вверх - разница небольшая, но у меня получился небольшой выигрыш в скорос- ти при хранении спрайтов вверх ногами,при- чём это удачно ещё и потому, что довольно логично определять положение персонажа по положению его ног, так как спрайты могут сильно различаться по высоте. Небольшой комментарий по программе: как можно заметить,в ней не используются недо- кументированные команды SLI, хотя в одном месте такая команда помогла бы сэкономить три (кажется) такта на линию. Но это смеш- ная экономия соотносительно с принципиаль- ностью, и потому команды нет. ;A=X coord. ;B=sprite height ;HL=screen ;DE=-34 (!) ;ONEBYTE - адрес свободного байта памяти ;SP=адрес спрайта (2 б.маска,2 б.рисунок) AND 7 JP PE,RLPE ;используется чётность ;1,2,4,7 DEC A JR Z,RL7 CP 3 JR Z,RL4 JP C,RL6 RL1 LD A,(HL) EXX POP BC POP HL LD DE,-#80 ADD HL,HL RL E RL C RL B RL D AND D XOR E EXX LD (HL),A INC L LD A,(HL) EXX AND B XOR H EXX LD (HL),A INC L LD A,(HL) EXX AND C XOR L EXX LD (HL),A ADD HL,DE DJNZ RL1 JP RLOK RL4 EXX LD HL,ONEBYTE EXX SCF RL40 LD A,(HL) EXX POP BC POP DE SBC A,A LD (HL),C RLD LD C,(HL) LD (HL),B RLD LD B,(HL) EXX AND (HL) LD C,A EXX XOR A LD (HL),E RLD LD E,(HL) LD (HL),D RLD EXX XOR C LD (HL),A INC L LD A,(HL) EXX AND B XOR (HL) EXX LD (HL),A INC L LD A,(HL) EXX AND C XOR E EXX LD (HL),A ADD HL,DE DJNZ RL40 JP RLOK RL7 LD A,(HL) EXX POP BC POP HL LD DE,#FF01 SRL H RR L RR E RR B RR C RR D AND B XOR H EXX LD (HL),A INC L LD A,(HL) EXX AND C XOR L EXX LD (HL),A INC L LD A,(HL) EXX AND D XOR E EXX LD (HL),A ADD HL,DE DJNZ RL7 JP RLOK ;SCF RL6 LD E,-30 INC L INC L RL60 LD A,(HL) EXX LD E,A POP BC POP HL SBC A,A DUP 2 RR B RR C RRA EDUP AND E LD E,A XOR A DUP 2 RR H RR L RRA EDUP XOR E EXX LD (HL),A DEC L LD A,(HL) EXX AND C XOR L EXX LD (HL),A DEC L LD A,(HL) EXX AND B XOR H EXX LD (HL),A ADD HL,DE DJNZ RL60 LD E,-34 JP RLOK RL3 SCF RL30 LD A,(HL) EXX LD C,A POP HL SBC A,A DUP 3 ADD HL,HL INC L RLA EDUP EX DE,HL AND C LD C,A POP HL XOR A DUP 3 ADD HL,HL RLA EDUP XOR C EXX LD (HL),A INC L LD A,(HL) EXX AND D XOR H EXX LD (HL),A INC L LD A,(HL) EXX AND E XOR L EXX LD (HL),A ADD HL,DE DJNZ RL30 JP RLOK RL2 SCF RL20 LD A,(HL) EXX LD C,A POP HL SBC A,A DUP 2 ADD HL,HL INC L RLA EDUP EX DE,HL AND C LD C,A POP HL XOR A DUP 2 ADD HL,HL RLA EDUP XOR C EXX LD (HL),A INC L LD A,(HL) EXX AND D XOR H EXX LD (HL),A INC L LD A,(HL) EXX AND E XOR L EXX LD (HL),A ADD HL,DE DJNZ RL20 JP RLOK RLPE JR Z,RL0 ;3,5,6 CP 5 JR Z,RL3 JR NC,RL2 LD E,-30 INC L INC L RL5 LD A,(HL) EXX LD E,A POP BC POP HL SBC A,A DUP 3 RR B RR C RRA EDUP AND E LD E,A XOR A DUP 3 RR H RR L RRA EDUP XOR E EXX LD (HL),A DEC L LD A,(HL) EXX AND C XOR L EXX LD (HL),A DEC L LD A,(HL) EXX AND B XOR H EXX LD (HL),A ADD HL,DE DJNZ RL5 LD E,-34 RLOK RLOKSP LD SP,0 RET RL0 INC E RL00 LD A,(HL) EXX POP BC POP HL AND B XOR H EXX LD (HL),A INC L LD A,(HL) EXX AND C XOR L EXX LD (HL),A ADD HL,DE DJNZ RL00 DEC E JP RLOK Итак, мы можем видеть,что вывод спрайта использует стек. Как же организовать пре- рывания? Я был бы неосмотрителен, если бы этого не продумал ;) Так вот, я изобрёл оригинальный способ избежать обычной в таких случаях чехарды с регистром SP. Разумеется,обработчик преры- вания мог бы с точностью до байта опреде- лить, на какой команде находится процедура и в каких регистрах и С КАКИМ СДВИГОМ рас- положены сейчас два последних считанных байта спрайта (запорченные адресом возвра- та), но в любом случае пришлось бы,во-пер- вых, решать головоломную задачу, а во-вто- рых, всё-таки немного менять и замедлять процедуры. В общем, было бы некрасиво и даже неоригинально - мы вернулись бы к то- му же движку Чёрного Ворона. А новый метод позволяет оставить всё на местах. Соль метода - разместить сам вывод спрайтов на прерывании. Как же так? - спросит Человек с Кальку- лятором, нельзя же ограничивать число пер- сонажей в игре тем жалким количеством, ко- торое влезет в прерывание? Разве стратеги- ческая игра может называться таковой в ус- ловиях наличия всего лишь десяти солдат? Разумеется,нет.Мы не будем ограничивать количество,а просто разобьём все выводимые спрайты на группы, каждая из которых может вывестись за одно прерывание. Как автоматизировать этот процесс раз- бития на группы? Выясняется количество линий спрайтов, безболезненно помещающееся во фрейм (220, для Turbo может быть больше),а после выво- да каждого спрайта проверяется, успеет ли очередной из них вывестись за ограниченное время - просто прибавлением высоты к счёт- чику. Если не успевает, покидаем процедуру вывода, оставляя спрайт на следующий кадр. У нас освобождается время между проце- дурами обработки прерываний. Как его за- нять? Лучше всего занять это время самыми ин- теллектуальными задачами: ИИ персонажей и составлением СПИСКА ВЫВОДИМЫХ СПРАЙТОВ,ко- торым и должна оперировать процедура выво- да. Тут мы неожиданно сталкиваемся с мало- знакомой проблемой из области параллельно- го программирования ;). Как быть,если про- цесс вывода спрайтов обгоняет процесс ге- нерации списка? Какой метод избегания та- кого непорядка не приводит к пустым задер- жкам времени? Ну уж тут простор для фантазии :) Я ви- жу даже три принципиально различных мето- да, и два из них реализовал. Вот они: 1) Хранить два списка, и пока генерируется один, визуализируется другой. Недостатки: - Нужно много памяти - по 14 байт на пер- сонажа; - Приводит к несоответствию изображаемой на экране картины мира его, мира,состоянию на текущий момент. 2) Хранить один список.Запускать генерацию раньше, чем вывод (в сущности,это произой- дёт автоматически после банального HALT, завершающего очередной цикл или чего-то в этом роде).В дальнейшем процессы будут ре- гулировать друг друга:если выводящая прог- рамма запнётся о конец списка,то после вы- хода из неё останется больше времени на список,а если ИИ сгенерирует больше спрай- тов, чем надо, то выводилка просто займёт почти всё прерывание и этим вытеснит пер- вый процесс. Недостаток: - Нужно немного, но всё-таки памяти: по 7 байт на персонажа. 3) Аналогично п.2, но хранить один КОЛЬЦЕ- ВОЙ список.В случае его переполнения ("го- лова" - текущий адрес у генератора - обог- нав, заезжает на "хвост" - текущий адрес у процесса вывода) генератор останавливается и ждёт вывод. Памяти нужно всего каких-то 256 байт, а недостаток всего один: - Этот метод я пока реализовать не смог %) Так что смотрите работу метода 2 в про- грамме "ALLODS.H" из приложения. А как во- обще началась эта история, спрашивайте Кибердемона, он меня хотел раскрутить на такую игру &] Рекомендую, кстати, поподробнее ознако- миться с исходником, поскольку там между делом решается ещё несколько смежных за- дач, таких как вывод ФРЕЙМОВОЙ стрелки на НЕФРЕЙМОВОМ фоне НЕ по методу В.С.Медноно- гова и т.д. :) Для более широких спрайтов (как, напри- мер, приведённый там же тролль) нужно ис- пользовать разрезание на вертикальные по- лосы. Хотя часть спрайтов (они выделены на рисунке цветом) этого не требует. Да, вся графика моя :)