Skip to content

Sesion 2: Empezando con ensamblador del Z80

Juan Gonzalez-Gomez edited this page Oct 12, 2021 · 68 revisions

Contenidos

Introducción a la Parte II

  • Playlist con todos los vídeos del curso RA2017

El punto de partida es justo el final del nivel 3 del curso DEZ80. Ahora estamos un poquito por encima del bajo nivel, utilizando una serie de herramientas automatizadas: Ensamblador y CPCTelera. Nos automatizan el trabajo y nos lo simplifican

Sesión 2.1

Click to see the youtube video

Enlaces

Creación de un proyecto ensamblador en #CPCtelera

Primero tenemos que tener instalado CPCTelera. En mi caso ya la instalé en el Nivel 1

  • Creamos un proyecto nuevo, que llamaremos practica: cpct_mkproject practica

Esta es la estructura de directorios creada para el proyecto:

El fichero main.c lo eliminamos, porque no trabajaremos en C, sino en ensamblador. Creamos el fichero main.s, donde se deben definir las áreas _DATA y _CODE necesarias para que el ensamblador del SDCC coloque el código y los datos en memoria

.area _DATA

.area _CODE

A continuación definimos la etiqueta global _main::. El sufijo :: sirve para indicar que la etiqueta es global. Es equivalente a utilizar la directiva .global

.globl _main
_main:

Para comprobar que todo funciona vamos a hacer el programa "Hola mundo" que muestre 4 píxeles rojos en la dirección 0xC000 de la memoria de vídeo

.area _DATA

.area _CODE

;;===============================
;;  Main program entry
;;===============================
_main::
  ld a, #0xFF
  ld (0xC000),a

  jr .

Para ensamblarlo ejecutamos el comando make

En el directorio del proyecto aparecen los ficheros practica.cdt y practica.dsk, que son los contenedores que simulan una cinta y un disco de Amstrad. Contienen el binario del proyecto

En el directorio obj están todos los objetos de compilación generados:

Algunos ficheros interesantes son:

  • practica.map: Contiene el mapa de todos los símbolos definidos en el programa. Por ejemplo, ahí podemos ver la dirección asignada a la etiqueta _main, donde está nuestro programa:
  Value  Global                              Global Defined In Module
  -----  --------------------------------   ------------------------
  00004000  _main                              

Saber la dirección en donde están las diferentes funciones nos permitirá poner Breakpoints en WinApe

  • practica.bin: Es el binario final con el código máquina

Para ejecutar el programa tecleamos el comando: cpct_winape -a. Se arranca Winape, se carga automáticamente el fichero .dsk y ejecuta el programa. Nos aparecerá esta pantalla:

Si abrimos el depurardor podemos ver en la dirección 0x4000 el código máquina de nuestro programa: 3E FF 32 00 C0 18 FE

Abriendo el proyecto en VSCODE

Para trabajar en nuestro proyecto desde VSCODE basta con abrir la carpeta practica

Desde un terminal podemos ejecutar los comandos para ensamblar y ejecutar Winape. O bien podemos utilizar las tareas ya creadas, accesibles desde el menú Terminal/Run Task...

He instalado los plugins Z80 assembly para el resaltado de sintáxis en ensamblador y Hex editor para ver los ficheros binarios en código máquina

Dibujando un solid box con CPCtelera

Ya tenemos el entorno listo para empezar a trabajar. Vamos a crear un cuadrado cyan de 8x8 píxeles, que utilizaremos de base como nuestro personaje, y que poco a poco iremos refinando y añadiendo cosas. Para ello utilizamos la función cpct_drawSolidBox de cpctelera

Para que el ensamblador tenga acceso al símbolo cpct_drawSolidBox_asm y que pueda llamar a la función, es necesario declarar esa etiqueta como global usando:

;;-- CPCtelera symbols
.globl cpct_drawSolidBox_asm

Al realizar el linkado la herramienta toma la dirección de esa función de la bibliotca de cpctelera. El programa para dibujar el cuadrado de 8x8 en la posición 0xC320 de la memoria de vídeo es el siguiente:

.area _DATA

;;-- CPCtelera symbols
.globl cpct_drawSolidBox_asm

.area _CODE

;;===============================
;;  Main program entry
;;===============================
_main::

  ;; Draw a Box
  ld    de, #0xC320    ;; Location
  ld     a, #0x0F      ;; Cyan
  ld    bc, #0x0802    ;; Height, Width: 8x8 pixels
  call  cpct_drawSolidBox_asm

  jr .

Ahora lo ensamblamos y ejecutamos con winape. Para hacer el proceso más sencillo, utilizamos este alias:

alias do="make && cpct_winape -a"

Lo que se hace es llamar a make y en caso de que se haya ensamblado correctamente, se ejecuta el comando cpct_winape -a

Nos aparecerá esto:

En la documentación de la función vemos los parámetros que le tenemos que pasar:

  • DE: Dirección de video a partir de la que dibujar el recuadro
  • A: Patrón de color a usar. En nuestro caso 4 píxeles azules
  • BC: La altura y la anchura. ¡OJO! La altura es en píxeles y la anchura en bytes!!!

En el fichero practica.map encontramos las etiquetas definidas y los valores asignados:

Value     Global                              Global Defined In Module
-----     --------------------------------   ------------------------
00004000  _main                              
0000400D  cpct_drawSolidBox_asm               cpct_sprites

Nuestro programa (_main) está situado a partir de la dirección 0x4000. La función cpct_drawSolidBox_asm está situada a partir de la dirección 0x400D. El símbolo está definido dentro del módulo cpct_sprites

En el fichero main.lst vemos el código máquina que se ha generado para cada parte de nuestro fichero main.s

   0000                      11 _main::
                             12 
                             13   ;; Draw a Box
   0000 11 20 C3      [10]   14   ld    de, #0xC320    ;; Location
   0003 3E 0F         [ 7]   15   ld     a, #0x0F      ;; Cyan
   0005 01 02 08      [10]   16   ld    bc, #0x0802    ;; Height, Width: 8x8 pixels
   0008 CD 00 00      [17]   17   call  cpct_drawSolidBox_asm
                             18 
   000B 18 FE         [12]   19   jr .

Las direcciones de etiquetas globales se dejan a 0 porque el ensamblador no conoce sus direcciones de memoria. Será luego el linker el que les asigne el valor correcto. Así por ejemplo, en la instrucción de call cpct_drawSolidBox_asm se genera el siguiente código máquina: CD 00 00. La dirección del call es 0000

En el fichero main.rst , generado por el linker, ya podemos ver las direcciones resueltas y sus valores reales:

   4000                      11 _main::
                             12 
                             13   ;; Draw a Box
   4000 11 20 C3      [10]   14   ld    de, #0xC320    ;; Location
   4003 3E 0F         [ 7]   15   ld     a, #0x0F      ;; Cyan
   4005 01 02 08      [10]   16   ld    bc, #0x0802    ;; Height, Width: 8x8 pixels
   4008 CD 0D 40      [17]   17   call  cpct_drawSolidBox_asm
                             18 
   400B 18 FE         [12]   19   jr .

Función drawHero()

El código que pinta el persona lo llevamos a una función: drawHero(). Para definir funciones simplemente ponemos su etiqueta y escribirmos el código. Añadimos una función de cabecera para indicar lo que hace. La llamamos desde main:

.area _DATA

;;-- CPCtelera symbols
.globl cpct_drawSolidBox_asm

.area _CODE

;;===============================
;; drawHero
;;
;; DESTROYs: AF, BC, DE, 
;;===============================
drawHero:
  ;; Draw a Box
  ld    de, #0xC320    ;; Location
  ld     a, #0x0F      ;; Cyan
  ld    bc, #0x0802    ;; Height, Width: 8x8 pixels
  call  cpct_drawSolidBox_asm
  ret

;;===============================
;;  Main program entry
;;===============================
_main::

  call drawHero

  jr .

Para dibujar el persona necesitamo definir dos variables, que contengan las posiciones x,y del personaje. ¡OJO! La variable x está en bytes, mientras que la de y en pixeles. Los rangos son por tanto:

  • x: [0-79]
  • y: [0-199]

Las variables las definidmos en el área _DATA, usando las directivas .db

.area _DATA

;;===================================
;; VARIABLES
;;===================================
hero_x: .db #39 ;;-- x position (in bytes [0-79])
hero_y: .db #80 ;;-- y posicion (in pixels [0-199])

.area _CODE

...

Las variables de posición x,y se tienen que convertir a memoria de vídeo. Usamos la función cpct_getScreenPtr-asm. La nueva función drawHero() queda ahora así:

;;===============================
;; drawHero
;;
;; DESTROYs: AF, BC, DE, HL
;;===============================
drawHero:

  ;;-- 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!)
  ld     a, #0x0F      ;; Cyan
  ld    bc, #0x0802    ;; Height, Width: 8x8 pixels
  call  cpct_drawSolidBox_asm
  ret

Al ejecutarlo, este es el resultado:

Sesión 2.2

Click to see the youtube video

Leer el teclado

Vamos a mover nuestro personaje con las teclas. Para ello tenemos que leer el teclado. Implementamos la función checkUserInput

;;===============================
;; 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
    ;;-- Increase the hero_x value by 1 (Hero_x = Hero_x + 1)
    ld a, (hero_x)  ;;-- A =  hero_x
    inc a           ;;-- A = A + 1
    ld (hero_x),a   ;;-- hero_x = A

  d_not_pressed:
  ret

Se usa la función cpct_scanKeyboard de cpctelera, que realiza una lectura del teclado y almacena el estado de las teclas pulsadas

Llamando a la función cpct_isKeyPressed_asm se comprueba si una tecla particular se ha apretado. En nuestro ejemplo hacemos que el personaje se mueva hacia la derecha con la tecla D por lo que le pasamos el valor Key_D en HL. Si la tecla está pulsada se incrementa la variable hero_pos

Los códigos de las teclas están accesible mediante este include:

.include "keyboard/keyboard.s"

Borrado del personaje

Para mover el personaje hay que borrarlo y dibujarlo en la nueva posición. Modificamos la función drawHero para pasarle como parámetro (a través del registro A) el patrón de color: 0x0F (Cyan) para el personaje y 0x00 (fondo) para borrarlo

;;===============================
;; 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

Para no perder el valor del registro A, lo metemos en la pila (push AF) y lo recuperamos (pop AF) antes de llamar a la función drawSolidBox

Bucle principal

Este es el bucle principal del programa:

;;===============================
;;  Main program entry
;;===============================
_main::

  ;; Erase previous hero
  ld a,#0x00            ;; Color pattern: 0 (Background)
  call drawHero

  call checkUserInput   ;; Check if user pressed keys
  
  ;;-- Draw the hero
  ld a,#0x0F            ;; Color pattern: Cyan
  call drawHero         

  ;;-- Wait for the raster to finish
  call cpct_waitVSYNC_asm
  
  jr _main

Tras dibujar el personaje en la nueva posición, se llama a la función waitVSYNC para esperar a que el raster llegue al final. Cuando eso ocurre se repite el ciclo: se borra el personaje actual, y se dibuja en la nueva posición

Usar la función waitVSYNC hace que el personaje se dibuje sin parpadeos, ya que cuando pasa el raster el fotograma es estable

Programa final

.area _DATA

;;===================================
;; VARIABLES
;;===================================
hero_x: .db #39  ;;-- x position (in bytes [0-79])
hero_y: .db #80  ;;-- y posicion (in pixels [0-199])

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

;;===============================
;; 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
    ;;-- Increase the hero_x value by 1 (Hero_x = Hero_x + 1)
    ld a, (hero_x)  ;;-- A =  hero_x
    inc a           ;;-- A = A + 1
    ld (hero_x),a   ;;-- hero_x = A

  d_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

