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 个精灵
  • 背景/精灵合成策略
  • sprite 0 hit
  • sprite overflow
  • pygame

运行本课

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