Несчастное совпадение - это однофайловая демо-версия c64, выпущенная 14 января 2021 года на специальном конкурсе, допускающем только спрайты. В этой статье я хотел бы дать вам обзор того, что происходит под капотом.
Эффект
Идея основного эффекта заключается в наложении горизонтальных полос растра слоем спрайтов, представляющих вертикальные цветные полосы с чередующимися включенными / выключенными пикселями, чтобы показать «прозрачную» решетку. В результате получается безумно выглядящий эффект плазмы с множеством цветов.
История
Я уже делал версию этого эффекта в режиме gfx для демо 25 Years Atlantis. В этой демонстрации чрезмерно использовалась сетка gfx вместо спрайтов, поэтому я мог иметь 2 цвета на вертикальный столбец char ($ d800 не использовался) с пикселями шириной в два пикселя. Я использовал уловку, чтобы избежать плохих строк (выборка Char DMA), поэтому вертикальные цвета в gfx пришлось обновлять только для первой строки char (40 байт), а не для всего экрана (1000 байт). Это позволило эффекту работать в полнокадровом режиме со скоростью 50 кадров в секунду. (Я очень горжусь этим эффектом, так как получил аплодисменты во время презентации на X’2016)
Версия только для спрайтов
Поэтому, когда я впервые услышал о Композиции только для спрайтов Рейстлина в CSDB, мне сразу пришло в голову, что это нужно делать со спрайтами и открытыми границами.
Я знал, что будут ограничения по сравнению с исходным эффектом, но надеялся, что он тоже будет хорошо смотреться.
- Чтобы покрыть всю ширину экрана, мне нужно использовать все 8 спрайтов, развернутых по оси x, что удваивает разрешение пикселей по оси x до 2 пикселей.
- У меня может быть только 3 цвета для наложения, так как спрайты могут иметь только 3 цвета, два из которых фиксированные, а один свободно настраиваемый. Для простоты я решил использовать 3 фиксированных цвета для всех спрайтов. Включение многоцветного снова удваивает разрешение пикселей по оси x, поэтому разрешение по оси x будет 4 пикселя.
Поскольку я планировал обновлять цвета растровой полосы на каждой четвертой растровой строке, наличие вертикальных полос шириной 4 пикселя оказалось приемлемым ограничением.
Первый прототип
Для начала я хотел увидеть, как происходит синхронизация с открытыми границами, полосами высотой в четыре пикселя и наложенными расширенными мультиплексированными спрайтами по оси X. Спрайты в этом примере темно-серого цвета со всеми установленными пикселями края спрайта. В этот момент я был очень взволнован тем, что могу это сделать.
Проблема
Проблема возникла после того, как я добавил расчет цветовой анимации и обновления спрайтов. Как вы можете видеть ниже, некоторые строки отображаются дважды, и это потому, что спрайты c64 имеют нечетное количество строк. Размещение одного и того же спрайта друг под другом вызовет это, поскольку первая и последняя строки спрайта одинаковы.
К сожалению, невозможно остановить рисование спрайта после его запуска, поэтому VIC будет отображать все строки, а мультиплексирование нового набора спрайтов также невозможно, пока не будут отображены все предыдущие строки спрайтов.
Что можно сделать, так это изменить указатель спрайта для каждой строки, чтобы VIC читал разные данные для четных и нечетных строк. Для этого мне пришлось в два раза больше рассчитывать, чем планировал изначально. Теперь из-за этой ошибки мне пришлось обновить 16 спрайтов вместо 8. Один набор содержит байты пикселей, например% 10101010, а другой -% 01010101, от 1 до 21 строки спрайтов.
Поскольку у меня не оставалось растрового времени для обновления 16 спрайтов в кадре, мне пришлось поместить расчет спрайтов в основной поток выполнения и использовать двойную буферизацию (с использованием переключателя банка VIC), чтобы скрыть сбои обновления.
Затем мне пришлось переписать код синхронизации, чтобы добавить переключатель $ d018 на строку для переключения между двумя наборами spite. (Наконец, у меня осталось 3 цикла на строку, уф ...)
Обновление: Освальд / Ресурс указал, что я мог бы использовать растяжение спрайта, чтобы решить проблему, вызванную нечетным количеством строк спрайта. (Поскольку я никогда не использовал растягивание спрайтов, эта идея никогда не приходила мне в голову.)
Итак, изучив трюк с растяжкой спрайта, я обновил свой прототип за 15 минут, чтобы использовать растяжение спрайта, и да, это позволяет мне удалить мультиплексор + только первая строка должна быть обновлена в спрайте, который растягивается (повторяется) на всю высоту эффекта. Это экономит 3840 циклов процессора, поэтому теперь в моей новой версии у меня есть время также обновлять анимацию спрайтов за каждый кадр.
Небольшое утешение в том, что если бы я использовал растяжку, я не смог бы разместить логотип «Совпадение» прямо под эффектом, потому что VIC визуализирует оставшиеся 20 строк спрайта после остановки растяжения.
Отскок
Поначалу добавление отскока казалось простым, поскольку я представлял, что все, что мне нужно сделать, это обновить позицию прерывания растра, и у меня все в порядке. Очевидно, я забыл о координатах y спрайта. Поэтому мне пришлось еще раз настроить точный код синхронизации, чтобы считывать координаты y для мультиплексора с адресов нулевой страницы, которые я обновляю до конца предыдущего кадра.
//------------------------------------------------------------------ irq: { irqStart ldy $10 // set y position of first sprite row ldx sine,y stx $d012 inx // add an offset of two as we need inx // two rasters to get a stable raster stx $d001 stx $d003 stx $d005 stx $d007 stx $d009 stx $d00b stx $d00d stx $d00f lda #$00 // acknowledge main thread to sta mloop.ack // continue updating sprites lda mloop.flp // set the correct video bank bne !+ // (double buffering for sprites) lda #%00000010 // VIC Bank to $4000-$7fff sta $dd00 jmp cont !: lda #%00000001 // VIC Bank to $8000-$bfff sta $dd00 cont: mw #mainEffect : $fffe // set next irq address irqEnd .align $100 mainEffect: { irqStart_stableRaster pause_looped #36 ldy #$00 // y holds the raster line counter jmp cont // align to $100 memory address .align $100 cont: .var rastercnt = 0; .var spritecnt = 0; .var step = -1; // we have 11 sets of 16 pixel high segments .for(var j=0;j<11;j++) { .var num = 4 .if (j == 10) { .eval num = 2 } // each segment but the last has 4 subsegments // each sub segment is 4 rasters high .for(var i=0;i<num;i++) { // the first line is the most exciting // update $d021, $d018, sprite pointers... ldx d018tab,y lda #0 dec $d016 sta $d021 stx $d018 // zeropage addresses from $f0 hold the // new y coordinates for the multiplexer ldx $f0 + spritecnt bit $00 inc $d016 .if (step < 3) { .eval step += 1; } .if (j<9) { stx $d001 + step*4 stx $d003 + step*4 } else { lda $1000 // dummy do nothing lda $1000 } iny // for the remaing 3 lines I use a cycle ldx #3 lp: nop nop nop nop dec $d016 inc $d016 bit $00 lda d018tab,y sta $d018 nop nop nop nop iny dex bne lp .if (i<3) { bit $00 } else { jmp next .align $100 } .eval rastercnt += 4; .if (floor(rastercnt/21) == 1) { .eval rastercnt -= 21; .eval spritecnt += 1; .eval step = -1; } } next: } nop nop lda #$00 dec $d016 // open sideborder for the last line inc $d016 sta $d021 irqEnd #irqBorder : #[*+25] irqStart lda #$00 // screen off 24 rows sta $d011 irqEnd #irqBorderOff : #[*+25] irqStart lda #%00001000 // screen off 25 rows sta $d011 // (top border gets opened) inc $10 // calculate sprite y positions for ldy $10 // the multiplexer of the next frame ldx sine,y inx inx txa clc adc #21 sta $f0 adc #21 sta $f1 adc #21 sta $f2 adc #21 sta $f3 adc #21 sta $f4 adc #21 sta $f5 adc #21 sta $f6 jsr anim_horiz jsr music.play dec $d021 irqEnd #irqMusic : #irq } }
Логотипы
Как только отскок был на месте, у меня возникла идея размещать логотипы сверху и снизу, когда пространство открывается. Здесь я должен был быть осторожным, чтобы логотип не попал в основной эффект, иначе время эффекта нарушится. Для этого я выбрал довольно простое решение, используя таблицу, которая сообщает мне для каждого кадра, безопасно ли отображать логотип или нет. Путем проб и ошибок я изменял содержимое этой таблицы до тех пор, пока она не работала так, как я хотел.
Прокрутка боковой границы
Относительно простой нанимает расширенный по оси X набор спрайтов, используя инструкцию ROL для прокрутки содержимого от спрайта к спрайту. Для увеличения скорости я перемежал горизонтальный (используемый для растров) код расчета плазмы в коде открытия границы. На этот раз мне повезло, так как обновление одного ряда плазмы составляет 17 циклов, два из которых - ровно столько, сколько свободных циклов у меня было между открытием.
// open 11 rasterlines (interleave plazma code inbetween) .for(var i=0; i<11; i++) { dec $d016 inc $d016 plazma_17c(0+i*2) plazma_17c(1+i*2) }
Поскольку я боялся, что добавление этого скроллера слишком сильно замедлит основной эффект, я решил использовать кодировку высотой всего 5 пикселей, и пропуск каждой второй строки все равно дал бы разумную высоту символа. В конце концов, на мой взгляд, он отлично смотрится с финальной продукцией.
Музыка
Я не хотел налагать ограничения на музыку, поэтому я держал в уме место и растровое время для музыки на протяжении всего проекта. Использует sidwizard demo player 20/25 растровых линий.
Мне это нравится, Винченцо проделал отличную работу!
Полные кредиты
Код: Strepto
Музыка: Винченцо
Графика: Grass
Спасибо за чтение! Мне было очень весело работать над этой демонстрацией, надеюсь, она вам понравится!