Los sprites (también llamados OBJ - Objects) son elementos gráficos que se mueven independientemente de los fondos. La SNES tiene capacidades avanzadas para manejar sprites:
La memoria de atributos de objetos (OAM) tiene dos partes:
| Dirección | Tamaño | Propósito |
|---|---|---|
| $0200-$03FF | 512 bytes | Tabla baja (atributos básicos) |
| $0400-$041F | 32 bytes | Tabla alta (tamaño y bits extra de X) |
Cada sprite usa 4 bytes:
Byte 0: X (bits 0-7)
Byte 1: Y
Byte 2: Número de tile
Byte 3: Atributos:
Bit 0-2: Paleta (0-7)
Bit 3: Prioridad (0=alta, 1=baja)
Bit 4-5: Sin uso
Bit 6: Flip horizontal (1=activado)
Bit 7: Flip vertical (1=activado)
Cada byte controla 4 sprites (2 bits por sprite):
Bits 0-1: Sprite 0
Bits 2-3: Sprite 1
...
Valores:
00: Tamaño pequeño (8x8 o 16x16 según OBJSEL)
01: Tamaño mediano (8x16 o 16x32)
10: Tamaño grande (16x16 o 32x32)
11: Tamaño extra grande (16x32 o 32x64)
Bit 0-2: Dirección base de tiles en VRAM (en bloques de $2000)
Bit 3: Selección de nombres de sprite (0=$0000, 1=$1000)
Bit 4-5: Tamaño de sprites pequeños:
00: 8x8 y 16x16
01: 8x8 y 32x32
10: 8x8 y 64x64
11: 16x16 y 32x32
Bit 6-7: Tamaño de sprites grandes (combinación con bits 4-5)
; Configurar sprites:
; - Tiles en VRAM $0000-$1FFF
; - Sprites pequeños: 16x16
; - Sprites grandes: 32x32
lda #%01000010 ; Bits 6-7: 01, Bits 4-5: 00, Bit 3: 0, Bits 0-2: 010
sta $2101 ; Registro OBJSEL
Los sprites se cargan en VRAM (memoria de video) usando DMA:
; Cargar gráficos de sprite en VRAM $0000
LoadSpriteGraphics:
lda #$80
sta $2115 ; Incremento de VRAM +1 después de $2119
lda #$00 ; Dirección VRAM $0000
sta $2116
lda #$00
sta $2117
lda #$01 ; DMA desde banco $01
sta $4304
lda #$18 ; Puerto $2118 (VRAM data)
sta $4301
ldx #sprite_data ; Dirección fuente
stx $4302
ldx #$2000 ; Tamaño: 8KB
stx $4305
lda #$01 ; Modo 1 (transferencia de bytes)
sta $4300
lda #$01 ; Iniciar DMA canal 1
sta $420B
; Actualizar posición de sprite 0
UpdateSpritePosition:
lda sprite_x ; Variable de posición X
sta $0200 ; Byte bajo X
lda #$00 ; Byte alto X (bit 9)
sta $0201
lda sprite_y ; Variable de posición Y
sta $0202
lda #$01 ; Tile número 1
sta $0203
lda #%00110011 ; Paleta 3, prioridad 1, no flip
sta $0204
; Actualizar OAM mediante DMA
lda #$02 ; DMA canal 2
sta $420B
Técnica para mostrar más de 32 sprites por línea:
; Ejemplo básico de multiplexing
HandleSpriteMultiplexing:
ldx #$00 ; Contador de sprites
ldy #$00 ; Índice OAM
ProcessSprite:
lda sprite_y,x ; Obtener Y del sprite
cmp scanline ; Comparar con línea actual
bcc SkipSprite ; Si está arriba, saltar
; Copiar datos del sprite a OAM
lda sprite_x,x
sta $0200,y
; ... copiar otros atributos
iny
iny
iny
iny ; Avanzar 4 bytes por sprite
SkipSprite:
inx
cpx #$80 ; Máximo 128 sprites
bne ProcessSprite
Para sprites con más detalle:
; Configurar paleta para sprite de 16 colores
; Paleta 0: 16 colores (2 tiles consecutivos)
lda #%00000000 ; Paleta 0, prioridad 0, no flip
sta $0203 ; Atributos del sprite
; En la paleta:
; Color 0 es transparente
; Colores 1-15 son los colores visibles
; Configurar sprite grande (32x32)
; Primero configurar OBJSEL para tamaños grandes
lda #%01100010 ; Sprites grandes: 32x32
sta $2101
; Luego en OAM high table:
lda #%01010101 ; Todos los sprites en este grupo como grandes
sta $0400 ; Configura 4 sprites a la vez
; Los tiles deben organizarse en VRAM:
; Tile 0: esquina superior izquierda
; Tile 1: superior derecha
; Tile 2: inferior izquierda
; Tile 3: inferior derecha
; Variables para animación
animation_timer: .db 0
animation_frame: .db 0
UpdateAnimation:
dec animation_timer
bne NoFrameChange
lda #$08 ; Velocidad de animación
sta animation_timer
inc animation_frame
lda animation_frame
and #$03 ; Ciclar entre 0-3
sta animation_frame
; Calcular tile basado en frame
asl a ; Multiplicar por 2 (tiles de 16x16)
clc
adc #$01 ; Tile base
sta $0203 ; Actualizar tile del sprite
NoFrameChange:
; Tabla de animaciones
AnimationTable:
.dw WalkRightFrames, JumpFrames, AttackFrames
WalkRightFrames:
.db $01, $02, $03, $02 ; Secuencia de tiles
.db $FF ; Marcador de fin
; Sistema de animación avanzado
UpdateComplexAnimation:
ldx animation_state
lda AnimationTable,x
sta animation_ptr
lda AnimationTable+1,x
sta animation_ptr+1
ldy animation_frame
lda (animation_ptr),y
cmp #$FF
bne ValidFrame
; Reiniciar animación
ldy #$00
lda (animation_ptr),y
ValidFrame:
sta $0203 ; Actualizar tile
iny
sty animation_frame
; Verificar colisión entre sprite A y B
CheckCollision:
; Coordenada X derecha de A >= X izquierda de B
lda sprite_a_x
clc
adc #$0F ; Ancho del sprite (16px)
cmp sprite_b_x
bcc NoCollision
; Coordenada X izquierda de A <= X derecha de B
lda sprite_b_x
clc
adc #$0F
cmp sprite_a_x
bcc NoCollision
; Repetir para Y (vertical)
lda sprite_a_y
clc
adc #$0F
cmp sprite_b_y
bcc NoCollision
lda sprite_b_y
clc
adc #$0F
cmp sprite_a_y
bcc NoCollision
; Colisión detectada
sec
rts
NoCollision:
clc
rts
La SNES tiene un registro que detecta colisiones entre sprites y fondos:
; Esperar colisión
WaitForCollision:
lda $4212 ; Registro HVBJOY
and #$40 ; Comprobar flag de colisión
beq WaitForCollision
; Leer resultados
lda $4218 ; Coordenada X del sprite que colisionó
lda $4219 ; Coordenada Y
lda $421A ; Número de sprite (bits 0-6)
; Bit 7: 1=colisión con fondo
; Ordenar sprites por coordenada Y para optimizar
SortSpritesByY:
ldx #$00
ldy #$00
SortLoop:
lda sprite_y,x
cmp sprite_y,y
bcc NoSwap
; Intercambiar posiciones
pha
lda sprite_y,y
sta sprite_y,x
pla
sta sprite_y,y
; Intercambiar otros atributos...
NoSwap:
iny
cpy #$80
bne SortLoop
inx
cpx #$7F
bne SortLoop
; Variables del jugador
player_x: .dw $0080
player_y: .dw $00A0
player_frame: .db $00
player_dir: .db $00 ; 0=derecha, 1=izquierda
UpdatePlayerSprite:
; Actualizar posición X
lda player_x
sta $0200
lda player_x+1
sta $0201
; Actualizar posición Y
lda player_y
sta $0202
; Determinar tile basado en dirección y frame
lda player_dir
beq FacingRight
; Mirando izquierda (usar tiles 4-7)
lda player_frame
clc
adc #$04
bra SetTile
FacingRight:
lda player_frame
SetTile:
sta $0203
; Configurar atributos
lda player_dir
beq NoFlip
lda #%01000000 ; Flip horizontal
bra StoreAttrs
NoFlip:
lda #%00000000
StoreAttrs:
ora #%00110000 ; Paleta 3, prioridad 1
sta $0204
; Estructura de partícula (4 bytes)
.struct Particle
y_pos .byte
x_pos .byte
tile .byte
attrs .byte
.endst
; Inicializar partículas
InitParticles:
ldx #$00
lda #$F0 ; Y fuera de pantalla
InitLoop:
sta particle_y,x
inx
cpx #MAX_PARTICLES*4
bne InitLoop
; Actualizar partículas
UpdateParticles:
ldx #$00
ParticleLoop:
lda particle_y,x
cmp #$F0
beq NextParticle
; Mover partícula
dec particle_y,x
; Verificar si salió de pantalla
cmp #$F0
bne NextParticle
; Desactivar partícula
lda #$F0
sta particle_y,x
NextParticle:
txa
clc
adc #.sizeof(Particle)
tax
cmp #MAX_PARTICLES*.sizeof(Particle)
bne ParticleLoop