0x07 Ensamblador ZX Spectrum Pong – Detección de colisiones

En esta nueva entrega de Ensamblador ZX Spectrum Pong vamos a empezar con la detección de colisiones, para que la bola rebote contra las palas.

Ensamblador ZX Spectrum Pong – 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 implementando 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.

GetPtrY

Como hemos visto en entregas anteriores, 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 (TTLLLSSS).

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 la 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 el siguiente.

; -----------------------------------------------------------------------------
; 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 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 con 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 la 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 a 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 la bola 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

Ahora ya solo nos queda ver si lo que hemos implementado hace lo que pretendemos.

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 ZX Spectrum - Colisiones
Ensamblador ZX Spectrum – colisiones

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

Podéis descargar todo el código que hemos generado.

Enlaces de interés

Vídeo

Si lo prefieres, puedes ver el vídeo que grabamos de esta sesión.

Ensamblador para ZX Spectrum PONG por Juan Antonio Rubio García.
Comentarios al código por Spirax.
Correcciones al texto original realizadas por Joaquín Ferrero.
Esta obra está bajo licencia de Creative Commons Reconocimiento-NoComercial-CompartitIgual 4.0 Internacional License.
Este tutorial ha sido publicado con anterioridad en AUA y se han grabado vídeos que están publicados a través de Retro Parla.

No olvides visitar las webs amigas.

AUA

Aquí puedes ver más cosas que he desarrollado para .Net, y aquí las desarrolladas en ensamblador para Z80.

Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.

Deja una respuesta

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

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies
A %d blogueros les gusta esto: