В исходниках Highway Encounter для УКНЦ есть процедуры вывода ч/б спрайтов 16x24 с маской, со сдвигом на -2/0/2/4 пикселов.
https://github.com/nzeemin/uknc-high...ter/HWYENC.MAC, ищите по "Draw sprite with shift".
Вид для печати
В исходниках Highway Encounter для УКНЦ есть процедуры вывода ч/б спрайтов 16x24 с маской, со сдвигом на -2/0/2/4 пикселов.
https://github.com/nzeemin/uknc-high...ter/HWYENC.MAC, ищите по "Draw sprite with shift".
а за счет изменения размера меню под экраном нельзя ли нормально оставить спрайты как есть, без ужиманий? Меню-то не критично - ужали/разжали - там одни тексты...
Измерил быстродействие: процедура рисования спрайта со стиранием точек в полтора раза медленней (11,7 мс ~ 85 раз в секунду) процедуры рисования по чёрному (8 мс ~ 124 раза в секунду).
Но самое интересное вот что: даже если рисовать тормознутой процедурой, платформа катается по экрану достаточно быстро. При этом анимация крутящихся гусениц сливается. Получается, эта анимация и не нужна вовсе. Чтобы разница между кадрами стала заметной, каждый кадр надо держать на экране раза в 3-4 дольше, а это уже слишком медленно для игрового процесса.
Пришла тут одна мысль как двигать спрайт при помощи CRC, на словах это объяснить сложно, попробую на примере.
Если бы в байте было 4 бита, можно было бы использовать полином x^4+x+1, который выдаёт последовательность:
111100010011010(1111...)
в ней отсутствует комбинация 0000, поэтому воткнём еще один 0 там где их 3 и получим:
1111000010011010(1111...)
если взять по 4 битика, получится 16 уникальных комбинаций:
1111 1110 1100 1000 0000 0001 0010 0100 1001 0011 0110 1101 1010 0101 1011 0111
(выделено некоторое значение и где его биты оказываются при сдвиге)
если их засунуть в таблицу, а в спрайте вместо комбинации из 4 бит разместить индекс элемента из таблицы, то прибавив к нему n>0 можно получить индекс элемента сдвинутого влево(с мусором в младших разрядах), а вычитая n>0 сдвинутого вправо(с мусором в старших разрядах).
Вытащив элементы с индексами отличающимся на 4, и используя маску можно получить циклический сдвиг одного из элементов находящихся между ними. чтобы можно было сдвигать любой элемент на n<4 и не прочитать мусор, три элемента из хвоста таблицы нужно приписать до начала, а три элемента из начала после хвоста.
Если вернуться к 16 разрядным словам, то нужно будет сделать табличку в 256+14(+14 если будем двигать и в другую сторону) элементов полученных при помощи полинома восьмой степени. Значения генерируемые полиномом копируются в чётные биты, а нечётные остаются нулевыми. Слово спрайта разделяется на чётные и нечётные биты, и заменяется двумя байтовыми индексами, показывающими где такие значения можно найти в таблице. То есть для сдвига 8 точек спрайта потребуется:
Прочитать два индекса i1 и i2 из спрайта.
Вычислить слово сдвинутое влево: table[i1+shift] +table[i2+shift]*2
Вычислить слово сдвинутое вправо: table[i1+8-shift] +table[i2+8-shift]*2
Наложить маски и вывести на экран.
Если править код чтения из таблиц(его можно будет использовать и для следующей строки), то для вывода 8 точек вроде как требуется 16 команд, а прочитанных или записанных слов примерно раза в два больше(организация цикла сюда не входит).
/*эта анимация и не нужна вовсе.*/
Это сейчас когда объектов на экране мало анимацию не видно, когда притормозит.. все будет ок. В оригинале ее тоже плохо заметно.. но все равно что-то мельтешит.. ощущается движение.. иначе будет совсем неживая аппликация двигаться. Кстати заметили в оригинале, что кроме гусениц по бокам платформы сверху типа "поршни" попеременно выскакивают.. то же оживляет..
С таблицами идея хорошая - по таблицам можно делать и зеркальное отражение спрайта.
Я пробовал использовать таблицы сдвига для пропорциональных шрифтов (шрифты в памяти монохромные, а выводятся в цвете, поэтому один байт раскрывается в целое слово по табличкам). Но отказался из-за громоздкости и незначительного прирорста в производительности. Опять же, надо писать код, замерять быстродействие и решать стоит ли оно того (таблички всё же занимают память).
- - - Добавлено - - -
В том-то и дело, что тормозить не хочется. Сейчас платформа проезжает экран примерно за 3 секунды. Сильно замедлять не хочется. Не в три же раза.
Ого, надо будет посмотреть в DOSbox в замедленном режиме.Цитата:
Кстати заметили в оригинале, что кроме гусениц по бокам платформы сверху типа "поршни" попеременно выскакивают.. то же оживляет..
Первые успехи на УКНЦ, разобрался с прямым доступом к ВОЗУ (правда памяти сжирает.. но зато быстро) и есть косячек в виде мигающей строчки сверху.. буду думать кто туда чего пишет.
https://yadi.sk/i/fEaMpdl1ZibuVA
blackmirror, пытаюсь осмыслить как применить эту хитрую таблицу на практике.
Возьмём какой-нибудь байт из исходного спрайта, например такие 4 точки:
◼︎◼︎◼︎◼︎
В таблице ему должно соответствовать несколько слов:
◼︎◼︎◼︎◼︎◻︎◻︎◻︎◻︎
◻︎◼︎◼︎◼︎◼︎◻︎◻︎◻︎
◻︎◻︎◼︎◼︎◼︎◼︎◻︎◻︎
◻︎◻︎◻︎◼︎◼︎◼︎◼︎◻︎
Значком ◻︎ я обозначил точки произвольного цвета, конкретные значения нас не интересуют.
В исходном спрайте рассматриваемые 4 точки должны храниться не в виде цветов (11001001), а в виде порядкового номера в таблице.
Чтобы нарисовать наши 4 точки, мы достаём число из данных спрайта, умножаем его на 2 (потому что таблица состоит из слов, а не из байтов), берём значение из этой таблицы, сделав отступ в таблице, если надо. И ещё применяем маску.
Дальше если надо вывести точки без сдвига, делаем так:Код:MOVB (R1)+,R2 ; взяли данные из спрайта
BIC #177400,R2 ; сбросили старшие биты, можно маску сброса хранить в каком-нибудь регистре
ASL R2 ; умножили на два, получили индекс в таблице
Если надо вывести точки со сдвигом на одну, делаем так:Код:MOV TABL(R2),R2 ; забрали 8 точек из таблицы
BIC #377,R2 ; сбросили ненужные 4 точки, маску опять же можно хранить в регистре
BIS R2,(R3) ; вывели оставшиеся 4 точки на экран
Сдвиг на две точки:Код:MOV TABL+2(R2),R2 ; забрали 8 точек из таблицы
BIC #140077,R2 ; сбросили ненужные 4 точки по маске
BIS R2,(R3) ; вывели оставшиеся 4 точки на экран
Сдвиг на три точки:Код:MOV TABL+4(R2),R2 ; забрали 8 точек из таблицы
BIC #170017,R2 ; сбросили ненужные 4 точки по маске
BIS R2,(R3) ; вывели оставшиеся 4 точки на экран
Довольно изящно. Да ещё и организация таблицы умная: точки ◻︎ - не абы что, а цвета для других входных данных.Код:MOV TABL+6(R2),R2 ; забрали 8 точек из таблицы
BIC #176003,R2 ; сбросили ненужные 4 точки по маске
BIS R2,(R3) ; вывели оставшиеся 4 точки на экран
Но изящно оно пока мы выводим изображение в чётные адреса экрана.
Как только надо вывести 4 точки по нечётному адресу, появляются костыли:
А это уже довольно долго.Код:MOVB (R1)+,R2 ; взяли данные из спрайта
BIC #177400,R2 ; сбросили старшие биты
ASL R2 ; умножили на два, получили индекс в таблице
MOV TABL+6(R2),R2 ; забрали 8 точек из таблицы
BIC #176003,R2 ; сбросили ненужные 4 точки по маске
BISB R2,(R3)+ ; вывели часть точек на экран
SWAB R2 ; переключились на оставшиеся точки
BISB R2,(R3) ; вывели оставшуюся часть точек на экран
P.S. На самом деле порядок точек на экране слева направо соответствует порядку битов справа налево, но это детали. Просто изображённые мной цветные квадратике будут выводиться на экран БК в обратном порядке.
Manwe, это в примере она по 4 точки, в реальности нужна таблица из 256 слов плюс небольшой хвост, которая будет работать для 8 точек. Только в таблице биты 0,2,4,6,8,10,12,14 будут хранить данные, а оставшиеся биты нули. Плюс по такому же принципу разделяются исходные 8 точек спрайта, и для них формируются два индекса(они будут разными), один показывает где в таблице можно найти такое сочетание чётных бит, а второй для нечётных(если их сдвинуть на место чётных). Два индекса хранить нужно сдвинутыми на 1 разряд, чтобы не умножать на 2, а использовать маску, в общем для 8 точек получается что-то типа такого:
- - - Добавлено - - -Код:;извлечение индексов
MOV (R1)+,R2 ; взяли из спрайта два индекса, для чётных и нечётных бит
;(они были циклически сдвинуты чтобы не умножать)
MOV R2,R3 ;скопировали индексы
BIC #177001,R2 ;извлекли индекс для чётных бит
XOR R2,R3 ;обнулили эти биты в копии
SWAB R3 ;извлекли индекс для нечётных бит
;вывод точек в текущее слово экрана
MOV TABLE+SHIFT(R3),R4 ;читаем нечётные биты(вернее пока они лежат в чётных)
ADD R4,R4 ;ставим на законное место
ADD TABLE+SHIFT(R2),R4 ;добавляем к ним чётные
BIC #MASK,R4 ;применяем маску
BIS R4,(R5)+ ;выводим на экран
;вывод остальных точек в следующее слово экрана
MOV TABLE+SHIFT-16(R3),R4 ;читаем нечётные биты
ADD R4,R4 ;ставим на законное место
ADD TABLE+SHIFT-16(R2),R4 ;добавляем к ним чётные
BIC #177777-MASK,R4 ;применяем маску
BIS R4,(R5) ;выводим на экран
;команд здесь конечно многовато, нужно еще подумать что можно сделать с циклическими сдвигами, вдруг 8 отдельных процедур будеть быстрее и меньше по размеру
;если остальные точки не выводить сразу а оставить скажем в регистре R0
;и взять из спрайта следующие 2 индекса
;то вместо команд BIC/BIS можно объединять 8 точек и вывести командой MOV
;при выводе точек из середины спрайта
XOR R0,R4
AND #MASK,R4
XOR R0,R4
MOV R4,(R5)+
;команд здесь столько же, но нет команд чтения/модификации/записи
;может по такому-же принципу можно объединять если выводим по 4 точки
Manwe, всё же отдельные функции вывода со сдвигом будут наверно работать чуть быстрее(считаем что данные спрайта уже загружены в R0):
сдвиг на 1 точку:
rol r0 rol r0 mov r0,r1 rol r1
сдвиг на 2 точки:
+ еще rol r0 rol r0 в начале
сдвиг на 3 точки:
в первом варианте rol меняется на ror, а в начале добавляется swab
сдвиг на 4 точки:
swab r0 mov r0,r1
для сдвигов на 5,6,7 точек:
rol меняется на rol и наоборот
То есть между чтением 8 точек из спрайта, наложением маски и выводом командами bic/bis требутеся не более 6 команд для его сдвига, можно вместо bic/bis делать объединение через xor, команд столько же, но для середины спрайта меньше обращений к памяти, а для хвостов пару раз читаем слово с экрана(и используем маски). В общем при выводе одной строки спрайта, для изменения 8 точек на экране требуется не более 12 команд(если цикл сделан через SOB). По объёму кода наверно тоже будет меньше чем табличка.
Manwe, извиняюсь не по теме вопрос, если вот такая крутизна как ваша ДЕМО (прямое и инферстное тыблоко)
- это второе место, как выглядело 1-е место
Вот так:
http://content.pouet.net/files/scree...7/00077759.jpg
Занимает 4 килобайта. http://www.pouet.net/prod.php?which=77759
Ну, это понятно, бы ли же вроде демы использующие функционал SMK, но во-первых тут сравнение было бы правильным, если бы эта дема использовала только функции BIOS/UEFI. А во-вторых, определение "platform: Windows" не совсем корректное. Скачал я этот экзешник и что - "работа программы была завершена" не начавшись, а меня тоже Windows на компутере.
Но, всё это уже жёсткий офф, так что завязываем ;-)
Manwe, администратор 1000бит добавил линк на вашу дему в самом низу на страничке портала про БК-11М )
https://www.1000bit.it/scheda.asp?id=1791
/* Для Manwe - запакованый лабиринт */
https://yadi.sk/d/BO9q5VcNdpHe9A
SCREEN:: - (0-91) массив ссылок на описание комнат.
PATTER:: -(0-117)ссылки на паттерны из тайлов.
SCRn::
1. 0, кол-во элементов (ниже) // нули добавлял чтобы читать словами
2. X, Y, № паттерна(PATnn), 0 (X,Y - координаты в буфере размерностью кратной размерам тайла т.е. Xmax кол-во тайлов в ширину..) //нули можно убрать или местами поменять, скажи как лучше.
.......
PATn::
1.x,y - размерность паттерна (так и выводить в буфер.. если 2x8 значит ширина 2 и вниз 8)
2. №тайла, №тайла...
........
P.S.
Сюда можно впихнуть описание объектов но я об этом пока не думал..
...
подправил малость
Думаю над хитрой буферизацией экрана под движущимися объектами. Хочу уложить всё в объём памяти стандартной БК-0010.
Пока идея такая:
1. Определять начальные адреса экрана для левого верхнего угла каждого спрайта.
2. Для каждой строки экрана считать сколько спрайтов на неё приходится. Это буфер из полторы сотни байтов.
3. Сортировать спрайты в порядке отрисовки сверху вниз и слева направо.
4. Формировать массив вида «содержимое, адрес». Для 10 спрайтов шириной в 3 слова и высотой в 10 пикселей получается массив 300*2 слов. В 16 раз меньше, чем полный буфер экрана.
5. Поскольку спрайты отсортированы, при отрисовке легко проверять массив на несколько элементов назад: а не записывалось ли уже по этому адресу содержимое других спрайтов. Если не записывалось - делаем просто MOV. Если записывалось - смешиваем содержимое (с применением масок если хватит памяти на них или без, просто XOR’ом).
6. Помимо отрисовки новых спрайтов, одновременно стираем старые, оставшиеся с предыдущего кадра. Таким же образом, с сортировкой и сравнением адресов.
7. На выходе получаем небольшой массив, который по сути содержит дельта-информацию: чем новый кадр отличается от старого. Выводим этот массив на экран MOV (R1)+,@(R1)+ Так у нас сделано в демке «Good Apple». Это гораздо быстрей, чем копировать полный экран.
Плюсы такого подхода:
+ При выводе спрайтов на экран ничего не мерцает.
+ Нужно мало памяти. Сможем сделать даже на БК-0010.
+ Быстрее, чем рисовать в экранном буфере и потом копировать его.
Минусы:
- Нужно продумывать детали. Неизведанная территория, так никто ещё не делал.
- Сортировка и поиск в массиве могут оказаться медленными. Суммарного выигрыша в скорости не получится.
Статические объекты (геометрия лабиринта) во всём описанном не участвуют, так как персонажи летают только поверх чёрного фона. Статику рисуем только один раз при входе в новую комнату.
Идея интересная, но скорее всего с быстродействием проблемы будут, много предварительных расчетов. Хотя сам вывод и быстрее будет. Я вот схалтурил, просто при выводе спрайта со всех сторон по точке стираю, быстро и не мерцает. (перед этим спрайт в буфере сдвигаю и BISом накладываю)
/* два спрайта пересекаются в одном экранном слове – неужели в этом месте не мерцает? */
Враги движутся достаточно быстро, определение столкновений - прямоугольник (враги чаще движутся по диагонали), пока заметишь мерцание - уже взрыв нужно выводить.
Возможно я не увидел всех "частных" случаев, буду решать проблемы по мере поступления. (либо маску придется добавлять)
Меня удручает быстродействие. В итоге мудреные алгоритмы придется откинуть. Нужно минимум действий - максимум эффективности.
Я бы сделал с двумя экранами, но пока не могу придумать как их переключать достаточно синхронно.. ПП живет своей жизнью :)
Тогда можно было проверять пространство под спрайтом при выводе (поскольку фон черный) зная квадрат врага выводить пока не будет 1-1.
Кстати в версии для PC просто стирают фон в предыдущем расположении спрайта и никаких морганий.. нам такая роскошь недоступна :)
Пока что столкнулся с дилеммой.
Спрайты лучше хранить по байтам, а не по словам, чтобы меньше места занимали. Ширина получается кратной четырём пикселям, а не восьми, значит для многих (но не для всех) спрайтов мы будем экономить целый столбец байтов. С другой стороны, выводить на экран быстрее словами, чем байтами. Это известная дилемма, но я не о ней. Всё гораздо хуже:
Я составляю список изменений на экране (новый кадр по отношению к старому) в таком виде: значение1, адрес1, значение2, адрес2,.. значениеN, адресN. Если адрес уже встречался в списке, то значение перезаписывается поверх существующего (там коротенький поиск в пределах одной экранной строки).
Поскольку спрайты хранятся байтами, адреса вывода на экран могут быть нечётными. Соседние два байта будут храниться двумя записями с чётным и нечётным адресами. Выходит, что в этом списке все "значения" будут содержать по лишнему пустому байту. Тратится лишнее место. Но главное, что вывод командой MOVB (R1)+,@(R1)+ становится невозможным, так как R1 будет переставляться на следующую запись неправильно.
Придумал хитрость: если адрес нечётный, то переделывать его в чётный и класть "значение" в старший байт. Тогда все адреса в списке получаются чётными, всё круто. Но! Теперь оказывается, что невозможно заранее посчитать сколько слов у нас получится при выводе. Так как чётные и нечётные смешиваются непредсказуемо (зависит от X-координаты каждого спрайта). Раньше с байтами было просто: ширина спрайта (+1 с учётом сдвига) и так для каждого спрайта, приходящегося на эту экранную строку. В сумме знаем сколько байт надо вывести в каждую экранную строку. А теперь при смешивании байтов в слова уже не знаем.
И вот дилемма: придумывать какой-то костыль для правильного подсчёта? Это затормозит работу алгоритма.
Либо хранить спрайты не байтами, а словами, тогда проще подсчёт и проще обращение со списком изменений. Но тормознутей сдвиг спрайтов (потому что становится возможным сдвиг уже не на 3, а на 7 пикселей).
Какой путь выбрать?
Manwe, во всех своих проектах на УКНЦ использую для тайлов/спрайтов хранение словами -- каждое слово это 8x1 пикселей в 4 цвета -- для вывода через ЦП передачей слова сразу в порт. Иначе слишком много времени тратится на подготовку данных для вывода на экран.
Так-то да, но ведь место жалко. Я хочу уложиться в 16 kb БК-0010.
/* Иначе слишком много времени тратится на подготовку данных для вывода на экран. */
При движении спрайта (сдвиге) все равно приходится слова разделять, поскольку в слове лежат две плоскости..
"плоскости" отражают суть процесса, а "план".. :)
Кстати, на счёт плоскостей. Возможно, придётся развернуть алгоритм на 90°
Раньше я перебирал все объекты (спрайты) и заполнял графическими данными массив строк. Проблема была в том, что непонятно какой длины получится каждая строка.
Но, возможно, правильней во внешнем цикле перебирать экранные строки, а во внутреннем цикле для каждой строки смотреть что за объекты в неё попадают, брать из соответствующих спрайтов только по одной строчке графических данных и заносить в массив. Это вроде бы дольше, но зато конечный вывод массива будет «вжух!»
Попробовал идею со строками. Назвал этот подход "scanlines".
Для компьютеров с маленьким объёмом памяти идея очень вдохновляющая: двойной буфер экрана, размер которого... не зависит от размера основного экрана! А зависит лишь от площади присутствующих на экране анимированных спрайтов. В моём эксперименте с семью спрайтами размер буфера получился всего 776 байт.
Но как обычно, есть и потери: скорость снизилась раза в три по сравнению с прошлым алгоритмом. Надо думать как оптимизировать.
Может быть более эффективно отсекать объекты, которые не попадают в scanline. Возможно, отслеживать когда спрайт полностью нарисован и ставить ему признак "сразу пропускать" для следующих сканлайнов.
Вторая причина тормозов: обращение к спрайту происходит не один раз за кадр, а много раз (столько, сколько строк на экране), и каждый раз вычисляется смещение в спрайте, чтобы выбрать соответствующую строку пикселей. Попробую запоминать прошлый указатель на графические данные спрайта, а не пересчитывать его заново при каждом обращении.
В целом такие оптимизации помогут ускорить раза в два, наверное. Перспективы есть :)
P.S. ещё идея со сканлайнами интересна тем, что она решает проблему выхода (полного или частичного) спрайта за пределы отображаемой области.
S_V_B, достаточно шестнадцати движущихся объектов для каждого экрана? Если да, то попробую массив слов, в которых каждый бит отвечает за 1 объект - присутствует он в данной экранной строке или нет.
7 объектов максимум.
Как вычисляются экранные координаты для отрицательных Х?
/* Считая всякие искрящиеся разряды? */
Да.
я сначала хотел вращать спрайты там же где они лежат, что бы каждый раз только один сдвиг делать, двигаемся же по одной точке.
потом сделал ASHC.
Чтобы не моргало через буфер фон->BIC (x-1)->BIS(X)->MOV.
В итоге ломаю голову как с периферийным процессором подружиться (диспетчер процессов) чтобы два экрана переключать.
Так будет проще и быстрее.
"Аппаратные" спрайты УКНЦ.. вещь сомнительная, скорее всего под знакогенератор заточена.