Ensamblador para ZX Spectrum – Pong: $09 Detección de colisiones
Detección de colisiones, cada vez más cerca de nuestro fin.

Ensamblador para ZX Spectrum – Pong: Paso 7, detección de colisiones
Creamos la carpeta Paso07 y copiamos desde la carpeta Paso06 los archivos Controls.asm, Game.asm, Main.asm, Sprite.asm y Video.asm.
A partir de aquí, vamos a utilizar todo lo que hemos implementado hasta ahora, evolucionándolo.
Vamos a implementar la detección de colisiones de la bola con las palas. Para ello necesitamos definir la columna en la que se produce dicha colisión, lo que vamos a hacer en Sprite.asm:
CROSS_LEFT: EQU $01 CROSS_RIGHT: EQU $1d
Para comprobar la colisión en la coordenada X, vamos a usar la columna. Para comprobar la colisión en la coordenada Y vamos a usar tercio, línea y scanline.
Como hemos visto anteriormente, la composición de la coordenada Y se encuentra en dos bytes distintos (010T TSSS LLLC CCCC), por lo que vamos a implementar una rutina que reciba una posición de memoria de la pantalla y devuelva la coordenada Y (TTLLLSSSS).
La rutina la vamos a implementar en Video.asm, tras la rutina Cls, y recibe la posición de memoria de la pantalla en HL y devuelve la coordenada Y obtenida, en A:
GetPtrY: ld a, h and $18 rlca rlca rlca ld e, a
Cargamos el tercio y scanline en A, LD A, H, nos quedamos con el tercio, AND $18, lo pasamos a los bits 6 y 7, RLCA, RLCA, RLCA, y cargamos el resultado en E, LD E, A.
ld a, h and $07 or e ld e, a
Volvemos a cargar tercio y scanline en A, LD A, H, nos quedamos con el scanline, AND $07, le agregamos el tercio, OR E, y cargamos el resultado en E, LD E, A.
ld a, l and $e0 rrca rrca or e ret
Cargamos la línea y columna en A, LD A, L, nos quedamos con la línea, AND $E0, ponemos el valor en los bits 3 a 5, RRCA, RRCA, y le añadimos tercio y scanline, OR E.
El aspecto final de la rutina es:
; – --------------------------------------------------------------------------- ; Obtiene tercio, línea y scanline de una posición de memoria. ; Entrada: HL -> Posición de memoria. ; Salida: A -> Tercio, línea y scanline obtenido. ; Altera el valor de los registros AF y E. ; – --------------------------------------------------------------------------- GetPtrY: ld a, h ; Carga en A el valor de H (tercio y scanline) and $18 ; Se queda con el tercio rlca rlca rlca ; Pasa el valor del tercio a los bits 6 y 7 ld e, a ; Carga el valor en E ld a, h ; Carga en A el valor de H (tercio y scanline) and $07 ; Se queda con el scanline or e ; Lo mezcla con E ld e, a ; Carga el valor en E TT***SSS ld a, l ; Carga en A el valor de L (línea y columna) and $e0 ; Se queda con la línea rrca rrca ; Pasa el valor a los bits 3 a 5 or e ; Lo mezcla con E (TTLLLSSS) ret
Este tipo de conversión ya la hicimos en la rutina checkVerticalLimit, pero al ser necesaria en más de una rutina, la hemos implementado como una rutina aparte.
Para probarla, vamos a modificar la rutina checkVerticalLimit, sustituyendo casi toda ella por una llamada a GetPtrY, quedando de la siguiente manera:
; – --------------------------------------------------------------------------- ; Evalúa si se ha alcanzado el límite vertical. ; Entrada: A -> Límite vertical (TTLLLSSS). ; HL -> Posición actual (010TTSSS LLLCCCCC). ; Altera el valor de los registros AF y BC. ; – -------------------------------------------------------------------------- checkVerticalLimit: ld b, a ; Guarda el valor de A en B call GetPtrY ; Obtiene la coordenada Y (TTLLLSSSS) ; de la posición actual cp b ; Lo compara con B. B = valor original de A = Límite vertical ret
Compilamos, cargamos en el emulador y comprobamos que no se ha roto nada.
Detección de colisiones
Ahora vamos a implementar la detección de colisiones, en el archivo Game.asm.
Empezamos por la rutina que evalúa si hay colisión en el eje X. Esta rutina recibe en C la columna donde se produce la colisión, y activa el flag Z si esta se ha producido:
CheckCrossX: ld a, (ballPos) and $1f cp c ret
Cargamos la posición de la bola en A, LD A, (ballPos), nos quedamos con la columna, AND $1F, y comparamos el valor resultante con la columna de colisión, CP C.
El siguiente paso es implementar la rutina que evalúa si hay colisión en el eje Y. Esta rutina recibe en HL la posición de la pala, y activa el flag Z si hay colisión:
CheckCrossY: call GetPtrY inc a ld c, a
Obtenemos la coordenada Y de la pala, CALL GetPtrY. Como el primer scanline de la pala es blanco, no lo tenemos en cuenta para las colisiones, así que pasamos al siguiente, INC A, y cargamos el valor en C, LD C, A.
ld hl, (ballPos) call GetPtrY ld b, a
Cargamos la posición de la bola en HL, LD HL, (ballPos), obtenemos la coordenada Y, CALL GetPtrY, y cargamos el valor en B, LD B, A.
add a, $04 sub c ret c
En A tenemos la coordenada Y de la bola. Apuntamos A al penúltimo scanline de la bola, el último que no es blanco, ADD A, $04, le restamos la coordenada Y de la pala, SUB C, y si hay acarreo salimos, RET C, ya que la bola pasa por encima de la pala.
Si no hemos salido, tenemos que comprobar si la bola pasa por debajo de la pala:
ld a, c add a, $16 ld c, a ld a, b inc a sub c ret nc xor a ret
Cargamos la coordenada Y de la pala en A, LD A, C, le sumamos $16 (22) para posicionarnos en el penúltimo scanline, el último que no está a 0, ADD A, $16, y cargamos el valor en C, LD C, A.
Cargamos la coordenada Y de la bola en A, LD A, B, la apuntamos al scanline 1, el primero que no está a 0, INC A, y le restamos la coordenada Y de la pala, SUB C.
Si tras la resta no hay acarreo salimos, RET NC, pues o bien la bola pasa por debajo, o colisiona en el último scanline de la pala, que está en la misma coordenada Y que el primer scanline de la bola, y al restar se activa el flag Z.
Si hay acarreo, la bola colisiona en el resto de la pala, por lo que activamos el flag Z, XOR A, y salimos, RET.
El siguiente paso es implementar la rutina principal a la que vamos a llamar para comprobar si hay colisión, en cuyo caso vamos a realizar las acciones necesarias:
CheckBallCross: ld a, (ballSetting) and $40 jr nz, checkBallCross_left
Cargamos la configuración de la bola en A, LD A, (ballSetting), y nos quedamos con el bit 6, AND $40, que especifica si la bola va hacia la derecha o hacia la izquierda. Si el bit 6 está a 1, la bola va hacia la izquierda y saltamos a comprobar si se produce colisión con la pala del jugador 1, JR NZ, checkBallCross_left.
Si no se ha producido el salto, la bola va hacia la derecha y comprobamos si hay colisión con la pala del jugador 2:
checkBallCross_right: ld c, CROSS_RIGHT call CheckCrossX ret nz ld hl, (paddle2pos) call CheckCrossY ret nz
Cargamos la columna de colisión en C, LD C, CROSS_RIGHT, evaluamos si se produce colisión en el eje X, CALL CheckCrossX. Si no se produce colisión salimos de la rutina, RET NZ.
Si se ha producido colisión en el eje X, cargamos la posición de la pala 2 en HL, LD HL, (paddle2pos), y evaluamos si se produce colisión en el eje Y, CALL CheckCrossY. Si no se produce colisión, salimos de la rutina, RET NZ.
Si no hemos salido de la rutina, se ha producido colisión:
ld a, (ballSetting) or $40 ld (ballSetting), a ld a, $ff ld (ballRotation), a ret
Cargamos la configuración de la bola en A, LD A, (ballSetting), ponemos el bit 6 a 1 para cambiar la dirección de la bola hacia la izquierda, OR $40, y cargamos el valor en memoria, LD (ballSetting), A.
Cargamos -1 en A, LD A, $FF, cambiamos la rotación de la bola, LD (ballRotation), A, y salimos de la rutina, RET.
La comprobación de si hay colisión con la pala del jugador 1 es similar a lo visto anteriormente, por lo que solo vamos a poner el código y marcar las diferencias, sin entrar en detalle:
checkBallCross_left: ; # Cambio # ld c, CROSS_LEFT ; # Cambio # call CheckCrossX ret nz ld hl, (paddle1pos) ; # Cambio # call CheckCrossY ret nz ld a, (ballSetting) and $bf ; # Cambio # ld (ballSetting), a ld a, $01 ; # Cambio # ld (ballRotation), a ret
El aspecto final de las rutinas de comprobación de colisiones entre las palas y las bolas es el siguiente:
; – --------------------------------------------------------------------------- ; Evalúa si hay colisión entre la bola y las palas. ; Altera el valor de los registros AF, C y HL. ; – --------------------------------------------------------------------------- CheckBallCross: ld a, (ballSetting) ; Carga la dirección/velocidad de la bola en A and $40 ; Se queda con el bit 6 (izquierda/derecha) jr nz, checkBallCross_left ; Si no está a 0 va hacia la izquierda y salta checkBallCross_right: ld c, CROSS_RIGHT ; Carga la columna de colisión en C call CheckCrossX ; Evalúa si se produce colisión en el eje X ret nz ; Si no se produce, fin de la rutina ld hl, (paddle2pos) ; Carga la posición de la pala 2 en HL call CheckCrossY ; Evalúa si se produce colisión en el eje Y ret nz ; Si no se produce colisión, fin de la rutina ; Si llega aquí hay colisión ld a, (ballSetting) ; Carga la dirección/velocidad de la bola en A or $40 ; Cambia la dirección, la pone hacia la izquierda ld (ballSetting), a ; Carga el valor en memoria ld a, $ff ; Cambia la rotación de la bola ld (ballRotation), a ; La carga en memoria ret ; Fin de la rutina checkBallCross_left: ; La bola va hacia la izquierda ld c, CROSS_LEFT ; Carga la columna de colisión en C call CheckCrossX ; Evalúa si se produce colisión en el eje X ret nz ; Si no se produce, fin de la rutina ld hl, (paddle1pos) ; Carga la posición de la pala 1 en HL call CheckCrossY ; Evalúa si se produce colisión en el eje Y ret nz ; Si no se produce colisión, fin de la rutina ; Si llega aquí hay colisión ld a, (ballSetting) ; Carga la dirección/velocidad de la bola en A and $bf ; Cambia la dirección, la pone hacia la derecha ld (ballSetting), a ; Carga el valor en memoria ld a, $01 ; Cambia la rotación de la bola ld (ballRotation), a ; La carga en memoria ret ; Fin de la rutina ; – --------------------------------------------------------------------------- ; Evalúa si la bola colisiona en el eje X con la pala. ; Entrada: C -> Columna dónde se produce la colisión. ; Salida: Z -> Colisiona. ; NZ -> No colisiona. ; Altera el valor de los registros AF. ; – --------------------------------------------------------------------------- CheckCrossX: ld a, (ballPos) ; Carga la línea y columna donde está la bola and $1f ; Se queda con la columna cp c ; Lo compara con la columna de colisión ret ; – --------------------------------------------------------------------------- ; Evalúa si la bola colisiona en el eje Y con la pala. ; Entrada: HL -> Posición de la pala ; Salida: Z -> Colisiona. ; NZ -> No colisiona. ; Altera el valor de los registros AF, BC y HL. ; – --------------------------------------------------------------------------- CheckCrossY: call GetPtrY ; Obtiene la posición vertical de la pala (TTLLLSSS) ; La posición devuelta apunta al primer scanline de la pala que está a 0, ; apunta al siguiente inc a ld c, a ; Carga el valor en C ld hl, (ballPos) ; Carga en HL la posición de la bola call GetPtrY ; Obtiene la posición vertical de la bola (TTLLLSSS) ld b, a ; Carga el valor en B ; Comprueba si la bola pasa por encima de la pala ; La bola está compuesta de 1 scanline a 0, 4 a $3c y otro a 0 ; La posición apunta al 1er scanline, y se comprueba la colisión con el 5º add a, $04 ; Apunta la posición de la bola al 5º scanline sub c ; Resta a la posición de la bola, la posición de la pala ret c ; Si hay acarreo sale porque la bola pasa por encima ; Comprueba si la bola pasa por debajo de la pala ld a, c ; Carga la posición vertical de la pala en A add a, $16 ; Le suma 22 para apuntar al penúltimo scanline, ; último que no es 0 ld c, a ; Lo vuelve a cargar en C ld a, b ; Carga la posición vertical de la bola inc a ; Le suma 1 para apuntar el scanline 1, primero que no es 0 sub c ; Resta a la posición de la bola, la posición de la pala ret nc ; Si no hay acarreo la bola pasa por debajo de la pala ; o colisiona en el último scanline. ; En este último caso se activa el flag Z ; Hay colisión xor a ; Activa el flag Z ret
Abrimos el archivo Main.asm y justo debajo de la etiqueta loop_continue, añadimos la siguiente línea:
call CheckBallCross
Compilamos y cargamos en el emulador para ver los resultados. Si todo ha ido bien, la bola choca contra las palas; la detección de colisiones está funcionando.

