Skip to content

Sesión 5 Videojuegos1

Juan Gonzalez-Gomez edited this page Oct 29, 2021 · 45 revisions

Contenidos

Sesión V1-5.1: Revisión de dudas y Consejos en ensamblador Z80

Click to see the youtube video

Sesión V1-5.2: Efecto de scroll por software en ensamblador Z80

Click to see the youtube video

Llevando el mapa a map.s

Refactorizamos el juego para llevarnos el mapa actual (el laberinto) al fichero map.s, que se llama desde main. Así es como lo tenemos:

.area _DATA
.area _CODE
;;==============================================
;;==============================================
;; PRIVATE DATA
;;==============================================
;;==============================================

.include "cpctelera.h.s"
.globl _g_tileset
.globl _level0

;;-- Constants
map_width = 40
map_height = 32

;;-- Variables
viewp_ptr:  .dw #_level0  ;;-- Pointer of the viewport

;;=========================================
;; Map initialization
;;=========================================
map_initialize::

  ;;-- Draw the map
  ;;-- Configure the tileset
  ld hl,#_g_tileset
  call cpct_etm_setTileset2x4_asm

  ret

;;=========================================
;; Draw the map
;;=========================================
map_draw::

  ;;-- Draw the level0
  ld hl, (viewp_ptr)
  push hl
  ld hl,#0xC000
  push hl
  ld bc,#0x0000   ;;-- Tile 0,0
  ld de,#0x2028   ;;-- Height/Width in tiles (of the map)
  ld a,#0x28      ;;-- Number of tiles in the row
  call cpct_etm_drawTileBox2x4_asm  ;; Draw the map!!
  ret

La inicialización del main.s queda así:

;;============================================
;; GAME INITIALIZATION
;;============================================
initialize_game:
  ;;-- Disable Amstrad firmware. Needed for changing
  ;;-- the video mode
  call cpct_disableFirmware_asm

  ;;-- Change the video modeo to 0 (160x200 16 colors)
  ld c,#0
  call cpct_setVideoMode_asm

  ;;-- Change the color palette
  ld hl, #_sprite_palette
  ld de, #16
  call cpct_setPalette_asm

  ;;-- Initialize the music
  ld DE, #_song_princess7
  call cpct_akp_musicInit_asm

  ;;-- Set ISR
  ld hl, #isr
  call cpct_setInterruptHandler_asm

  ;;-- Map initialization
  call map_initialize

  ;;-- Draw the map
  call map_draw

  ret

Poniendo el mapa del oeste

Para implementar el scroll voy a poner el mismo mapa del vídeo. Se encuentra en el repositorio del juego kungfuguns

El mapa que viene tiene una anchura de 320. En el vídeo usan uno de 166, así que voy a usar el mismo. Desde el tiled cambio el tamaño y genero el fichero level0_166.tmx que ya tiene ese tamaño

En el fichero map2.s se definen las constantes y se lee el nuevo mapa:

.area _DATA
.area _CODE
;;==============================================
;;==============================================
;; PRIVATE DATA
;;==============================================
;;==============================================

.include "cpctelera.h.s"
.globl _g2_tileset
.globl _g2_level0

;;-- Constants
map2_width = 166  ; Map total width
map2_height = 17  ; 

;;-- Variables
viewp_ptr:  .dw #_g2_level0  ;;-- Pointer of the viewport

;;=========================================
;; Map initialization
;;=========================================
map2_initialize::

  ;;-- Draw the map
  ;;-- Configure the tileset
  ld hl,#_g2_tileset
  call cpct_etm_setTileset2x4_asm

  ret

;;=========================================
;; Draw the map
;;=========================================
map2_draw::

  ;;-- Draw the level0
  ld hl, (viewp_ptr)
  push hl
  ld hl,#0xC000
  push hl
  ld bc,#0x0000   ;;-- Tile 0,0
  ld de,#0x1128   ;;-- Height/Width in tiles (of the map)
  ld a,#map2_width   ;;-- Number of tiles in the row
  call cpct_etm_drawTileBox2x4_asm  ;; Draw the map!!
  ret

También he comentado la bola que se movía, la colisión y la bala. Sólo he dejado el héroe (comecocos). Esto es lo que sale ahora:

Los colores cambian con respecto al vídeo porque estoy usando la paleta anterior. De momento lo dejo así

Haciendo scroll del mapa

Para hacer scroll del mapa hay que incrementar la variable viewp_ptr. Definimos estas dos nuevas funciones en map2.s:

;;=========================================
;; Checks keyboard and scrolls the map
;;=========================================
map2_update::
  ;;-- Check for key 'D' being pressed
  ld hl, #Key_D               ;;-- hl = Key_D Keycode
  call cpct_isKeyPressed_asm
  ret z

    ;;-- D is pressed
    call map2_scrollRight
  
  ret

