Info Guide
#12
31 декабря 2017 |
|
Игрушки - Скроллинг в Evo SDK.
Скроллинг в Evo SDK Hippiman/Conscience Итак, ребята,сегодня мы снова поговорим о том, как выжать все соки из Evo SDK и выйти за его ограничения. Я думаю, те из вас, кто пытался сделать какую-либо игру в этой среде, сталкивались с отсутствием функции скроллинга. Это достаточно большая проблема, т.к.делать скроллинг путем пере─ рисовки кучи тайлов каждый кадр - дело неблагодарное и очень тормозное. Можно, конечно,делать так,как я в своей игре "Nomad": резать большой фон на неско─ лько маленьких полос и двигать их по оче─ реди,имитируя параллаксный эффект. Но этот метод больше похож на "танцы с бубном", доставляет много головной боли при рисовке фонов, все равно отъедает много процессор─ ного времени и не очень приятен глазу иг─ рока. Остается ещё два выхода: забить на скроллинг и делать игру поэкранной, или же писать его на ассемблере, напрямую изменяя видеопамять.Вот этим мы сегодня и займёмся на примере вертикального скроллинга. А в конце статьи я расскажу про один очень неприятный подводный камень, и если его не обойти,то все труды по работе с видеопамя─ тью будут напрасны. Организация памяти Evo SDK выдаёт картинку в разрешении 320*200 в16 цветов из 64 возможных. Это стандартный ATM'овский видео-режим со все─ ми вытекающими. Каждый из двух экранов занимает 2 стра─ ницы ОЗУ,причём каждая страница разбита на две области: Страница #05 (#07):"нечётные" #2000...#ЗFЗF - пары пикселей3,7,11...155,159(8000 байт) #0000...#1FЗF - пары пикселей1,5,9...153,157(8000 байт) Страница #01 (#03):"чётные" #2000...#ЗFЗF - пары пикселей2,6,10...154,158(8000 байт) #0000...#1FЗF - пары пикселей0,4,8...152,156(8000 байт) Каждый байт содержит цвет двух пиксе─ лей, причем биты в нем перемешаны: 6,2,1,0- цвет левого пикселя, 7,5,4,3- цвет правого пикселя. Пример скроллинга Для примера разберем простейшую функ─ цию.Эта функция просто сдвигает весь экран вниз на несколько строк (указывается в па─ раметре shift ). Сначала он определяет, в каком "экране" нужно сделать сдвиг, затем вычисляет адрес начала сдвига и адрес,куда нужно записать данные, а затем при помощи командыlddr производит пересылку данных в фоновом экране. void do_scrolldown(u8 shift) __naked { __asm push ix ld ix,#0 add ix,sp ld a,(_SCREENACTIVE) //ищем нужную страницу bit 1,a jr z,secpaged frstpaged: ld a,#0x1 jpdopaged secpaged: ld a,#0x3 dopaged: ld (_gl_page),a //В переменной SCREENACTIVE SDK хранит //номер активного экрана. Код выше читает //эту переменную и устанавливает нужный //номер страницы в _gl_page, которая была //заранее объявлена в C коде. begd: //page ld a,(_gl_page) xor #0x7f ld bc,#Oxbff7 ld (_MEMSLOT2),a out (c),a //Этот код включает выбранную ранее //страницу во 2-е окно. _MEMSLOT2 - //единственное окно, которое мы можем //более-менее свободно использовать. //Остальные постоянно заняты. beg2d: //--------------------------- //writeaddr //Вычислим адрес того места, куда будем //перемещать часть экрана. ld c,#0x0 ld b,4 (ix) dec b //Получим количество строк, на которые //нужно сдвинуть экран. //4 (ix) - это 1-й параметр, //переданный в С функцию. ld hl,#0x28 ld de,#0x28 jr z,zerd muld: add hl,de djnz muld zerd: //Умножим кол-во строк на 40 байт //(сложением в цикле), получим сдвиг от //начала страницы. ld b,h ld c,l ld de,(_gl_addr) add hl,de ld de,#0x1f40 add hl,de push hl //Получим реальный адрес, куда следует //копировать данные: начало сектора памяти //+ размер копируемой области //+ размер экрана. //Каждый байт хранит информацию о //двух пикселях //- получаем не 320 байт в ряду, а 160. //Каждый ряд разделен на 2 страницы //- получаем по 80 байт на страницу //каждая страница поделена ещё раз пополам //- получаем по 40 байт на область. //readaddr ld hl,(_gl_addr) add hl,de push hl //Рассчитаем адрес, откуда будем //копировать память: //начало сектора памяти + размер экрана. //чтение и запись ld hl,#0x1f40 sbc hl,bc ld b,h ld c,l pop hl pop de lddr //Воспользуемся командой lddr для //перемещений большой области памяти. //Эта команда пересылает блоки снизу вверх //(в HL должен находиться адрес начала //пересылаемых данных, //в DE - адрес назначения пересылки, //в BC - размер пересылаемой области). //Далее следует несколько проверок на //номер страницы и адрес начала области. //Ничем не примечательный код, служит для //того, чтобы обработать все 4 экранных //отрезка памяти. ld hl,(_gl_addr)//проверим адрес ld bc,#0x8000 sbc hl,bc jr z,addrifd jp nxtd addrifd: ld hl,#OxaOO0 ld (_gl_addr),hl jp beg2d nxtd: ld hl,#0x8000 ld (_gl_addr),hl ld a,(_gl_page) ld b,#0x1 sub b jr z,frst_chngd jp next_page_ifd frst_chngd: ld a,#0x5 ld (_gl_page),a jp begd next_page_ifd: ld a,(_gl_page) ld b,#0x3 sub b jr z,sec_chngd jp endd sec_chngd: ld a,#0x7 ld (_gl_page),a jp begd endd: ld bc,#Oxbff7 ld a,#0x71 out (c),a pop ix ret __endasm; } Можно заметить, что между двумя облас─ тями памяти #0000...#1FЗF и #2000...#ЗFЗF есть немного свободного места(192 байта). Этого места в аккурат хватает для хранения 4 строк. В данном примере это не нужно, но для зацикленного скроллинга, в качестве буфера придётся очень кстати. Обман спрайтового движка Теперь, казалось бы, ничего не может омрачить радость от честного вертикального скроллинга, однако если вы включите спрай─ ты, то столкнётесь с особенностью работы спрайтового движка. Он не даёт менять па─ мять экрана и при передвижении спрайтов банально портит картинку. Должно быть так: А получается так: Спрайты восстанавливают область экрана, над которой движутся. Но и это решаемо. Как вообще работает спрайтовый движок? Помимо основных двух экранов, каждый из которых занимает по2 страницы, в SDK есть ещё специальный буфер фона для спрайтов, который занимает4 страницы. Этот буфер нужен для того,чтобы спрайты при перемещении восстанавливали фон под предыдущим своим местоположением. При каждом обновлении экрана вызывается функцияupdateTilesToBuffer , которая све─ ряется с картой обновления тайлов и копи─ рует нужные тайлы в буфер восстановления спрайтов. А карта обновления тайлов, в свою оче─ редь,изменяется каждый раз при перерисовке какого-либо тайла. Эта схема с одной стороны выглядит запутанной, а с другой стороны - позволяет достичь максимального быстродействия за счёт расхода лишней памяти (которой на ATM/ZX Evo предостаточно). Первое, что приходит в голову: нужно как-то дать SDK знать,что тайлы скроллиру─ емой области обновились и их нужно скопи─ ровать в буфер. Чуть подумав,понимаешь,что не обязательно обновлять всю область. Ведь необходимо обновить только те тайлы в из─ менённой области, над которыми есть спрай─ ты, это позволит повысить быстродействие программы. Итак,у нас есть задача:каким-то образом заставить движок обновить тайлы в буфере. В исходниках SDK есть замечательная функ─ ция -setTileUpdateMapF , которая устанав─ ливает флаг обновления тайла, а координаты берёт из регистровой парыBC . Это то, что нам и нужно. Однако эта функция недоступна из C'шного кода. Но никто не мешает нам вызывать её по адресу, ведь в конечной программе код движка всегда находится в одном и том же месте. Адрес вхождения в функцию можно опреде─ лить, банально продебажив конечный код, но мы ведь не извращенцы, тем более что так нужно будет делать каждый раз при внесении изменений в код SDK. Мы пойдем другим, бо─ лее простым путем. Открываем директорию с исходниками SDK. Находим файл lib_startup.asm. Листаем в самый конец и находим код,который отвечает за экспорт функций. Дописываем туда на но─ вой строчке вот этот код: export setTileUpdateMapF Теперь делаем toolssjasmplus sjasmplus.exe lib_startup.asm На выходе получаем startup.bin и lib_startup.exp . Второй нам и нужен. От─ крываем его в текстовом редакторе и нахо─ дим setTileUpdateMapF: EQU 0x0000E644 (у вас адрес может быть другим). 0x0000E644 есть адрес вхождения в нашу функцию. Далее знатоки асма могут написать свою обвязку вызова этой функции из C кода, для остальных я приведу пример такой обвязки. void setTileUpdateMap(u8 x,u8 y) __naked { __asm push ix ld ix,#0 add ix,sp ld a,#0x1 ld c,4 (ix) ld b,5 (ix) call (#OxeбЧ4) inc b call (#OxeбЧ4) inc b call (#OxeбЧ4) inc c call (#OxeбЧ4) inc c call (#OxeбЧ4) dec b call (#OxeбЧ4) dec b call (#OxeбЧ4) dec c call (#OxeбЧ4) inc b call (#OxeбЧ4) pop ix ret __endasm; } #OxeбЧ4 подмените на свой адрес. Спрайт в Evo SDK имеет размер16*16 px, т.е. в лучшем случае нужно будет обновить 4 тайла. Но практически всегда обновлять придется 9 тайлов вокруг центра спрайта. Эта функция как раз так и делает: на входе получает координаты спрайта (левый верхний угол) и обновляет 9 тайлов вниз и вправо от него. Теперь каждый второй кадр нужно вызы─ вать эту функцию, и будет вам счастье - спрайты не будут портить фон. "Третьей ко─ смической скорости" при сдвиге всего экра─ на у вас всё равно не получится, спрайты тормозят рендер более чем в два раза, но простор для творчества и задел на будущее у вас теперь есть. P.S.:в приложении к журналу вы найдёте проект mem_test, а в нем несколько функций вертикального скроллинга,включая зациклен─ ный скроллинг всего экрана вверх и вниз, а также зацикленный скроллинг определённого сектора экрана.
Другие статьи номера:
Похожие статьи:
В этот день... 21 ноября