Несчастное совпадение - это однофайловая демо-версия 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

Спасибо за чтение! Мне было очень весело работать над этой демонстрацией, надеюсь, она вам понравится!