NES TDD

304. Отрисовка одного спрайта 8x8

Отрисовать один спрайт 8x8 в данные фреймбуфера.

Урок 304 из 356 · tests/chapter_09_sprite_rendering/test_304_render_one_sprite_8x8.py

Изменяемый файл

emulator/rendering/sprite_renderer.py

Зачем нужен этот шаг

Предыдущие шаги, посвящённые спрайтам, декодировали записи OAM, декодировали атрибуты спрайтов и построили палитры спрайтов. Теперь мы рисуем один спрайт в чистый Framebuffer.

Это первый шаг со спрайтами, который создаёт видимые данные пикселей, но он всё ещё небольшой и контролируемый:

one SpriteEntry
one CHR tile
one selected sprite palette
one Framebuffer target

Предлагаемый пример реализации

def render_sprite_8x8_to_framebuffer(
    framebuffer: Framebuffer,
    sprite: SpriteEntry,
    pattern_table: bytes,
    sprite_palettes: SpritePalettes,
) -> None:
    attributes = decode_sprite_attributes(sprite.attributes)

    tile_start = sprite.tile_index * 16
    tile_end = tile_start + 16

    if tile_end > len(pattern_table):
        raise ValueError("Pattern table does not contain sprite tile bytes")

    tile_bytes = pattern_table[tile_start:tile_end]
    color_indexes = decode_chr_tile(tile_bytes)
    palette = sprite_palettes[attributes.palette_id]

    for tile_y in range(8):
        for tile_x in range(8):
            color_index = color_indexes[tile_y][tile_x]

            if color_index == 0:
                continue

            screen_x = sprite.x + tile_x
            screen_y = sprite.y + tile_y

            if not (0 <= screen_x < framebuffer.width):
                continue
            if not (0 <= screen_y < framebuffer.height):
                continue

            framebuffer.set_pixel(screen_x, screen_y, palette[color_index])

Важное правило прозрачности

Для спрайтов индекс CHR-цвета 0 прозрачен. Он не должен перезаписывать существующий пиксель фреймбуфера.

Важное упрощение Y-позиции

В реальном NES позиционирование спрайтов по Y имеет аппаратную особенность: сохранённое в OAM значение Y не совпадает в точности с видимой верхней строкой развёртки. На этом шаге руководства исходное значение sprite.y используется как экранная позиция Y. Более точное поведение тайминга/позиционирования можно добавить позже.

Вне рамок этого шага

  • поддержка горизонтального/вертикального отражения
  • приоритет спрайтов позади фона
  • отрисовка всех 64 спрайтов
  • политика композитинга фона и спрайтов
  • попадание спрайта 0
  • переполнение спрайтов
  • pygame

Запустить этот урок

uv run pytest tests/chapter_09_sprite_rendering/test_304_render_one_sprite_8x8.py -v