;;===============================
;;  Main program entry
;;===============================
_main::

  ;; Erase previous hero
  ld a,#0x00            ;; Color pattern: 0 (Background)
  call drawHero

  call checkUserInput   ;; Check if user pressed keys
  
  ;;-- Draw the hero
  ld a,#0x0F            ;; Color pattern: Cyan
  call drawHero         

  ;;-- Wait for the raster to finish
  call cpct_waitVSYNC_asm
  
  jr _main
  • Resultado:

Tareas para practicar

Dibujar suelo

El suelo está implementado en la función drawGround()

;;===============================
;; 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

Hay que llamar 2 veces a la función drawSolidBox, ya que no permite usar anchos mayores de 64. Para cubrir todo el suelo hay que dibujar dos veces

El bucle principal queda así:

;;===============================
;;  Main program entry
;;===============================
_main::

  ;;-- Draw the Ground in red color
  ld a, #0xFF     ;;-- Red pattern
  call drawGround

  main_loop:
    ;; Erase previous hero
    ld a,#0x00            ;; Color pattern: 0 (Background)
    call drawHero
  
    call checkUserInput   ;; Check if user pressed keys
    
    ;;-- Draw the hero
    ld a,#0x0F            ;; Color pattern: Cyan
    call drawHero         
  
    ;;-- Wait for the raster to finish
    call cpct_waitVSYNC_asm
    
    jr main_loop
  

Movimiento a izquierda y derecha

En la función checkUserInput se comprueba la tecla A tras la D, y se decrementa la variable hero_x si se ha pulsado

Evitar que salga por los lados

Para que el personaje esté en la misma horizontal hay que restringir el valor de hero_x a [0, 79]. Dentro de la función checkUserInput modicamos el valor de la variable sólo si está dentro del rango

;;===============================
;; 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
    ;;-- Increase the hero_x value by 1 (Hero_x = Hero_x + 1)
    ld a, (hero_x)  ;;-- A =  hero_x

    ;;- Check if the hero_x has reached its maximum value
    cp #78
    jr z,checkUserInput_end  ;;-- Max value reached: end

    ;;- No max reached: increase the hero_x by 1
    inc a           ;;-- A = A + 1
    ld (hero_x),a   ;;-- hero_x = A

    jr checkUserInput_end

  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
    ;;-- 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, checkUserInput_end   ;;-- Yes: Finish

    ;;-- Decrease the hero_x
    dec a
    ld (hero_x), a

  a_not_pressed:
  checkUserInput_end:
  ret
  • Resultado

Sesión 2.3

Click to see the youtube video

Función moveHero

El movimiento del personaje se consigue mediante la actualización de la variable hero_x. Al actualizarla hay que comprobar también que no se rebasen sus límites. Por ello creados las funciones moveHeroRight y moveHeroLeft

;;=============================================
;; 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

Función checkUserInput

La función de comprobar el teclado queda ahora más limpia. Sólo se hacen cosas relacionadas con el teclado y se llama a las acciones correspondientes

;;===============================
;; 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

Implementación del salto

El salto se activa con la tecla 'w'. Al hacerlo, se actualiza la variable hero_y con un cierto incremento en cada fotograma, hasta que se acaba el salto. La variable hero_jump se usa para identificar todos los fotogramas del salto. Inicialmente vale -1 que significa que no se está saltando

Los incrementos a aplicar en cada fotograma se almacenan en la tabla jumptable (que se llama tabla de salto):

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

En el bucle principal se llama a la función jumpControl para actualizar la posición y del personaje si se está realizando el salto

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 acceder a la tabla, leer el incremento actual, se lo aplica a la variable hero_y y actualiza hero_jump para apuntar al siguiente valor de la tabla. El numero 0x80 se usa para indicar el final de la tabla

;;=====================================
;; 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,update_jump_index

    ;;-- 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
    jr continue_jump

  update_jump_index:
    ld a,#-1
    ld (hero_jump),a

  continue_jump:
  ret

Desde la función checkUserInput se llama a la función startJump cada vez que se aprieta la tecla W. En la función se comprueba si ya se está saltando. Si es así, se continua

;;=============================================
;; 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
  • Resultado:

Clone this wiki locally