El aspecto final del archivo Main.asm es el siguiente:
; Detección de colisiones org $8000 ; – --------------------------------------------------------------------------- ; Entrada al programa ; – --------------------------------------------------------------------------- Main: ld a, $00 ; A = 0 out ($fe), a ; Pone el borde en negro call Cls ; Limpia la pantalla call PrintLine ; Imprime la línea central call PrintBorder ; Imprime el borde del campo Loop: ld a, (countLoopBall) ; Carga el contador de vueltas de la bola inc a ; Lo incrementa ld (countLoopBall), a ; Lo carga en memoria cp $06 ; Comprueba si ha llegado a 6 jr nz, loop_paddle ; Si no ha llegado a 6 salta call MoveBall ; Mueve la bola ld a, ZERO ; Pone el contador a 0 ld (countLoopBall), a ; Lo carga en memoria loop_paddle: ld a, (countLoopPaddle) ; Carga el contador de vueltas de las palas inc a ; Lo incrementa ld (countLoopPaddle), a ; Lo carga en memoria cp $02 ; Comprueba si ha llegado a 2 jr nz, loop_continue ; Si no ha llegado a 2 salta call ScanKeys ; Escanea las teclas pulsadas call MovePaddle ; Mueva las palas ld a, ZERO ; Pone el contador a 0 ld (countLoopPaddle), a ; Lo carga en memoria loop_continue: call CheckBallCross ; Evalúa si hay colisión entre la bola y las palas call PrintBall ; Pinta la bola call ReprintLine ; Repinta la línea ld hl, (paddle1pos) ; Carga en HL la posición de la pala 1 call PrintPaddle ; Pinta la pala 1 ld hl, (paddle2pos) ; Carga en HL la posición de la pala 2 call PrintPaddle ; Pinta la pala 2 jr Loop ; Bucle infinito include "Game.asm" include "Controls.asm" include "Sprite.asm" include "Video.asm" countLoopBall: db $00 ; Contador de vueltas de la bola countLoopPaddle: db $00 ; Contador de vueltas de las palas end $8000
En el próximo capítulo de Ensamblador para ZX Spectrum, implementaremos la partida a dos jugadores, y el cambio de velocidad de la bola, lo que nos sitúa en la última curva antes de afrontar la recta final.
Enlaces de interés
- Notepad++.
- Visual Studio Code.
- Sublime Text.
- ZEsarUX.
- PASMO.
- Git.
- Curso de ensamblador Z80 de Compiler Software.
- Z80 instruction set.