Repository navigation
Sesion 3: Objetos en movimiento y colisiones
- Sesión 3.1: Depurando el salto del personaje en ensamblador de Z80
- Sesión 3.2: Múltiples ficheros en ensamblador de z80 con cpctelera
- Sesión 3.3: Obstáculos en movimiento
- Sesión 3.4: Colisiones entre Bounding Boxes
- Sesión 3.5: Detección de colisiones en ensamblador z80
- Sesión 3.6: Como optimizar detección de colisiones por frecuencias
El código se ha refactorizado ligeramente:
.area _DATA
;;===================================
;; VARIABLES
;;===================================
;;-- HERO DATA
hero_x: .db #39 ;;-- x position (in bytes [0-79])
hero_y: .db #80 ;;-- y posicion (in pixels [0-199])
hero_jump: .db #-1 ;;-- Are we jumping?
;;-- Jump table
jumptable:
.db #-3, #-2, #-1, #-1
.db #-1, #0, #0, #0
.db #1, #2, #2, #3
.db #0x80
;;-- CPCtelera symbols
.globl cpct_drawSolidBox_asm
.globl cpct_getScreenPtr_asm
.globl cpct_scanKeyboard_asm
.globl cpct_isKeyPressed_asm
.globl cpct_waitVSYNC_asm
.include "keyboard/keyboard.s"
.area _CODE
;;=============================================
;; moveHeroRight: Move the hero to the right
;; (inside the line limits)
;;
;; DESTROYS:
;;=============================================
moveHeroRight:
ld a, (hero_x) ;;-- A = hero_x
;;- Check if the hero_x has reached its maximum value
cp #80-2 ;; Check against right limit
jr z,do_not_move_r
;;- No max reached: increase the hero_x by 1
inc a ;;-- A = A + 1
ld (hero_x),a ;;-- hero_x = A
do_not_move_r:
ret
;;=============================================
;; moveHeroLeft: Move the hero to the left
;; (inside the line limits)
;;
;; DESTROYS:
;;=============================================
moveHeroLeft:
;;-- Decrease the hero_y value by 1 (Hero_x = Hero_x - 1)
;;-- (only is decreased if it is greather than 0)
ld a, (hero_x)
cp #0 ;;-- hero_x == 0?
jr z, do_not_move_l ;;-- Yes: Finish
;;-- Decrease the hero_x
dec a
ld (hero_x), a
do_not_move_l:
ret
;;=============================================
;; startJump
;;
;; DESTROYS:
;;=============================================
startJump:
ld a,(hero_jump)
cp #-1 ;; Are we already jumping?
ret nz ;; Yes: ret
ld a, #0
ld (hero_jump), a
ret
;;=====================================
;; Control the hero jump
;;=====================================
jumpControl:
ld a,(hero_jump) ;; A=Hero jump status
cp #-1 ;; Are we jumping?
ret z ;; No jumping, return
;;-- Move Hero
;;-- Calculate HL = jumptable[A]
ld hl, #jumptable ;;-- HL ponts to the jumptable
ld b, #0
ld c, a ;; BC = A
add hl,bc ;; HL = HL + Satus. Address of the current jump position
;;-- Calculate the new hero_y pos
ld b, (hl) ;;-- Get the current jump increment
ld a,b
cp #0x80 ;;-- Check if it is the last position
jr z,end_of_jump ;;-- If so, end the current jump
;;-- NOT the last position
;;-- update the hero_y
ld a, (hero_y) ;;-- Get the hero_y
add b ;;-- A = A + B (current pos)
ld (hero_y),a ;;-- Updated hero_y
;;-- Increment hero_jump index
ld a, (hero_jump)
inc a
ld (hero_jump),a
;;-- We are done
ret
;;-- Finish the jup: write -1 in the hero_jump variable
end_of_jump:
ld a,#-1
ld (hero_jump),a
ret
;;===============================
;; checkUserInput: Read the keyboard and update the position
;; of our hero
;;
;; DESTROYs:
;;===============================
checkUserInput:
;;-- Scan the whole keyboard
call cpct_scanKeyboard_asm
;;-- Check for key 'D' being pressed
ld hl, #Key_D ;;-- hl = Key_D Keycode
call cpct_isKeyPressed_asm
cp #0 ;; check A == 0
jr z, d_not_pressed ;;-- Jump if D is not pressed
;;-- D is pressed
call moveHeroRight
d_not_pressed:
;;-- Check for the key 'A' being pressed
ld hl, #Key_A ;;-- hl = Key_A Keycode
call cpct_isKeyPressed_asm
cp #0 ;; check A == 0
jr z, a_not_pressed ;;-- Jump if D is not pressed
;;-- A is pressed
call moveHeroLeft
a_not_pressed:
;;-- Check for the key 'W' being pressed
ld hl, #Key_W ;;-- hl = Key_W Keycode
call cpct_isKeyPressed_asm
cp #0 ;; check A == 0
jr z, w_not_pressed ;;-- Jump if D is not pressed
;;-- W is pressed
call startJump
w_not_pressed:
ret
;;===============================
;; drawHero
;;
;; INPUT:
;; A => Color Pattern
;;
;; DESTROYs: AF, BC, DE, HL
;;===============================
drawHero:
;;-- Store the color patter
push af
;;-- Convert the hero_x, hero_y variables into
;;-- the corresponding address in the Video memory
ld de, #0xC000 ;;-- Video initial address
ld a,(hero_x)
ld c,a ;;-- Hero x position
ld a,(hero_y)
ld b,a ;;-- Hero y position
call cpct_getScreenPtr_asm
;; HL contains the video address
;; move it to the DE register
ex de, hl
;; Draw a Box (our hero!)
pop af ;; Read the color pattern
ld bc, #0x0802 ;; Height, Width: 8x8 pixels
call cpct_drawSolidBox_asm
ret
;;===============================
;; drawGround
;;
;; INPUT:
;; A => Color Pattern
;;
;; DESTROYs: AF, BC, DE, HL
;;===============================
drawGround:
push AF ;;-- Save the color pattern
;;-- Draw the half-left side of the ground
ld de, #0xC000 ;;-- Video initial address
ld bc, #0x5800 ;;-- Position: 0,0x50
call cpct_getScreenPtr_asm
ex de, hl ;;-- DE=video address for the ground
pop AF ;;-- Color pattern
push AF ;;-- Save the color pattern
ld bc, #0x0240 ;;-- B: height (Pixels), C: width [1-64]
call cpct_drawSolidBox_asm
;;-- Draw the half-right side of the ground
ld de, #0xC000 ;;-- Video initial address
ld bc, #0x5840 ;;-- Position: 0x50,0x58
call cpct_getScreenPtr_asm
ex de, hl ;;-- DE=video address for the ground
pop AF ;;-- Color pattern
ld bc, #0x0210 ;;-- B: height (Pixels), C: width [1-64]
call cpct_drawSolidBox_asm
ret
;;===============================
;; Main program entry
;;===============================
_main::
;;-- Draw the Ground in red color
ld a, #0xF0 ;;-- Red pattern
call drawGround
main_loop:
;; Erase previous hero
ld a,#0x00 ;; Color pattern: 0 (Background)
call drawHero
call jumpControl ;;-- Do jumps
call checkUserInput ;; Check if user pressed keys
;;-- Draw the hero
ld a,#0xFF ;; Color pattern: Cyan
call drawHero
;;-- Wait for the raster to finish
call cpct_waitVSYNC_asm
jr main_loopLa función jumpControl tiene varios puntos de salida (implementados con ret) porque es más eficiente. Ocupa menos un ret que poner un jr o un jump hacia la etiqueta final (Siempre que sean funciones pequeñas no hay problema)
El resultado es el mismo que en la sección 2: un cuadrado rojo de 8x8 que se mueve y puede saltar (y está limitado a una fila)
Nuestro programa está en el fichero main.s... y aunque todavía hace pocas cosas está aumentando mucho de tamaño. Para hacerlo manejable vamos a separarlos en dos ficheros:
- main.s : Dejaremos el bucle principal
- hero.s : Pondremos aquí las funciones públicas de control del personaje
También queremos tener el control de dónde se guardan las variables en memoria por lo que el área _DATA la dejaremos en blanco y las variables las pondremos en la propia zona de código: _CODE: Así estarán siempre junto a nuestro código
Al separar en ficheros, cada fihero puede tener sus propias etiquetas locales, y no colisionan con las del resto de ficheros. Así podemos reutilizar los nombres de las etiquetas. Y también el nombre de las variables
En el fichero hero.h.s metemos las definiciones de las funciones públicas del módulo hero:
;;========================================
;;========================================
;; HERO PUBLIC FUNCTIONS
;;========================================
;;========================================
.globl hero_erase
.globl hero_draw
.globl hero_updateEn fichero hero.s queda de la siguiente forma. Las funciones se han separado en públicas y privadas. Las públicas son etiquetas globales y por tanto las definimos usando :: al final de las etiquetas. La parte del bucle principal se ha eliminado (se ha llevado a main.s)
.area _DATA
.area _CODE
;;==============================================
;;==============================================
;; PRIVATE DATA
;;==============================================
;;==============================================
;;-- HERO DATA
hero_x: .db #39 ;;-- x position (in bytes [0-79])
hero_y: .db #80 ;;-- y posicion (in pixels [0-199])
hero_jump: .db #-1 ;;-- Are we jumping?
;;-- Jump table
jumptable:
.db #-3, #-2, #-1, #-1
.db #-1, #0, #0, #0
.db #1, #2, #2, #3
.db #0x80
;;-- CPCtelera symbols
.globl cpct_drawSolidBox_asm
.globl cpct_getScreenPtr_asm
.globl cpct_scanKeyboard_asm
.globl cpct_isKeyPressed_asm
.include "keyboard/keyboard.s"
;;=============================================
;;=============================================
;; PUBLIC FUNCTIONS
;;=============================================
;;=============================================
;;======================
;; Updates the Hero
;; DESTROYS: AF, BC, HL
;;=======================
hero_update::
call jumpControl ;;-- Do jumps
call checkUserInput ;; Check if user pressed keys
ret
;;======================
;; Draw the hero
;; DESTROYS: AF, BC, HL
;;=======================
hero_draw::
;;-- Draw the hero :)
ld a,#0xFF ;; Color pattern: red
call drawHero
ret
;;======================
;; Erase the hero
;; DESTROYS: AF, BC, HL
;;=======================
hero_erase::
;; Erase previous hero
ld a,#0x00 ;; Color pattern: 0 (Background)
call drawHero
ret
;;=============================================
;;=============================================
;; PRIVATE FUNCTIONS
;;=============================================
;;=============================================
;;=============================================
;; moveHeroRight: Move the hero to the right
;; (inside the line limits)
;;
;; DESTROYS:
;;=============================================
moveHeroRight:
ld a, (hero_x) ;;-- A = hero_x
;;- Check if the hero_x has reached its maximum value
cp #80-2 ;; Check against right limit
jr z,do_not_move_r
;;- No max reached: increase the hero_x by 1
inc a ;;-- A = A + 1
ld (hero_x),a ;;-- hero_x = A
do_not_move_r:
ret
;;=============================================
;; moveHeroLeft: Move the hero to the left
;; (inside the line limits)
;;
;; DESTROYS:
;;=============================================
moveHeroLeft:
;;-- Decrease the hero_y value by 1 (Hero_x = Hero_x - 1)
;;-- (only is decreased if it is greather than 0)
ld a, (hero_x)
cp #0 ;;-- hero_x == 0?
jr z, do_not_move_l ;;-- Yes: Finish
;;-- Decrease the hero_x
dec a
ld (hero_x), a
do_not_move_l:
ret
;;=============================================
;; startJump
;;
;; DESTROYS:
;;=============================================
startJump:
ld a,(hero_jump)
cp #-1 ;; Are we already jumping?
ret nz ;; Yes: ret
ld a, #0
ld (hero_jump), a
ret
;;=====================================
;; Control the hero jump
;;=====================================
jumpControl:
ld a,(hero_jump) ;; A=Hero jump status
cp #-1 ;; Are we jumping?
ret z ;; No jumping, return
;;-- Move Hero
;;-- Calculate HL = jumptable[A]
ld hl, #jumptable ;;-- HL ponts to the jumptable
ld b, #0
ld c, a ;; BC = A
add hl,bc ;; HL = HL + Satus. Address of the current jump position
;;-- Calculate the new hero_y pos
ld b, (hl) ;;-- Get the current jump increment
ld a,b
cp #0x80 ;;-- Check if it is the last position
jr z,end_of_jump ;;-- If so, end the current jump
;;-- NOT the last position
;;-- update the hero_y
ld a, (hero_y) ;;-- Get the hero_y
add b ;;-- A = A + B (current pos)
ld (hero_y),a ;;-- Updated hero_y
;;-- Increment hero_jump index
ld a, (hero_jump)
inc a
ld (hero_jump),a
;;-- We are done
ret
;;-- Finish the jup: write -1 in the hero_jump variable
end_of_jump:
ld a,#-1
ld (hero_jump),a
ret
;;===============================
;; checkUserInput: Read the keyboard and update the position
;; of our hero
;;
;; DESTROYs:
;;===============================
checkUserInput:
;;-- Scan the whole keyboard
call cpct_scanKeyboard_asm
;;-- Check for key 'D' being pressed
ld hl, #Key_D ;;-- hl = Key_D Keycode
call cpct_isKeyPressed_asm
cp #0 ;; check A == 0
jr z, d_not_pressed ;;-- Jump if D is not pressed
;;-- D is pressed
call moveHeroRight
d_not_pressed:
;;-- Check for the key 'A' being pressed
ld hl, #Key_A ;;-- hl = Key_A Keycode
call cpct_isKeyPressed_asm
cp #0 ;; check A == 0
jr z, a_not_pressed ;;-- Jump if D is not pressed
;;-- A is pressed
call moveHeroLeft
a_not_pressed:
;;-- Check for the key 'W' being pressed
ld hl, #Key_W ;;-- hl = Key_W Keycode
call cpct_isKeyPressed_asm
cp #0 ;; check A == 0
jr z, w_not_pressed ;;-- Jump if D is not pressed
;;-- W is pressed
call startJump
w_not_pressed:
ret
;;===============================
;; drawHero
;;
;; INPUT:
;; A => Color Pattern
;;
;; DESTROYs: AF, BC, DE, HL
;;===============================
drawHero:
;;-- Store the color patter
push af
;;-- Convert the hero_x, hero_y variables into
;;-- the corresponding address in the Video memory
ld de, #0xC000 ;;-- Video initial address
ld a,(hero_x)
ld c,a ;;-- Hero x position
ld a,(hero_y)
ld b,a ;;-- Hero y position
call cpct_getScreenPtr_asm
;; HL contains the video address
;; move it to the DE register
ex de, hl
;; Draw a Box (our hero!)
pop af ;; Read the color pattern
ld bc, #0x0802 ;; Height, Width: 8x8 pixels
call cpct_drawSolidBox_asm
retY así queda el programa principal en el fichero main.s:
.area _DATA
.area _CODE
;;-- CPCtelera symbols
.globl cpct_waitVSYNC_asm
.globl cpct_drawSolidBox_asm
.globl cpct_getScreenPtr_asm
.include "hero.h.s"
;;===============================
;; drawGround
;;
;; INPUT:
;; A => Color Pattern
;;
;; DESTROYs: AF, BC, DE, HL
;;===============================
drawGround:
push AF ;;-- Save the color pattern
;;-- Draw the half-left side of the ground
ld de, #0xC000 ;;-- Video initial address
ld bc, #0x5800 ;;-- Position: 0,0x50
call cpct_getScreenPtr_asm
ex de, hl ;;-- DE=video address for the ground
pop AF ;;-- Color pattern
push AF ;;-- Save the color pattern
ld bc, #0x0240 ;;-- B: height (Pixels), C: width [1-64]
call cpct_drawSolidBox_asm
;;-- Draw the half-right side of the ground
ld de, #0xC000 ;;-- Video initial address
ld bc, #0x5840 ;;-- Position: 0x50,0x58
call cpct_getScreenPtr_asm
ex de, hl ;;-- DE=video address for the ground
pop AF ;;-- Color pattern
ld bc, #0x0210 ;;-- B: height (Pixels), C: width [1-64]
call cpct_drawSolidBox_asm
ret
;;===============================
;; Main program entry
;;===============================
_main::
;;-- Draw the Ground
ld a, #0xA0 ;;-- yello pattern
call drawGround
main_loop:
call hero_erase ;; Erase the hero
call hero_update ;; Update the Hero
call hero_draw ;;-- Draw the hero
;;-- Wait for the raster to finish
call cpct_waitVSYNC_asm
jr main_loopLa función drawGround se ha dejado en el main, porque no es del personaje. Más adelante se llevará a su propio módulo
Esta parte se deja como ejercicio. Hay que dibujar un objeto de 4x4 píxeles, que sea una bala. Se mueve desde la parte derecha a la izquierda, y va a la altura de los ojos del personaje, para que lo podamos saltar. De momento sin colisiones
Todo el código hay que añadirlo en un fichero nuevo, con sus métodos públicos. Se llama desde main
Las funciones públicas están en este fichero: bullet.h.s
;;========================================
;;========================================
;; BULLET PUBLIC FUNCTIONS
;;========================================
;;========================================
.globl bullet_erase
.globl bullet_draw
.globl bullet_updateSu implementación está en este fichero: bullet.s
.area _DATA
.area _CODE
;;==============================================
;;==============================================
;; PRIVATE DATA
;;==============================================
;;==============================================
;;-- BULLET DATA
bullet_x: .db #79 ;;-- x position (in bytes [0-79])
bullet_y: .db #81 ;;-- y posicion (in pixels [0-199])
;;=============================================
;;=============================================
;; PUBLIC FUNCTIONS
;;=============================================
;;=============================================
;;-- CPCtelera symbols
.globl cpct_drawSolidBox_asm
.globl cpct_getScreenPtr_asm
;;======================
;; Updates the bullet
;; DESTROYS: AF, BC, HL
;;=======================
bullet_update::
ld a,(bullet_x) ;;-- Read the bullet position
cp #0 ;;-- Check the limit
jr z,start_again ;;-- Left limit reached: start again from the right
;;-- No limit reached. Decrease the bullet_x variable
dec a
ld (bullet_x),a
ret
start_again:
ld a, #79
ld (bullet_x), a
ret
;;======================
;; Draw the hero
;; DESTROYS: AF, BC, HL
;;=======================
bullet_draw::
;;-- Draw the bullet
ld a,#0x0F ;; Color pattern: cyan
call drawBullet
ret
;;======================
;; Erase the hero
;; DESTROYS: AF, BC, HL
;;=======================
bullet_erase::
;; Erase previous bullet
ld a,#0x00 ;; Color pattern: 0 (Background)
call drawBullet
ret
;;=============================================
;;=============================================
;; PRIVATE FUNCTIONS
;;=============================================
;;=============================================
;;===============================
;; Draw the bullet
;;
;; INPUT:
;; A => Color Pattern
;;
;; DESTROYs: AF, BC, DE, HL
;;===============================
drawBullet:
;;-- Store the color patter
push af
;;-- Convert the bullet_x, bullect_y variables into
;;-- the corresponding address in the Video memory
ld de, #0xC000 ;;-- Video initial address
ld a,(bullet_x)
ld c,a ;;-- Bullet x position
ld a,(bullet_y)
ld b,a ;;-- Bullet y position
call cpct_getScreenPtr_asm
;; HL contains the video address
;; move it to the DE register
ex de, hl
;; Draw a Box
pop af ;; Read the color pattern
ld bc, #0x0401 ;; Height, Width: 4x4 pixels
call cpct_drawSolidBox_asm
retY el programa principal es el siguiente (main.s)
.area _DATA
.area _CODE
;;-- CPCtelera symbols
.globl cpct_waitVSYNC_asm
.globl cpct_drawSolidBox_asm
.globl cpct_getScreenPtr_asm
.include "hero.h.s"
.include "bullet.h.s"
...
...
;;===============================
;; Main program entry
;;===============================
_main::
;;-- Draw the Ground
ld a, #0xA0 ;;-- yellow pattern
call drawGround
main_loop:
call hero_erase ;; Erase the hero
call bullet_erase ;; Erase the bullet
call hero_update ;; Update the Hero
call bullet_update ;; Update the bullet
call hero_draw ;;-- Draw the hero
call bullet_draw ;;-- Draw the bullet
;;-- Wait for the raster to finish
call cpct_waitVSYNC_asm
jr main_loop- Resultado:

