En esta entrada cuento qué ha cambiado en la rama feat-4px-scroll-and-sprite-animation respecto a la versión anterior del port. En resumen:
- Scroll fino a 4px por frame en lugar del desplazamiento al carácter.
- Animación del sprite del pájaro (frames alternos) para aportar fluidez.
- Ajustes en el pipeline de render para evitar tearing: cálculo previo en
screenBuffery un únicomemcopyal final del frame. - Revisión de colisión para funcionar correctamente con el scroll de 4px.
Motivación
El problema con la versión anterior era que el desplazamiento iba a saltos de un carácter entero (columnas de 8×8 píxeles), lo que hacía el movimiento horizontal bastante brusco. Quería algo más suave sin tocar la geometría del juego, y pasar a 4px por frame fue la solución.
Implementación técnica (resumen)
-
Scroll a 4px:
-
El scroll horizontal suave se consigue alternando los atributos tanto de las columnas de tubería como del suelo entre valores normales (
ATTR_PIPE,ATTR_SKY, etc.) y valores especiales (ATTR_FIRST_HALFyATTR_LAST_HALF) en frames pares e impares (worldCol Mod 2). - El patrón de píxeles
halfTile(mitad izquierda del carácter encendida) permite que, al combinarse con estos atributos, cada carácter se pinte visualmente partido: la mitad izquierda con un color (ink/paper) y la derecha con otro. - Así, tanto las tuberías como el suelo parecen avanzar 4px cada frame, aunque la memoria de atributos sigue siendo de 32 columnas. No se duplica la resolución lógica ni se hace un desplazamiento por píxel real.
- Fragmento clave del código (
src/draw.bas):
If worldCol Mod 2 = 1 Then
attrFront = ATTR_FIRST_HALF
attrBack = ATTR_LAST_HALF
Else
attrFront = ATTR_PIPE
attrBack = ATTR_SKY
End If
...
bufferPipeColumn(leadingCol, gap, attrFront)
bufferPipeColumn(trailingCol, gap, attrBack)
-
Animación del sprite:
-
El sprite del pájaro ahora tiene 3 frames en
src/spriteset.bas. El juego alterna el índice del frame cada N frames (por ejemplo, cada 6 frames) para dar sensación de aleteo. -
Código de animación:
Sub drawBird()
' Calculate yo-yo animation frame (0, 1, 2, 1) changing every 2 ticks
Dim animIdx As Ubyte = (worldCol / 2) Mod 4
If animIdx = 3 Then animIdx = 1
' Draw pixels based on animation frame
If animIdx = 0 Then
putChars(birdX, Int(birdYPos), 2, 2, @sprite0(0))
ElseIf animIdx = 1 Then
putChars(birdX, Int(birdYPos), 2, 2, @sprite1(0))
Else
putChars(birdX, Int(birdYPos), 2, 2, @sprite2(0))
End If
' Paint bird with Yellow Ink (6) and Blue Paper (1) -> 14
paint(birdX, Int(birdYPos), 2, 2, 14)
End Sub
Compatibilidad con colisión y lógica existente
Al introducir el scroll a 4px, el pájaro puede quedarse durante varios frames a caballo entre dos columnas de atributos, lo que rompía las colisiones. Para arreglarlo sin complicar demasiado el código:
-
La comprobación de colisión ahora usa coordenadas de juego (geométricas): se calcula si el rectángulo del pájaro intersecta la geometría de una tubería, en lugar de leer atributos redondeados a la columna. Así se evitan falsos positivos/negativos en la fase intermedia del scroll.
-
Seguimos comprobando solo la tubería frontal (la que puede estar en contacto), pero con posiciones y anchuras/gaps en píxeles.
Rendimiento y timing
Me preocupaba que estos cambios afectasen la tasa de frames, pero por suerte no ha sido así. El coste extra del scroll a 4px y la animación del sprite es mínimo comparado con hacer múltiples escrituras directas a la RAM de atributos. La estrategia sigue siendo la misma: calcular el screenBuffer completo antes del waitretrace y hacer un único memcopy durante la ventana de retrace para evitar el tearing.
Qué notarán los jugadores
- El mundo se mueve más suave: 4px por frame en lugar de saltos de carácter.
- El pájaro aletea con un sprite animado.
- Las colisiones son más precisas gracias a la comprobación geométrica en píxeles.
Enlaces
- Código de esta mejora (rama): feat/4px-scroll-and-sprite-animation