Curso Programación ZX SpectrumCursos

Ensamblador para ZX Spectrum – Pong: $09 Detección de colisiones

Detección de colisiones, cada vez más cerca de nuestro fin.

5
(2)

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.

Ensamblador para ZX Spectrum, detección de colisiones en marcha
Ensamblador para ZX Spectrum, detección de colisiones en marcha

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

Ficheros

¿Te ha Resultado útil este artículo?

Ayúdanos a mejorar y danos tu opinión:

Mostrar más

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicaciones relacionadas

Mira también
Cerrar
Botón volver arriba