Skip to content

Sesion 3: Objetos en movimiento y colisiones

Juan Gonzalez-Gomez edited this page Oct 13, 2021 · 41 revisions

Contenidos

Sesión 3.1: Depurando el salto del personaje en ensamblador de Z80

Click to see the youtube video

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_loop

La 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)

Sesión 3.2: Múltiples ficheros en ensamblador de Z80 con CPCtelera

Click to see the youtube video

Separación en dos ficheros

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_update

En 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
  ret

Y 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_loop

La función drawGround se ha dejado en el main, porque no es del personaje. Más adelante se llevará a su propio módulo

Añadir objetos

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_update

Su 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
  ret

Y 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:

Sesión 3.3: Obstáculos en movimiento

Click to see the youtube video

Código para los obstáculos

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_asm

Creamos 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
  ret

El fichero obstacle.h.s contiene esto:

;;========================================
;;========================================
;; OBSTACLE PUBLIC FUNCTIONS
;;========================================
;;========================================
.globl   obstacle_erase
.globl   obstacle_draw
.globl   obstacle_update

Y 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:

Ejercicio: Pensar/implementar las colisiones

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_loop

Hay 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:

Sesión 3.4: Colisiones entre Bounding Boxes

Click to see the youtube video

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

Sesión 3.5: Detección de colisiones en ensamblador Z80

Click to see the youtube video

Enfoque general

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 bytes

Para 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
  ret

Y 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_getPtrHL

Para 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     ;;-- pixels

Función de colisión

La 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

  ret

Como 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_checkCollision

En 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_loop

Se 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:

Ejercicio: Implementar la colisión para el eje y

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:

Sesión 3.6: Cómo optimizar detección de colisiones por frecuencias

Click to see the youtube video

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

Clone this wiki locally