;;=========================================
;; Scrolls the map to the right
;;=========================================
map2_scrollRight::
  ld hl, (viewp_ptr)
  inc hl
  ld (viewp_ptr), hl
  
  ret

Una es para hacer scroll y la otra para actualizar el mapa cuando se aprieta la tecla

Desde el main llamamos a las funciones del mapa (desde el bucle principal)

  main_loop:

    call hero_erase     ;; Erase the hero

    call hero_update     ;; Update the Hero
    call map2_update      ;; Update the map
 
    call hero_draw      ;;-- Draw the hero
    call map2_draw
  
    ;;-- Wait for the raster to finish
    call cpct_waitVSYNC_asm

    jp main_loop

Como estamos usando el teclado, hay que incluir en map2.s el fichero de las constantes el teclado. Sin embargo, como también se usa en hero.s, hay un buffer que se redefine y da error. Para solucionarlo hay que copiar el fichero keyboard.s de cpctelera a nuestra carpeta de proyectos con el nombre keyboard.s.h, y eliminar esta línea:

_cpct_keyboardStatusBuffer:: .ds 10

¡Nuestro programa ya hace scroll!

El problema que hay ahora es que va 3 veces más lento!!!. Este scroll pesa mucho

Sesión V1-5.3: Scroll por software + double buffer en ensamblador Z80

Click to see the youtube video

Estimando el tiempo en dibujar el mapa

Dibujar el mapa lleva mucho tiempo. Tarda 3 veces más que cuando no se redibuja. En la documentación de cpctelera se encuentra el tiempo que tarda (parametrizado). Para este ejemplo tarda 50.000µs (50ms). El raster tarda aproximadamente 20ms (un poquito menos) en dibujar un fotograma. Por eso, ahora estamos tardando en torno a 3 veces más

Vamos a hacer un experimento para verlo "visualmente", cambiando el color del borde. Para ello usamos esta macro:

;;===============================
;; Macro: Change the border color
;; Useful for debuging
;;===============================
.macro setBorder color
    ;;-- H: Set the color
    ;;-- L: 0x10 means Border color
    ld hl, #0x'color'10  ;-- The character ' is used for concatenating elements
    call cpct_setPALColour_asm
.endm

En el bucle principal ponemos el color del borde a 1. Antes de dibujar el personaje lo cambiamos a 2, y al terminar lo ponemos otra vez a 1. De esta forma vemos cuánto tarda en dibujarse el personaje. El resto del tiempo se dedica al mapa

El personaje que tengo en mi programa es pequeño. Voy a dibujarlo 3 veces para que tarde más tiempo y poder verlo mejor (como en el vídeo). Este es el bucle principal:

main_loop:

    setBorder 1

    call hero_erase     ;; Erase the hero
    call hero_update     ;; Update the Hero
    call map2_update      ;; Update the map

    setBorder 2
    call hero_draw      ;;-- Draw the hero
    call hero_draw
    call hero_draw
    setBorder 1

    call map2_draw
  
    ;;-- Wait for the raster to finish
    call cpct_waitVSYNC_asm

    jp main_loop

Esto es lo que vemos al ejecutarlo:

En la parte superior se ve el color verde. Pero además este color parpadea. Lo verde es lo que tarda el personaje en dibujarse y lo gris el mapa. Primero se dibujar verde, luego gris hasta completar el fotograma. En el siguiente fotograma se dibuja todo gris y en el tercero también. Luego vuelve a comenzar. Por ese se ve que el verde parpadea: está sólo durante 1 fotograma, y durante 3 no se muestra. Por eso se ve parpadeando

Si se comenta la llamada call map2_draw para que no se redibuje el mapa, entonces se verá el color verde sin parpadeo

Viendo ese color verde, si hay que dibujar 6 sprites ya nos hemos comido todo el tiempo del fotograma. Y no sólo eso. Sino que hay sprites que se dibujan cuando el raster va por la mitad, y nunca se verán en pantalla porque el raster ya ha pasado (en ese momento no estaban en la memoria de video, se han añadido después, por lo que ya no se ven). Todo esto genera probelmas de flikering (parpadeos)

Para poder redibujar el mapa, y además solucionar problemas de flikering... NO HAY QUE DIBUJAR directamente en la memoria de vídeo, sino que ha que utilizar el doble buffer

Doble buffer

La idea es dibujar en otra parte de la memoria, de forma que la imagen que saque el raster permanezca fija durante todo el proceso. Mientras el raster actua, en paralelo se dibuja en la parte de la memoria oculta. Al terminar el raster se dibujará esta nueva imagen (que es estable). Y así se continúa todo el rato: mientras una la usa el raster, se dibujar en la otra. Y luego se cambian

La memoria de vídeo va de la dirección 0xC000 hasta la 0xFFFF por defecto (4K). El raster comienza por la direccion 0xC000. Pero eso se puede cambiar. Le podemos indicar que dibuje el bloque de 4K situado a partir de la dirección 0x8000

