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