Эмуляция мерцания спрайтов старой школы (теория и концепция)

Я пытаюсь разработать старую школьную видеоигру в стиле NES с мерцанием спрайтов и графическим замедлением. Я думал о том, какой тип логики я должен использовать для включения таких эффектов.

Я должен учитывать следующие ограничения, если я хочу пойти в стиле старой школы NES:

  • Не более 64 спрайтов на экране за раз
  • Не более 8 спрайтов на строку сканирования или для каждой строки по оси Y
  • Если на экране происходит слишком много действий, система замерзает изображение для кадра, чтобы процессор мог догнать действие

Из того, что я прочитал, если на экране было больше 64 спрайтов, разработчик будет рисовать только высокоприоритетные спрайты, игнорируя при этом низкоприоритетные. Они также могут чередовать, рисуя каждый четный спрайт на противоположных кадрах из нечетных номеров.

Вопрос о сканировании интересен. Из моего тестирования невозможно получить хорошую скорость на XBOX 360 XNA, рисуя пиксели по пикселям, как это сделала NES. Вот почему в школьных играх, если в одной строке было слишком много спрайтов, некоторые появлялись бы, если бы их сокращали вдвое. Для всех целей для этого проекта я делаю сканированные линии высотой 8 пикселей и группирую спрайты на каждой линии сканирования по их позиционированию Y.

Чтобы уточнить, я бы рисовал спрайты на экране партиями размером 8x8 пикселей, а не 1x1.

Итак, ошарашенный, мне нужно найти решение, которое...

  • 64 спрайтов на экране сразу
  • 8 спрайтов на 'scanline'
  • Может рисовать спрайты на основе приоритета
  • Может чередоваться между спрайтами на кадр
  • Эмуляция замедления

Вот моя нынешняя теория

Прежде всего, основная идея, которую я придумал, - это приоритет спрайта. Если предположить, что значения между 0-255 (0 являются низкими), я могу назначить уровни приоритетов спрайтов, например:

  • От 0 до 63
  • От 63 до 127 - средний.
  • От 128 до 191
  • Максимум от 192 до 255

В моих файлах данных я могу назначить каждый спрайт определенным приоритетом. Когда родительский объект создается, спрайт произвольно получает назначенное число между его назначенным диапазоном. Затем я рисую спрайты по порядку от высокого к низкому, с конечной целью рисования каждого спрайта.

Теперь, когда спрайт нарисован в кадре, я тогда произвольно сгенерировал бы его новое значение приоритета в пределах его начального уровня приоритета. Однако, если спрайт не получается рисоваться в кадре, я мог бы добавить 32 к его текущему приоритету. Например, если система может только рисовать спрайты до уровня приоритета 135, тогда спрайт с начальным приоритетом 45 может быть нарисован после того, как 3 кадра не будут оттянуты (45 + 32 + 32 + 32 = 141)

Это теоретически позволило бы спрайтам чередовать кадры, разрешать уровни приоритетов и ограничивать спрайты до 64 на экран.

Теперь интересный вопрос заключается в том, как ограничить спрайты только 8 на строку сканирования?

Я думаю, что если я сортирую приоритеты спрайтов с низким приоритетом, перебираю цикл, пока не удалю 64 спрайта. Однако я не должен просто взять первые 64 спрайтов в списке.

Прежде чем рисовать каждый спрайт, я мог бы проверить, сколько спрайтов было нарисовано в соответствующей строке с помощью счетных переменных. Например:

  • Значения Y от 0 до 7 относятся к Scanline 0, scanlineCount [0] = 0
  • Значения Y от 8 до 15 относятся к Scanline 1, scanlineCount [1] = 0
  • и др.

Я мог бы reset значения для каждой строки сканирования для каждого кадра. При смене списка спрайтов добавьте 1 к соответствующему счетчику scanline, если спрайт будет нарисован в этой строке сканирования. Если он равен 8, не рисуйте этот спрайт и перейдите к спрайту со следующим низким приоритетом.

Slowdown

Последнее, что мне нужно сделать, это эмулировать замедление. Моя первоначальная идея заключалась в том, что если я рисую 64 спрайта на кадр и еще больше спрайтов, которые нужно нарисовать, я могу приостановить рендеринг на 16 мс или около того. Тем не менее, в играх NES, которые я играл, иногда наблюдается замедление, если не происходит мерцания спрайтов, в то время как игра прекрасно перемещается, даже если есть какое-то мерцание спрайтов.

Возможно, дайте значение каждому объекту, который использует спрайты на экране (например, значения приоритета выше), и если объединенные значения всех объектов w/спрайтов превышают порог, введите замедление?

В ЗАКЛЮЧЕНИИ...

Разве все, что я написал, на самом деле звучит законно и может работать, или это мечта? Какие улучшения вы можете подумать с этой теорией программирования игр?

20
задан Jeffrey Kern 05 июня '10 в 14:47
источник поделиться
2 ответов

Во-первых, мне нравится идея. При создании ретро-игры было бы не так, если бы вы проигнорировали ограничения сложности. Написав фреймворк, который тип принудительно его является отличной идеей.

Могу ли я просто сказать, что если вы отвлеките движок как графический движок с открытым исходным кодом "Retro Game", это действительно здорово, и я бы обязательно присоединился к нему!

  • Что касается ваших спрайтов с наивысшим приоритетом, возможно, Priority Queue упростит эту часть, поскольку вы можете просто вытащить 64 спрайта с наивысшим приоритетом.

  • При замедлении, возможно, в двигателе вы можете иметь limitValue. Каждый спрайт как подходящий limitUse. Добавьте все limitUse vars, и если это ниже limitValue, не замедляйтесь. Теперь, для slowDelay = f(limitUseTotal - limitValue), где f - функция, которая преобразует избыточное количество спрайтов в вычисленное значение замедления. Это общая идея, которую вы предлагаете, или я ее неправильно понял?

3
ответ дан Dan McGrath 05 июня '10 в 15:03
источник поделиться

Для приоритета используйте Z-index. Это то, что делает GBA, по крайней мере; Приоритет 0 оказывается самым передним, поэтому имеет самый слабый приоритет. Таким образом, от высокой до низкой, и если вы сделали слишком много спрайтов, остановитесь.

Приоритет спрайта определяется его индексом в таблице спрайтов. В GBA было 128 записей [0,127], которые были расположены как последовательные четыре hwords (по 16 байт). Для XNA вы, вероятно, используете вместо List<Sprite> или аналогичный. Может быть, Sprite[256] для упрощения вещей.

Ограничение рендеринга спрайта естественно вытекает из этого механизма. Поскольку вы показываете от 255 до 0, вы можете отслеживать, какие строки сканирования вы затронули. Итак, в основном:

int[] numPixelsDrawnPerScanline = new int[Graphics.ScreenHeight];

foreach(Sprite sprite in SpriteTable.Reverse()) {
    int top = Math.Max(0, sprite.Y);
    int bottom = Math.Min(Graphics.ScreenHeight, sprite.Y + sprite.Height);

    // Could be rewritten to store number of pixels to draw per line, starting from the left.
    bool[] shouldDrawOnScanline = new bool[Graphics.ScreenHeight];

    for(int y = top; y < bottom; ++y) {
        bool shouldDraw = numPixelsDrawnPerScanline[y] + sprite.Width <= PixelsPerScanlineThreshold;

        shouldDrawOnScanline[y] = shouldDraw;

        if(shouldDraw) {
            numPixelsDrawnPerScanline[y] += sprite.Width;
        }
    }

    Graphics.Draw(sprite, shouldDrawOnScanline); // shouldDrawOnScanline defines clipping

    skipDrawingSprite:
}

Я не совсем уверен, что вы подразумеваете под замедлением, поэтому я не могу ответить на эту часть вашего вопроса. К сожалению.

Надеюсь, что это поможет.

3
ответ дан strager 05 июня '10 в 15:20
источник поделиться

Другие вопросы по меткам