Эмуляция старой школы мерцания спрайтов (теория и концепция) - PullRequest
20 голосов
/ 05 июня 2010

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

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

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

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

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

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

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

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

Вот моя текущая теория

Прежде всего, я пришел к фундаментальной идее, касающейся приоритета спрайтов. Предполагая значения между 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
  • и т.д.

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

Slowdown

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

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

В заключение ...

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

Ответы [ 2 ]

3 голосов
/ 05 июня 2010

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

Приоритет спрайта определяется его индексом в таблице спрайтов. GBA имел 128 записей [0,127], расположенных в виде четырех последовательных слов (по 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 голосов
/ 05 июня 2010

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

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

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

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

...