Aprovechamos para meter en el fichero cpctelera.h.s todas las funciones que estamos usando:
;;========================================
;;========================================
;; CPCTELERA PUBLIC FUNCTIONS USED
;;========================================
;;========================================
;;-- CPCtelera symbols
.globl cpct_drawSolidBox_asm
.globl cpct_getScreenPtr_asm
.globl cpct_scanKeyboard_asm
.globl cpct_isKeyPressed_asm
.globl cpct_waitVSYNC_asmCreamos código nuevo para los obstáculos en el fichero obstacle.s:
.area _DATA
.area _CODE
;;==============================================
;;==============================================
;; PRIVATE DATA
;;==============================================
;;==============================================
;;-- OBSTACLE DATA
obs_x: .db #80-1 ;;-- x position (in bytes [0-79])
obs_y: .db #82 ;;-- y posicion (in pixels [0-199])
;;=============================================
;;=============================================
;; PUBLIC FUNCTIONS
;;=============================================
;;=============================================
;;-- CPCtelera symbols
.include "cpctelera.h.s"
;;======================
;; Updates the obstacle
;; DESTROYS: AF, BC, HL
;;=======================
obstacle_update::
ld a,(obs_x) ;;-- Read the obstacle position
dec a
jr nz,not_restart_x ;;-- If (Obs_x != 0) then not restart x
;;-- Restart_x when it is 0
ld a, #80-1 ;;-- A = Obs_x = 80-1
not_restart_x:
ld (obs_x), a
ret
;;======================
;; Draw the obstacle
;; DESTROYS: AF, BC, HL
;;=======================
obstacle_draw::
;;-- Draw the bullet
ld a,#0xF0 ;; Color pattern: yellow
call drawObstacle
ret
;;======================
;; Erase the obstacle
;; DESTROYS: AF, BC, HL
;;=======================
obstacle_erase::
;; Erase previous bullet
ld a,#0x00 ;; Color pattern: 0 (Background)
call drawObstacle
ret
;;=============================================
;;=============================================
;; PRIVATE FUNCTIONS
;;=============================================
;;=============================================
;;===============================
;; Draw the obstacle
;;
;; INPUT:
;; A => Color Pattern
;;
;; DESTROYs: AF, BC, DE, HL
;;===============================
drawObstacle:
;;-- Store the color patter
push af
;;-- Convert the bullet_x, bullect_y variables into
;;-- the corresponding address in the Video memory
ld de, #0xC000 ;;-- Video initial address
ld a,(obs_x)
ld c,a ;;-- Obs x position
ld a,(obs_y)
ld b,a ;;-- Obs y position
call cpct_getScreenPtr_asm
;; HL contains the video address
;; move it to the DE register
ex de, hl
;; Draw a Box
pop af ;; Read the color pattern
ld bc, #0x0401 ;; Height, Width: 4x4 pixels
call cpct_drawSolidBox_asm
retEl fichero obstacle.h.s contiene esto:
;;========================================
;;========================================
;; OBSTACLE PUBLIC FUNCTIONS
;;========================================
;;========================================
.globl obstacle_erase
.globl obstacle_draw
.globl obstacle_updateY por último el fichero main.s:
.area _DATA
.area _CODE
.include "hero.h.s"
.include "obstacle.h.s"
.include "cpctelera.h.s"
...
...
;;===============================
;; Main program entry
;;===============================
_main::
;;-- Draw the Ground
ld a, #0xA0 ;;-- yellow pattern
call drawGround
main_loop:
call hero_erase ;; Erase the hero
call obstacle_erase ;; Erase the bullet
call hero_update ;; Update the Hero
call obstacle_update ;; Update the bullet
call hero_draw ;;-- Draw the hero
call obstacle_draw ;;-- Draw the bullet
;;-- Wait for the raster to finish
call cpct_waitVSYNC_asm
jr main_loop- Resultado: El resultado es el mismo que el obtenido como ejercicio en la sección anterior:

Hacer que se pinten 4 píxeles rojos en la posición de memoria 0xC000 cada vez que se detecta una colisión
Lo he implementado en el main.s. He tenido que hacer públicas las variables hero_x,hero_y y obs_x,obs_y. El main queda así:
...
checkCollision_x:
ld a,(obs_x)
ld b,a ;;-- B = obs_x
ld a,(hero_x) ;;-- A = hero_x
cp b ;; obs_x == hero_x?
ret z
;;-- No colision
inc a ;; A = hexo_x + 1
cp b ;; obs_x == hero_x + 1?
ret
checkCollision_y:
ld a,(obs_y)
ld b,a ;;-- B = obs_y
ld a,(hero_y) ;;-- A = hero_y
add #4 ;;-- A = hero_y + 4
cp b ;;-- obs_y < hero_y+4?
jr nc, collision1
;;-- No collision
ret
collision1:
;;-- There is collision. Check the other condition
add #-12 ;;-- obs_y > hero_y-8?
cp b
jr c, collision2
;;-- No collision
ret
collision2:
;;-- hero_y-8 < obs_y < hero_y + 4. Both conditions met
xor a ;;-- set z=1. There is collision!
ret
;;========================================================
;; Check the colision between the hero and the obstacle
;;========================================================
checkCollision:
;; There is colision when the collision on x is true and the colision on y is also true
call checkCollision_x
ret nz ; No colision in x (no need for checking on y)
;;-- Collision in x. Check the y variable
call checkCollision_y
ret
;;===============================
;; Main program entry
;;===============================
_main::
;;-- Draw the Ground
ld a, #0xA0 ;;-- yellow pattern
call drawGround
main_loop:
call hero_erase ;; Erase the hero
call obstacle_erase ;; Erase the bullet
call hero_update ;; Update the Hero
call obstacle_update ;; Update the bullet
;;-- Check colision
call checkCollision
jr z,colission
;;-- No colission
ld a,#0x00
jr continue
colission:
ld a,#0xFF
continue:
ld (0xC000),a
call hero_draw ;;-- Draw the hero
call obstacle_draw ;;-- Draw the bullet
;;-- Wait for the raster to finish
call cpct_waitVSYNC_asm
jr main_loopHay colisión si la hay en las coordenadas x, Y también en las coordenadas y. Cada una se comprueba por separado. La colisión en Y está a su vez dada por dos comprobaciones que se deben cumplir a la vez: hero_y-8 < obs_y < hero_y+4
-
Resultado:
Explicación de la teoría para detectar las colisiones. Hay que mirar las proyecciones en los ejes x,y de forma independiente. Es más fácil comprobar los casos en los que NO hay colisión. No hay colisión entre los intervalos (obs_x, obs_x + obs_w) y (hx, hx + hw) si:
- obs_x + obs_w < hx
- ó, obs_x > hx + hw
Esa función se puede usar igual en ambos intervalos
En este vídeo se explica la teoría, pero todavía no se ha implementado
La opción que se implementa es la de definir una función de colisión en obstacle.s, a la que se le pasan como parámetros la posición del personaje y sus dimensiones (4 bytes). Lo pasaremos por referencia (mediante un puntero)
Creamos una estructura en hero.s con toda la información personaje: posición + dimensiones:
;;-- HERO DATA
hero_x: .db #39 ;;-- x position (in bytes [0-79])
hero_y: .db #80 ;;-- y posicion (in pixels [0-199])
hero_w: .db #2 ;;-- Width in bytes
hero_h: .db #8 ;;-- Height in bytesPara acceder a esa estructura necesitamos un puntero (y así no tener que definir las varibles globales). Creamos la función hero_getPtrHL (pública) que nos devuelve precisamente el puntero a esa estructura (cuyo primer valor es hero_x):
;;======================
;; Get a pointer to hero data in HL
;; DESTROYS: HL
;; RETURN:
;; HL: Pointer to hero Data
;;=======================
hero_getPtrHL::
ld hl,#hero_x
retY por supuesto, como es una función pública, la metemos en el fichero hero.h.s:
;;========================================
;;========================================
;; HERO PUBLIC FUNCTIONS
;;========================================
;;========================================
.globl hero_erase
.globl hero_draw
.globl hero_update
.globl hero_getPtrHLPara nuestro obstáculo definimos también su estructura:
;;-- OBSTACLE DATA
obs_x: .db #80-1 ;;-- x position (in bytes [0-79])
obs_y: .db #82 ;;-- y posicion (in pixels [0-199])
obs_w: .db #1 ;;-- bytes
obs_h: .db #4 ;;-- pixelsLa colisión la implementamos con la función pública obstacle_checkCollision en el fichero obstacle.s. Esta función sólo comprueba de momento los casos en los que NO hay colisión en el eje x: que el obstáculo esté por la izquierda del personaje, o bien por la derecha. En caso de que sí haya colisión se devuelve en A el valor 0xFF, y 0x00 en caso de no haberla. Esto nos sirve par depurar
;;================================================================
;; Checks Collision between obstacle and another entity
;; Input:
;; HL: Points to the other entity with which to check collision
;; Return:
;; xxxxxxx
;;================================================================
obstacle_checkCollision::
;;
;; if (obs_x + obs_w <= hero_x) no collision
;;
;; obs_x + obs_w - hero_x <= 0
;;
ld a,(obs_x)
ld c,a
ld a,(obs_w)
add c ;;-- A = obs_x + obs_w
sub (hl) ;;-- A = obs_x + obs_w - hero_x
jr z, no_collision
jp m, no_collision
;; if (hero_x + hero_w <= obs_x)
;; hero_x + hero_w - obs_x <= 0
ld a,(hl) ; A = hero_x
inc hl
inc hl ; HL points to hero_w
add (hl) ; A = hero_x + hero_w
ld c,a ; C = hero_x + hero_w
ld a,(obs_x) ; A = obs_x
ld b,a ; B = obs_x
ld a,c ; A = hero_x + hero_w
sub b ; A = hero_x + hero_w - obs_x
jr z, no_collision
jp m, no_collision
;;-- Collision
ld a, #0xFF
ret
no_collision:
ld a, #0x00
retComo es una función pública, la metemos en obstacle.h.s:
;;========================================
;;========================================
;; OBSTACLE PUBLIC FUNCTIONS
;;========================================
;;========================================
.globl obstacle_erase
.globl obstacle_draw
.globl obstacle_update
.globl obstacle_checkCollisionEn el programa principal ponemos esto:
;;===============================
;; Main program entry
;;===============================
_main::
;;-- Draw the Ground
ld a, #0xA0 ;;-- yellow pattern
call drawGround
main_loop:
call hero_erase ;; Erase the hero
call obstacle_erase ;; Erase the bullet
call hero_update ;; Update the Hero
call obstacle_update ;; Update the bullet
;;-- Check colision
call hero_getPtrHL
call obstacle_checkCollision
ld (0xC000),a ;;-- Draw collision
ld (0xC001),a
ld (0xC002),a
ld (0xC003),a
call hero_draw ;;-- Draw the hero
call obstacle_draw ;;-- Draw the bullet
;;-- Wait for the raster to finish
call cpct_waitVSYNC_asm
jr main_loopSe llama primero a hero_getPtrHL para obtener el puntero a la estructura de datos de Hero, y luego se llama a obstacle_checkCollision. El valor devuelto en A se escribe en la memoria de vídeo para ver qué ocurre.
Para hacer las comprobaciones de funcionamiento podemos bajar la velocidad del simulador desde la opción Settings/General/Timing
- Resultado:

Se deja como ejercicio de la sesión el implementar la colisión para el eje y, completando así la función de colisión
La función completa es esta:
;;================================================================
;; Checks Collision between obstacle and another entity
;; Input:
;; HL: Points to the other entity with which to check collision
;; Return:
;; xxxxxxx
;;================================================================
obstacle_checkCollision::
;;
;; if (obs_x + obs_w <= hero_x) no collision
;;
;; obs_x + obs_w - hero_x <= 0
;;
ld a,(obs_x)
ld c,a
ld a,(obs_w)
add c ;;-- A = obs_x + obs_w
sub (hl) ;;-- A = obs_x + obs_w - hero_x
jr z, no_collision
jp m, no_collision
;; if (hero_x + hero_w <= obs_x)
;; hero_x + hero_w - obs_x <= 0
ld a,(hl) ; A = hero_x
inc hl
inc hl ; HL points to hero_w
add (hl) ; A = hero_x + hero_w
ld c,a ; C = hero_x + hero_w
ld a,(obs_x) ; A = obs_x
ld b,a ; B = obs_x
ld a,c ; A = hero_x + hero_w
sub b ; A = hero_x + hero_w - obs_x
jr z, no_collision
jp m, no_collision
;;-- Check collision for y-axis
;;-- if (hero_y + hero_h <= obs_y) no Collision
;;--- (hero_y + hero_h - obs_y <= 0)
dec hl ;-- HL points to hero_y
ld a,(hl) ;-- A = hero_y
ld c,a ;-- C = hero_y
inc hl
inc hl ;-- HL points to hero_h
ld a,(hl) ;-- A = hero_h
add c ;-- A = hero_y + hero_h
ld c,a ;-- C = hero_y + hero_h
ld a,(obs_y) ;-- A = obs_y
ld b,a ;-- B = obs_y
ld a,c ;-- A = hero_y + hero_h
sub b ;-- A = hero_y + hero_h - obs_y
jr z, no_collision
jp m, no_collision
;;-- if (obs_y + obs_h <= hero_x) no Collision
;;-- This case cannot happend... no need to implement it
;;-- Collision
ld a, #0xFF
ret
no_collision:
ld a, #0x00
ret- Resultado:

En este juego, la y colisiona casi todo el tiempo. Por eso es mejor comprobar primero las colisiones en X, que se producen menos. Así se optimiza la ejecución del código