Lo importante es mantener la coherencia entre los dos buffers

De la dirección de vídeo, los bits más significatimos se llaman página y los menos significativos offset

Para cambiar la dirección de comienzo de la memoria de vídeo usamos las función cpct_setVideoMemoryPage y cpct_setVideoMemoryOffset

En map2.s definimos una variable que contiene el puntero al buffer de video:

videoPtr:   .dw #0x8000   ;;-- Video buffer address

La función de dibujado del mapa la modificamos para aceptar como parámetro (por DE) la dirección del buffer de video donde dibujar

;;=========================================
;; Draw the full map
;;
;; INPUTS:
;;   DE: Video memory location where to draw
;;=========================================
map2_draw::

  ;;-- Draw the level0
  ld hl, (viewp_ptr)   ;;-- Parameter for the cpct_etm_drawTileBox function
  push hl              ;;-- Insert tilemap data pointer
  push de              ;;-- Inser the videomemory location
  ld bc,#0x0000   ;;-- Tile 0,0
  ld de,#0x1128   ;;-- Height/Width in tiles (of the map)
  ld a,#map2_width   ;;-- Number of tiles in the row
  call cpct_etm_drawTileBox2x4_asm  ;; Draw the map!!
  ret

Y esta es la función que cambia los buffers. El cambio se hace con código automodificable. En l hay que cargar los valores 0x20 ó 0x30. La instrucción tiene dos bytes (el opcode 0x2E y luego el valor). Definimos la etiqueta modifier para que tenga la dirección de este valor

;;=========================================
;; Switches Video Buffers
;;=========================================
map2_switchBuffers::

  ;-- Self modifiyin code. It contains the address of the inmediate value 
  ;;-- to load in the l regiger
  modifier = . + 1
  ld l, #0x20  ;;-- 0x20 for memory address 0x8000, 0x30 for address 0xC0000 
  call cpct_setVideoMemoryPage_asm

  ;;-- Change the value (Next time it is called another values is used)
  ld hl, #modifier
  ld a, #0x10
  xor (hl)       ;-- Change between 0x20 and 0x30 values
  ld (modifier), a

  ;-- Change the current video pointer
  ld hl, #videoPtr+1
  ld a, #0x40
  xor (hl)
  ld (videoPtr+1), a

  ret

En el bucle principal, tras llamar a la función del sincronizado vertical, hacemos el cambio de los buffers:

main_loop:

    setBorder 1

    call hero_erase     ;; Erase the hero
    call hero_update     ;; Update the Hero
    call map2_update      ;; Update the map

    setBorder 2
    call hero_draw      ;;-- Draw the hero
    call hero_draw
    call hero_draw
    setBorder 1

    call map2_getVieoPtr
    ex de,hl       ;;-- DE=Pointer to the videoBuffer
    call map2_draw
  
    ;;-- Wait for the raster to finish
    call cpct_waitVSYNC_asm

    ;;-- Switch the buffers
    call map2_switchBuffers

    jp main_loop

Además tenemos que borrar el buffer que está en la dirección 0x8000 inicialmente, porque tendrá valores aleatorios que se dibujarían

;;=========================================
;; Map initialization
;;=========================================
map2_initialize::

  ;;-- Draw the map
  ;;-- Configure the tileset
  ld hl,#_g2_tileset
  call cpct_etm_setTileset2x4_asm

  ld hl, #0x8000
  ld de, #0x8001
  ld (hl), #00
  ld bc, #0x4000
  ldir

  ret

Se utiliza la instrucción ldir que copia de la dirección de hl a de el número de bytes indicados en bc

Este es el resultado:

El scroll ya funciona bien. El personaje parpadea porque se está dibujando sólo en uno de los buffers (y no en el otro)

Sesión V1-5.4: Planificando y preparando la entrega

Click to see the youtube video

Conversion de .cdt a .wav

Los ficheros CDT son los que contienen nuestro programa, en el formato contenedor de cinta de casete. Para cargalos en un amstrad real hay que convertirlo a fichero .wav (fichero de sonido). Luego se reproduce en el móvil y la salida de audio se lleva a un casete reproductor

Para realizar la conversión a .wav se utiliza el programa CDT2WAV. El programa está en JAVA, por lo que es multiplataforma, aunque en la zona de descarga sólo estén disponibles los ficheros para _Windows.

Una vez descargado y descromprimido el zip, se ejecuta con el siguiente comando:

java -jar CDT2WAV.jar

Ahora abrimos el fichero .cdt generado con cpctelera al hacer make:

La conversión se realiza automáticamente y se almacena en el mismo directorio donde estaba el fichero .cdt. Desde el cdt2wav podemos escuchar el sonido también

Este es el fichero convertido: practica.wav

Clone this wiki locally