Sprites – Movimiento en X (bit 8)

En el post de hoy tenia pensado ir implementando un generador de números aleatorios, para que a intervalos de tiempo vayan cayendo tanques de combustible con los que recargar los Jets. Pero decidi introducir unos cambios gracias a que MI amigo Pablo Borobia se bajo el codigo e introdujo unos cambios para que los jugadores se muevan por toda la pantalla.

Originalmente no puse esa caracteristica porque implica un poco mas de programación, y conocimientos que al principio del tutorial no tenía. Hay que  chequear si ocurre un desbordamiento de carro y setear un bit extra, para posicionar el sprite en una parte de la pantalla u otra.

Un poco de teoría

La posicion X de los sprites se guardan en las direcciones $d000, $d002,  …. $d00e. Ahora bien, una posicion de memoria tiene una longitud de 8 bits, con lo que nos permite posicionar el sprite en las posiciones de 0 a 255 en la pantalla… y nuestra pantalla tiene 320 pixels de ancho (la parte central, no contemos los bordes, que se pueden abrir y mostrar los sprites alli… pero eso es una historia mas avanzada).

Para ello necesitamos utilizar el registro del VIC II que esta en la direccion $d010. Este tiene el bit 8 que nos falta de cada uno de los sprites, correspondiendo el bit 0 al bit 8 del sprite 0, bit 1 al sprite 1… bit 7 al sprite 7. Por ejemplo, la posicion X del sprite 0 nos queda determinada por la direccion $d000 y el bit 0 de la posicion $d010.

Y esto como lo trabajamos?

Buena pregunta, hay montones de forma, una que me parecio bastante correcta es la implementacion que a continuación voy a explicar (para el jugador 1, luego tenemos que ampliar todo al jugador 2, los disparos, etc.)


@chkLeft  lda            joy2      
          and            #4        ; left
          bne            @chkRight  
          
          jsr            checkFloorP1 ; solo se mueve si esta volando
          cpx            #1      
          beq            @chkRight  

          lda            #ptrJPLeft   ; pone sprite mirando a la izquierda
          sta            sprpoint  

          ; CODIGO ANTERIOR
          ; inc sprx

          ; NUEVO CODIGO
          sec                              ; seteo el carry, ya que voy a decrementar A
          lda            sprx              ; cargo en A posicion X sprite jugador 1
          sbc            #1                ; y resto 1
          bcc            @chkOvrL          ; si ocurre acarreo, llamo a checkOverflowP1
          sta            sprx              ; si no, estamos en la de antes, actualizo posicion X
          jmp            @chkRight         ; sigo checkeando el resto ...

@chkOvrL  jsr            checkOverflowP1   ; subrutina para controlar Bit 8 del jugador 1
          sta            sprx              ; actualizo posicion, con el bit 8 actualizado

Y mas adelante (al final de la maquina de estados):


; ------------- end of main animatePlayer1 ---------------------------                

; verifica posicion X, setea bit 8 spr 0
checkOverflowP1
          tax                           ; guardo A en X (lo necesito despues)
          lda            sprxBit8       l cargo en A $d010
          eor            #1             ; invierto el bit 1 (jugador)
                                        ; es simple: si ocurre un acarreo, es bit se invierte
                                        ; es todo lo que hay que hacer :) 
                                        ; a lo sumo me quedará inicializarlo en 0
                                        ; en initVars.asm
          sta            sprxBit8       
          txa                           ; restauro A, ya que al retornar tengo un STA... 
          rts

Y eso es todo, tenemos que repetir este codigo (con las variables/etiquetas que correspondan) para animatePlayer2, y ver como lo agregamos en la parte de codigo de los disparos… no demasiado complicado.

Y creo que eso es todo.

No: me faltó agregar la variable sprxBit8 = $d010, en vars.asm, si no este codigo no anda ni para atras…

Como siempre, el codigo completo lo pueden bajar desde el repositorio:
https://github.com/moonorongo/jp_wars.git
Ahora si,  en la próxima entrega vamos a implementar un generador simple de numeros pseudoaleatorios, el que utilizaremos para que aparezcan cayendo tanques de combustible.
Hasta la proxima!

Anuncios

Combustible de los JetPacs 1

En esta entrada vamos a implementar el indicador que figura debajo de HITS, concretamente el combustible de los jets. La idea detras de esto es agregar un poco de dificultad haciendo que los jets tengan una cantidad limitada de combustible, y que haya una posibilidad de recuperarlo con tanques que caerán aleatoriamente. Y para darle mas emoción, que se puedan destruir con los tiros (para, por ejemplo, evitar que el otro jugador agarre combustible).

Para comenzar, vamos a agregar una dirección de memoria que guarde la cantidad de combustible, para lo cual agregaremos la siguiente etiqueta en vars.asm:

    JP1Jet = $c003          ; cantidad de combustible del jet 1 

y luego la inicializamos en initVars.asm:

    ldx            #$ff       
    stx            JP1Jet    

También necesitaremos un ‘tick’ extra. Como hicimos previamente en otro post, vamos a agregar un poco de código para generar una señal, en este caso cada 64 loops, la que usaremos dentro de animatePlayer1.asm para decrementar el combustible cuando empujamos el joystick hacia arriba. La razón de usar un tick cada 64 loops es porque si no lo incluimos el combustible baja rapidamente, y consideré que cada 64 ciclos esta mas que bien (este código va en tickGenerator.asm).

; tick64 ------------------------------------
@tick64
          lda            internalCounter
          and            #64
          cmp            tick64        ; comparo con nuevo tick64
          bne            @setTick64_1   
          ldx            #0
          stx            tick64
          jmp            @skip     
@setTick64_1
          ldx            #1  
          stx            tick64

Ahora, con las variables definidas y el tick64 disponible, vamos a agregar el codigo para que decremente el combustible cada vez que ‘volamos’ (en animatePlayer1.asm):

@chkUp
          lda            joy2      
          cmp            #127      
          beq            @jmpNext  

          ldx            JP1Jet    ; check si hay combustible
          cpx            #$0       
          beq            @chkLeft  ; si se acaba... adiooos
          .
          .
          .
          beq            @chkLeft
          dec            spry      
          
          lda            tick64    
          cmp            #0        ; si no hay tick64 paso a revisar otra direccion
          beq            @chkLeft  
          dec            JP1Jet    ; y si no gasta combustible (c/64 frames)

@chkLeft  .
          .
          .
@jmpNext  
          rts

Y para actualizar en pantalla la cantidad de combustible tenemos que agregar 2 cositas mas: una llamada, al final de la maquina de estados en animatePlayer1.asm.

@exit          
          .
          .
          .
          jsr            updateJP1Jet
          rts
; ------------- end of main animatePlayer1 ---------------------------

y la subrutina ‘updateJP1Jet’ que se encargará de mostrar el valor en pantalla (la podemos agregar luego de la que actualiza los hits:

; actualiza el medidor de JET de JP1
updateJP1Jet
          lda            JP1Jet   
          jsr            convert2ascii
          sty            $0565
          stx            $0566
          sta            $0567
          rts

Y creo que eso es todo.
Como siempre, el codigo completo lo pueden bajar desde el repositorio:
https://github.com/moonorongo/jp_wars.git
La idea en la próxima entrega vamos a implementar un generador simple de numeros pseudoaleatorios, el que utilizaremos para que aparezcan cayendo tanques de combustible.
Hasta la proxima!

Animaciones – explosiones

En esta entada voy a explicar como agregar una animacion para cuando el jugador es impactado por un laser.
Inicialmente haciamos algo tan sencillo como incrementar o decrementar la posicion del jugador impactado por 3 pixeles, luego lo mejoramos con una caida ‘catastrofica’ cuando implementamos la maquina de estados, y ahora le pondremos una animacion.
Para que la animacion no pase tan rapido agregue un bloque de codigo que genera un ‘tick’ cada 4 frames, que lo usaremos para avanzar un frame de la animacion.

Primero, un vistazo y explicacion al codigo del ‘Tick Generator’ (seguramente, cuando mas adelante necesitemos otros intervalos los agregaremos aqui)


tickGenerator          
    lda     internalCounter   ; direccion contador interno programa
                              ; se incrementa en cada ciclo
    and     #4                ; si el bit 2 esta activado 
    sta     tick4             ; guardo tick4 anterior 
                              ; usando la direccion tick4 como temporal          
    inc     internalCounter

    lda     internalCounter   
    and     #4         
    cmp     tick4             ; comparo con nuevo tick4
                              ; si es distinto hay un cambio de estado
    bne     @setTick4_1       ; entonces genero un 'tick'          

    ldx     #0
    stx     tick4             ; si no pongo tick4 en 0

    jmp     @skip     
    
@setTick4_1
    ldx     #1  
    stx     tick4 
    
@skip
    rts

El funcionamiento del generador de ticks es muy sencillo, simplemente tenemos una direccion de memoria llamada internalCounter, que la incrementamos en cada loop, y que haciendo diferentes AND’s (1,2,4,8,16,32,64,128) podemos generar una suerte de mensajes cada 1, 2, 4… 128 frames (cada loop es un frame).

Con el generador de ticks podemos explicar el siguiente codigo, que corresponde al STATUS_3:


@statusJP_3                    ; --------------- STATUS 3 -----
    ldx    gravityCounter      ; Bloque controlador de gravedad  
    dex                        ; si no pongo esto
    stx    gravityCounter      ; la animacion pasa muy rapido
    cpx    #$0                 ; quiza a la brevedad 
    bne    @exit               ; lo meta en una subrutina
    ldx    #gravity
    stx    gravityCounter
                               ; fin bloque controlador gravedad
                               
    lda    tick4               ; me fijo si hay un tick4
    cmp    #0                  ; si no hay tick
    beq    @skipIncFallCounter ; sigo de largo
                               ; si hay tick4 (c/4 frames)
    inc    fallCounter         ; incremento fallCounter 
         
@skipIncFallCounter          
    lda    spry                ; en A cargo posicion Y de JP
    adc    fallCounter         ; sumo fallCounter 
    sta    spry                ; y actualizo

    ldx    fallCounter    ; tambien voy a usar fallCounter
    dex                   ; como contador de frames (0 a 4)
    cpx    #4             ; hasta que llegue a 4 
    beq    @explodeEnded  ; si es igual, que no asigne ningun frame mas

    txa                   ; transfiero x -> a
    adc    #ptrJPExplode  ; lo sumo a primer ptr explosion
    sta    sprpoint       ; lo asigno al ptr de JP
         
@explodeEnded          
                        ; controlo si decremento pos X de JP
                        ; segun para donde esta mirando el otro
    ldx    sprpoint2    ; (si recibe el tiro de frente o detras)
    cpx    #ptrJPRight  ; si esta mirando a la derecha
    bne    @decxspr2    ; va a decrementa sprx

    ldx    sprx       
    inx              ; si mira a la izq incrementa
    stx    sprx      
    jmp    @chkFloor  
         
@decxspr2
    ldx    sprx      
    dex              ; decrementa si mira a la derecha
    stx    sprx      


@chkFloor
    jsr    checkFloorP1  ; compara si llego al piso
    cpx    #1            
    bne    @exit         ; si no, sigue

    ldx    #0            ; si llego al piso 
    stx    fallCounter   ; inicializa fallCounter

    ldx    #0            ; cambia STATUS a 0 
    stx    statusJP1 

@exit                             
    rts

y finalmente, un video con el resultado final:

Como siempre, el codigo completo lo pueden bajar desde el repositorio:
https://github.com/moonorongo/jp_wars.git

Ademas: el compilado para descargar: link
Hasta la proxima!

Maquinas de estados

En la entrada de hoy voy a explicar el concepto de ‘maquina de estado’ (no se si se llama asi, pero asi lo aprendi yo). Basicamente  es separar el codigo en diferentes ‘bloques’ que se ejecutaran segun un numero de estado. Para el caso que nos compete, que son los jugadores, vamos a diferenciar el estado de los mismos en 4:

  • STATUS 0: Inicializacion del jugador
  • STATUS 1: Animacion de entrada: una pequeña animacion, durante la cual el jugador es inmune a los disparos (si no otro jugador lo suficientemente habilidoso directamente no lo dejaria jugar)
  • STATUS 2: El codigo de deteccion de movimientos, etc (todo lo que estaba hasta ahora)
  • STATUS 3: Animacion de muerte del jugador

Entonces, si yo tengo una variable statusJP, en base al valor de la misma voy a ejecutar un codigo u otro. Esto en C seria algo asi:


  switch(statusJP1) {
    case 0 : // inicializacion de todas las vars de jugador 1
        statusJP = 1;
        break;
    case 1 : // animacion entrada
             if (condicion_terminacion_animacion_entrada) {
               statusJP = 2;
             }
             break;
    case 2 : // estado normal de juego
             .
             .
             .
             .
             break;
    case 3 : // muerto
             // animacion muerte 
             // y lo que necesitemos
             if (termino_animacion_muerte) {
               statusJP = 0;
             }
             break;
  }
  

En assembler es un un poquito mas complicado, ya que no disponemos de la instruccion switch. Aqui muestro el codigo del player 2 (lo limpie un poco, para que no quede kilometrico – el codigo original esta en el repositorio).


animatePlayer2
                                   ; -------------- STATUS 0 -------
          ldx            statusJP2 
          cpx            #0
          bne            @statusJP2_1
                                   ; INIT SPRITE JP2
          ldx            #233      ; posicionamos jetpac 2
          stx            sprx2
          ldx            #0        ; que salga desde arriba de la pantalla...
          stx            spry2     
          lda            #ptrJPLeft      
          sta            sprpoint2  ; jet pac 2 mirando a la izquierda
                        
          ldx            #1        
          stx            statusJP2 
          
@statusJP2_1                       ; -------------- STATUS 1 -----------------
                                   
          ldx            statusJP2 
          cpx            #1        
          bne            @statusJP2_2
          
          inc            spry2      
          ldy            spry2      
          cpy            #50       
          beq            @set_statusJP2_2
          
          jmp            @exit     
          
@set_statusJP2_2
          ldx            #2
          stx            statusJP2
          
          
@statusJP2_2                       ; -------------- STATUS 2 -----------------------
                                   ; todo el codigo que estaba antes va aqui...
          
          
@statusJP2_3                       ; --------------- STATUS 3 -----------------
             
          ldx            gravityCounter2
          dex
          stx            gravityCounter2
          cpx            #$0       
          bne            @exit
          ldx            #gravity
          stx            gravityCounter2

          inc            fallCounter2
          lda            spry2
          adc            fallCounter2
          sta            spry2
          
          ldx            sprpoint
          cpx            #ptrJPRight  ; si esta mirando a la derecha
          bne            @decxspr2    ; decrementa sprx
         
          lda            sprx2
          adc            fallCounter2  ; si mira a la izq incrementa
          sta            sprx2      
          jmp            @chkFloor  
          
@decxspr2 
          lda            sprx2     
          sbc            fallCounter2
          sta            sprx2      
          
          
@chkFloor
          jsr            checkFloorP2
          cpx            #1        
          bne            @exit
          
          ldx            #0      
          stx            fallCounter2

          ldx            #0        
          stx            statusJP2
          

@exit                             
          rts
; ------------- end of main animatePlayer2 ---------------------------                

Para muestra, aqui tengo un videos, observen que cuando recibe un tiro sale disparado hacia atras, a diferencia de antes, que se corria 3 mugrosos pixeles. Mi idea ademas es hacer una pequeña animacion con una explosion, que lo voy a dejar para el proximo post.

Como siempre, el codigo completo lo pueden bajar desde el repositorio:
https://github.com/moonorongo/jp_wars.git
Hasta la proxima!

Colisiones

Hoy vamos a explicar como se detectan las colisiones utilizando el VIC II. Estuve un rato para entender como era, pero finalmente lo pude hacer.
Basicamente hay 2 formas de detectar colisiones: una es viendo si los sprites que nos interesan estan colisionando (haciendo un polling), y la otra es utilizando interrupciones. Como la segunda opcion queda fuera de mi conocimiento (de momento) voy a explicar la primera.

El código (simplificado)

detectCollision
        ; detecto que ocurrio una colision de sprites
          lda            $d019    ; Direccion que indica en bit 2 que ocurrio una colision
          and            #$04     ; MASK bit 2
          cmp            #$04     ; testeo si bit 2 esta encendido
          bne            @skipSprDetect ; si NO sigo de largo

        ; SI OCURRIO UNA COLISION
          lda            $d01e    ; Direccion que indica cuales colisionaron 
          and            #JP2 + FJP1 ; mascara con los que quiero testear (2 + 4 = 6)
          cmp            #JP2 + FJP1 ; testeo si estan encendidos los 2
          bne            @skipSprDetect  ; si no, que siga de largo

        ; SI LLEGA ACA = COLISIONARON DISPARO jugador 1  CONTRA jugador 2
          ; aca hago todo lo que quiera: incremento puntaje, 
          ; resto energia... 
          ; animo el sprite impactado... 

          jmp            @skipSprDetect

@skipSprDetect
          rts

Vamos a trabajar con 2 registros: $d019, que indica que ocurrio una colision sprite-sprite, y $d01e, que indica cuales colisionaron.
Aca el tema es que, cuando quise fijarme directamente en la direccion $d01e (registro de colision de sprites-sprites), no funcionaba. Eso ocurria porque ese registro se llena cuando ocurre una colision, y eso hay que detectarlo con el registro $d019, concretamente el bit 2. Si tenemos colision, entonces RECIEN podemos ver cuales colisionaron.
Un detalle que lei en la documentacion es que el registro se limpia cuando se lee… o sea, que si queremos chequear varios sprites tenemos que cargar el valor del registro y guardalo de alguna forma (ya sea en memoria, en el stack… donde se nos ocurra).

El codigo simplificado esta comentado, se entiende muy facil, NO ES EL CODIGO final que subi al repositorio, ya que ese codigo hace varias cositas mas:

  • detecta colisiones fire1 y spr2
  • fire2 y spr1
  • registra el impacto en un contador
  • muestra la cantidad de hits de ambos players
  • hace una pequeña animacion…

Pero como tengo idea de implementar maquinas de estado en ambos players (mas adelante explico que es), decidi explicar mas teoricamente esto de las colisiones, y luego de hacer las maquinas de estado poner todo mas completo.

A partir de ahora no voy a poner tanto codigo ya que ademas de codigo nuevo voy corrigiendo el codigo que escribi previamente, o hago alguna modificacion de ultimo momento, asi que prefiero explicar lo que hice, tratando de entenderlo uds y yo, y pueden ir bajando desde Github lo que tengo realizado hasta el momento:
https://github.com/moonorongo/jp_wars.git

Animacion Sprites Player 2

La entrada de hoy es corta, ya que a pesar que vamos a agregar un segundo jugador, lo unico que vamos a hacer es duplicar las variables, duplicar un par de archivos, agregar unas llamadas en el main.asm y… voila!!! tenemos el segundo jugador funcionando!!! magia… no, en realidad, son las ventajas de separar el codigo en archivos. A continuacion, un video de lo que me quedo:

Para lograr ello, lo primero que tenemos que hacer es duplicar las etiquetas que utilizamos para el jugador 1. por ejemplo:

agregados en vars.asm

; direcciones jetpac 2 -------------------------------------------------------
sprcolor2  = $d029
sprpoint2  = $07fa
sprx2      = $d004
spry2      = $d005
fire2     = $2a                   ; zero page direccion sin usar
gravityCounter2 = $04

; direcciones disparo
sprcolorFire2  = $d02a
sprpointFire2  = $07fb
sprxFire2      = $d006
spryFire2  = $d007
; dirFire2 esta en la direccion de memoria que me sobra del jet pac right

Tambien tenemos que inicializar estas variables, para ello agregamos en el archivo macros.asm (en la macro initVars) lo siguiente:

          ldx            #150      ; posicionamos jetpac 2
          stx            sprx2
          ldx            #139       
          stx            spry2

          ldx            #196      ; posicionamos disparo 2
          stx            sprxFire2
          ldx            #139       
          stx            spryFire2

          lda            #$05      
          sta            spractive ;activamos el sprite 0 y 2

          lda            #$03
          sta            sprcolor2  ; jetpac 2 color cyan

          lda            #$0a
          sta            sprcolorFire2  ; fire color red

          lda            #$0a      
          sta            sprxpandX ; expandimos X el disparo 1 y 2.

          lda            #ptrJPLeft
          sta            sprpoint2  ; jet pac 1 mirando a la izquierda

          lda            #ptrJPFire      
          sta            sprpointFire2  ; punteros de disparo

          lda            #$0       ; fire flag 
          sta            fire2     

          lda            #gravity
          sta            gravityCounter2

y tenemos que duplicar animatePlayer2.asm y animatePlayer2Fire.asm, vamos a Project properties y en el build order los ubicamos a continuacion de los del Player 1.
Ademas tenemos que modificar todas las referencias a las variables del player 1, a las nuevas variables.

Un agregado mas

Por ultimo, un detalle que me viene rompiendo las bolas desde que comence a programar… siempre que cargamos un prg en la c64 tenemos que ejecutarlo con SYS 4096… para ahorrarnos ese paso agregamos una llamada sys, que la generamos con una herramienta de CBM PRG Studio.
Agregamos un nuevo archivo asm, que lo nombraremos como syscall.asm. Luego vamos a Tools -> Generate SYS() Call, se nos abrira un popup, en Enter start addres ponemos $0801, hacemos click en ok, y nos generará algo como esto:

; 10 SYS4096

*=$0801

        BYTE    $0B, $08, $0A, $00, $9E, $34, $30, $39, $36, $00, $00, $00

Nota: El popup nos da 2 opciones para seleccionar, basicamente si queremos que la linea aparezca de una forma u otra… pero no se como sabe el programa que la direccion es esa (4096)… supongo que la saca del main.asm… pero creo que en ningun lado estoy poniendo que ese es el archivo principal, o lo que sea… en fin…

A partir de ahora no voy a poner tanto codigo ya que ademas de codigo nuevo voy corrigiendo el codigo que escribi previamente, o hago alguna modificacion de ultimo momento, asi que prefiero explicar lo que hice, tratando de entenderlo uds y yo, y pueden ir bajando desde Github lo que tengo realizado hasta el momento:
https://github.com/moonorongo/jp_wars.git

Animacion Sprites

Retomamos el codigo para esta vez comenzar a animar nuestro personaje… Hoy empiezo mostrando todo lo que hice en un video, y despues vamos con el codigo y las complicaciones, de paso vemos un poco de teoria que tuve que aprender.

Como podemos ver, ahora nuestro personaje hace bastante mas cosas. En principio el grafico ‘gira’ para el lado que se mueve, lo que obligó a modificar la rutina de disparo para que, segun para donde este mirando nuestro personaje, salga el laser.
Ademas hay gravedad, o algo parecido… ahora si pulsamos para arriba nos ‘impulsamos’, y si soltamos la gravedad nos tira para abajo hasta tocar el piso.
Como nuestro personaje no puede caminar… bah, no tengo ganas de hacer tooooda la animacion de las patitas… lo resolvi muuuy simple: si estas tocando el piso NO TE PODES MOVER.
La idea detras de esto es la siguiente:

  • Solo te podes mover si volas con el jetpac
  • Solo podes volar si tenes combustible
  • Si te quedas sin combustible quedas anclado en el piso (y seras masacrado horriblemente por tu competidor)
  • Podes recargar mas combustible con celdas de combustible que cae del cielo (y podes dispararle a las celdas para evitar que tu competidor agarre combustible)
  • Hablando de disparos: cada tiro que disparas te quita una unidad de energia (para no ponerte a disparar como loco)
  • Cada tiro que te ponen te resta 30 unidades de energia
  • Cada vez que volas perdes energia (no tanto como si te pusieran un tiro, pero perdes)… todavia a definir

O sea: perdes energia por cualquier cosa, y tenes que pelear por recuperarla.
De todo eso solo tengo implementado el primer punto, los restantes los ire implementando mas adelante.

Ahora ¡El codigo!

Lo primero que explicaré es como hice la animacion del jetpac (que mire para donde se está moviendo). Es super sencillo, abrí el archivo sprites.asm y definí un grafico identico al que habia realizado previamente, pero lo puse en espejo. Lo puse un label con nombre jetpacLeft, a continuacion de la definicion del disparo. Codigo a continuación:

jetpacLeft
          bits           ..........BBBB..........
          bits           ............BBB.........
          bits           ........BBBB.BB.B.......
          bits           ........BBBB.BB.B.......
          bits           ........BBBB.BB.B.......
          bits           .........BBBBB..BB......    
          bits           ................BB......
          bits           ..........BBBB.B.B......
          bits           .......B.BB..B.BBB......
          bits           .......B.BB..B.B.B......
          bits           ....BBBBB....B.BBB......
          bits           .........BBBB.BB.B......
          bits           ..........BBB.BBBB......
          bits           ......BBBBBB.BBBB.......
          bits           ......BBBBBB..B.B.......
          bits           ......BB.....B.B.B......
          bits           .............BBBBB......
          bits           .....BBB................
          bits           ....BBBB.....B.BB.......
          bits           .............B..B.......
          bits           ...............B..B.....
          byte           0

Luego agregue unas variables en vars.asm (en realidad las uso como constantes, pero bue… todo lo que definimos aca son punteros o constantes):

; constantes
gravity   = $05   ; usado para setear la 'fuerza' de la gravedad, 1 = super fuerte, mayor = mas debil
floorPosition = 223
topPosition = 52

ptrJPLeft = $23
ptrJPRight = $21
ptrJPFire = $22
; dirFire esta en la direccion de memoria que me sobra del sprite de disparo... mhmm          

ademas de una de nombre dirFire, que es el ultimo byte del sprite ptrJPRight, asi lo aprovechamos para algo (puse ese comentario con las demas variables porque me paso que no me acordaba donde la habia definido… si… es medio una cochinada, pero solo tengo 65535 espacios libres de memoria… y ni siquiera eso).
Las variables ptrJPLeft y ptrJPRight guardan el puntero de la ubicacion de los sprites. Podria haber utilizado los numeros, pero el codigo queda mucho mas claro asi. Estas vars/const (llamenlas como quieran) las utilizo en animatePlayer1.asm. A continuacion un codigo de ejemplo (no es el codigo exactamente, borre un pedacito para clarificar):

chkLeft   lda            joy2      
          and            #4         ; checkeo si es left
          bne            chkRight   ; si no voy a checkear right
                                    ; y si es left, sigo por aca
          lda            #ptrJPLeft ; cargo en a el valor de ptrJPLeft ($23)     
          sta            sprpoint   ; y lo establezco en el puntero del jetpac
          dec            sprx       ; y todo lo demas... .

Lo mismo para right, o sea, en cada cambio derecha/izquierda lo unico que hago es cambiar un numerito en el puntero del sprite del jetpac1. De la misma forma, si quisieramos hacer que camine, o que expulse fuego por el jetpac cuando vuela lo que deberemos hacer es armar un contador, que se incremente cada n frames, que segun el numero que establezca el puntero del frame correspondiente… es laborioso, pero no imposible.

Un detalle que tuve que cambiar fue el tema de los disparos. Antes siempre miraba hacia la derecha, por lo que la rutina de disparos era tan elemental como que cuando detectaba el boton de disparo procedia a:

  • tomar la posicion x e y del jugador, establecia la posicion x e y del disparo, y pasaba el status a disparando.
  • mientras estaba disparando en cada frame avanzaba 3 pixeles, y chequeaba que no haya llegado al final de la pantalla (si x es MAYOR o IGUAL que una determinada posicion – esto tiene profundas implicancias mas adelante).
  • si llegaba al final de la pantalla procedia a apagar el sprite de disparo, y pasaba a estado esperando.

Ahora tengo que tener en cuenta ademas para adonde esta mirando, e inicializar una variable dirFire para mas adelante, cuando este en status disparando, decremente o incremente la posicion del disparo.

Anteriormente dije que chequeaba si el disparo habia finalizado cuando llegaba al final de la pantalla, o sea a una posicion x MAYOR o IGUAL a un punto arbitrario. En assembler no es tan trivial como en cualquier otro lenguaje hacer una comparacion por mayor o igual (hay que hacer una operacion, y ver si ademas tenemos acarreo). Eso se hace cargando en A la posicion x del sprite, luego hacemos un ADC, que es una operacion para sumar, pero con acarreo, ponemos el resultado de la suma en la posicion x del sprite, y con BCS (Branch on Carry Set) saltamos a status0 si se produjo un acarreo (o sea la suma excedio 255). Codigo de ejemplo donde hago eso:

        lda            sprxFire  
        clc             ; seteamos el flag de acarreo en 0
        adc            #$02 ; cantidad de pixels que aumenta
        sta            sprxFire  
        bcs            @setStatus0        

Diferente es cuando en vez de sumar tenemos que restar. Hacemos exactamente lo mismo, pero con unas salvedades:

  • en vez de ADC usamos SBC, que a diferencia de ADC, BORRA el flag de acarreo cuando se excede el valor de una resta (o sea, da menos que cero). Eso nos lleva a …
  • en vez de CLC usamos SEC, que ESTABLECE el flag de acarreo, para finalmente chequear con
  • BCC (Branch on Carry Clear), lo mismo que el otro, pero cuando borra el Carry.

esta boludez me tuvo una semana, todo por no leer la puta documentacion. Quedais avisaos…

Otro detalle que agregue fue la gravedad y los limites superiores e inferiores. Para la gravedad agregue dos constantes gravity, que es el valor inicial del contador, y gravityCounter, que es el contador de gravedad. Al final de cada frame decremento gravityCounter y me fijo llego a cero. Si no llego a cero decremento el contador en 1, y si es igual a cero decremento la posicion Y del sprite e inicializo el gravityCounter con el valor de gravity.

next
          ldx            gravityCounter
          dex
          stx            gravityCounter
          cpx            #$0       
          bne            @exit

          jsr            checkFloorP1

          cpx            #1      ; check floor (si result checkFloorP1 es 1 es el piso)
          beq            @skipGravity

          inc            spry      ; gravity :P 

@skipGravity
          ldx            #gravity
          stx            gravityCounter

Pueden ver ademas que hago una llamada a una subrutina de nombre checkFloorP1. Esta rutina checkea que si el jugador llego al piso, y establece el registro x con 1 o 0, comparamos x con 1 o 0 (true o false, seria) y procedemos… a continuacion la rutina para checkear si llego al piso (y de paso la rutina para chequear el techo, de regalo).

; verifica posicion Y de P1, y retorna 1 en X si es el piso 
checkFloorP1
          ldx            spry      
          cpx            #floorPosition
          beq            @returnTrue    ; is equal
          bcs            @returnTrue    ; or greater ;) 
          ldx            #0        
          rts
@returnTrue
          ldx            #1        
          rts

; verifica posicion Y de P1, y retorna 1 en X si es el top
checkTopP1
          ldx            spry      
          cpx            #topPosition
          beq            @returnTrue    ; is equal
          ;bcs            @returnTrue    ; or greater ;) 
          ldx            #0        
          rts
@returnTrue
          ldx            #1        
          rts

A partir de ahora no voy a poner tanto codigo ya que ademas de codigo nuevo voy corrigiendo el codigo que escribi previamente, o hago alguna modificacion de ultimo momento, asi que prefiero explicar lo que hice, tratando de entenderlo uds y yo, y pueden ir bajando desde Github lo que tengo realizado hasta el momento:
https://github.com/moonorongo/jp_wars.git

Exportacion del fondo del juego

Previamente diseñamos la pantalla de fondo de nuestro juego, ahora vamos a incorporarla. Para ello necesitaremos exportar la imagen como .asm, y luego una pequeña rutina que copie estos datos al area de memoria de la pantalla. El post de hoy sera corto, ya que es realmente sencillo lo que hay que hacer.

Exportación de datos

Primero, carguemos el fondo que diseñamos en el tuto anterior en el ‘Screen editor’, luego vamos a File, y seleccionamos Export Assembler, tras lo que aparecera la siguiente pantalla:

dejamos las opciones como estan, ya que queremos la pantalla, asi como tambien los colores, y clickeamos en ok. Se abrirá una ventana con los datos exportados, los seleccionamos y copiamos, y los pegamos en un nuevo .asm que crearemos en nuestro proyecto. Para el fondo que yo hice me quedo algo asi:

screen.asm

*         = $2000
screen  BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$0A,$05,$14,$10,$01,$03,$20
        BYTE    $20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$20,$17,$01,$12,$13,$21,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$51,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$74,$0A,$10,$20,$31,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$08,$09,$14,$20,$20,$31,$30
        BYTE    $20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$06,$15,$05,$0C,$20,$39,$39
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$0A,$10,$20,$32,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$08,$09,$14,$20,$20,$31,$35
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$74,$06,$15,$05,$0C,$20,$32,$35
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$E9,$DF,$20,$20,$20,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$2E,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$E9,$A0,$A0,$DF,$20,$20,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$E9,$A0,$A0,$A0,$A0,$DF,$20,$20,$20,$20,$20,$2E,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$E9,$A0,$A0,$A0,$A0,$A0,$A0,$DF,$20,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$20,$2E,$20,$20,$20,$20,$20,$20,$E9,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$DF,$20,$20,$20,$20,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $68,$68,$68,$68,$68,$68,$68,$68,$68,$68,$68,$68,$68,$68,$68,$68,$68,$E9,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$DF,$68,$68,$68,$74,$02,$19,$20,$20,$20,$20,$20
        BYTE    $A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$74,$20,$20,$20,$20,$20,$20,$20
        BYTE    $A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$74,$0D,$13,$03,$09,$06,$15,$20
        BYTE    $A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$A0,$74,$20,$20,$20,$20,$20,$20,$20

screen_colour.asm

*         = $2400
colour  BYTE    $03,$01,$01,$01,$01,$01,$01,$01,$01,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$01,$01,$01,$01,$01,$01,$01,$01,$03,$03,$03,$07,$0C,$0C,$0C,$0C,$0C,$0C,$0C
        BYTE    $03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$01,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$07,$05,$05,$05,$03,$03,$03,$0C
        BYTE    $03,$03,$01,$03,$03,$03,$03,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$0C,$0A,$0A,$0A,$0A,$0A,$0C
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0B,$0B,$0B,$0B,$0B,$01,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$01,$0B,$0B,$07,$0C,$0C,$01,$0F,$0F,$01,$0C
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$03,$03,$03,$03,$03,$01,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$01,$0B,$0B,$0B,$0B,$0B,$07,$05,$05,$05,$05,$05,$05,$05
        BYTE    $05,$05,$03,$03,$03,$03,$03,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$05,$05,$05,$01,$05,$05,$05
        BYTE    $03,$0B,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$05,$05,$05,$05,$01,$05,$05
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$05,$05,$05,$05,$05,$05,$05
        BYTE    $03,$03,$03,$03,$03,$01,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$01,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$03,$03,$03,$03,$03,$03,$03
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$01,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$01,$0C,$0C,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0C,$0C,$0C,$07,$03,$03,$03,$03,$03,$03,$03
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0C,$0C,$0C,$07,$03,$03,$03,$03,$03,$03,$03
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0C,$0C,$0C,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$0B,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0C,$0C,$0C,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$03,$03,$03,$03,$0B,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0C,$0C,$0B,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0C,$0C,$0C,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $03,$03,$03,$03,$03,$03,$03,$0B,$0C,$0C,$0C,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0C,$0C,$0C,$07,$01,$01,$01,$01,$01,$01,$01
        BYTE    $0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0C,$0C,$0C,$0C,$0C,$0C,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$07,$07,$0C,$0C,$0C,$0C,$0C
        BYTE    $0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$0B,$07,$0C,$0C,$0C,$0C,$0C,$0C,$0C
        BYTE    $0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$07,$07,$07,$07,$07,$07,$07,$0C
        BYTE    $0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$07,$0C,$0C,$0C,$0C,$0C,$0C,$0C

En Project properties, en la parte de build order ubiquemoslos al final de todos, primero screen y luego screen_colour.

Solo queda agregar una pequeña macro que copie estos dos bloques de codigo a sus correspondientes direcciones (pantalla y color), como la siguiente (agregar al archivo macros.asm):

defm      copyScreen
          ldx            #$00      
@loop          
          lda            screen,x  
          sta            scrPtr,x  

          lda            screen + $100,x  
          sta            scrPtr + $100,x  

          lda            screen + $200,x  
          sta            scrPtr + $200,x  

          lda            screen + $300,x  
          sta            scrPtr + $300,x  

          lda            colour,x  
          sta            scrColPtr,x

          lda            colour + $100,x  
          sta            scrColPtr + $100,x

          lda            colour + $200,x  
          sta            scrColPtr + $200,x

          lda            colour + $300,x  
          sta            scrColPtr + $300,x

          inx
          cpx            #$00       
          bne            @loop     
          endm

e invocamos copyScreen en main.asm

*         = $1000

; no cambiar el orden de estas llamadas!
; (copyScreen pisa la inicializacion de los punteros de sprites)
          copyScreen
          initVars

main_loop
          jsr            animatePlayer1
          jsr            animatePlayer1Fire

          waitrt
          jmp            main_loop 

exit
          rts                      ; BASIC

Como ya saben a esta altura el codigo del proyecto con lo que tenemos desarrollado hasta el momento lo pueden bajar desde Github:
https://github.com/moonorongo/jp_wars.git

Fondo del juego

Vamos a diseñar un fondo sencillo para nuestro juego, simulando algunas estrellas con los caracteres PETASCII, asi le vamos dando un marco al juego. Para ello utilizaremos una de las herramientas que nos provee el CBM PRG Studio para diseñar pantallas. Para ello vamos al menú Tools, y seleccionamos Screen editor:

y se nos abrirá una ventana para editar una pantalla de C64.

El Editor

Aquí tenemos 4 áreas bien separadas:

Área de trabajo: nuestra pantalla, una matriz de caracteres, de 40×25.
Character set (Caracteres): El set que utilizaremos para trabajar. La C64 tiene 2 sets de 128 caracteres, el primero solo con mayusculas y caracteres PETASCII, y el segundo incluyendo las minusculas, con un set reducido de caracteres graficos. Del caracter 128 en adelante se repite el set, pero invertido (esto es clickeando en Reversed). El caracter que tengamos seleccionado aqui es el que se utilizará con la opcion Draw, subopcion Chars, del siguiente área.
Mode: Aqui especificamos como queremos dibujar en la matriz de pantalla. Los modos son los siguientes:

  • Draw: Modo dibujo, segun las opciones debajo que tengamos seleccionadas podremos dibujar con el caracter que tengamos seleccionado de Character set, o Lineas, o Cajas. Si seleccionamos cajas se nos habilita un boton para configurar la forma en que se crearan (ejemplo: si se hacen con un caracter seleccionado, o rellenas, o utilizando unos determinados PETASCII…). Si hacemos click con el boton izquierdo pinta, y si hacemos click con el derecho borra.
  • Select: Al seleccionar un area se nos habilitará el menu Selection, tras lo cual podemos cortar, copiar, mover la seleccion, colorear, etc.
  • Re-colour: Igual que Draw, pero aqui solo cambia el color de los caracteres sobre los que dibujemos.
  • Text: Aqui podemos ingresar texto.
  • Path: Podemos generar un path, y cuando queremos que lo ‘pase a chars’, vamos al menu ‘Path’ y seleccionamos ‘convert to characters’. No le encontre demasiada utilidad a esta opcion… Tal vez ustedes…

Colours: Aqui especificamos el color con el que pintaremos. A fin de mantener la simpleza del tutorial nosotros diseñaremos una pantalla en modo ‘Single colour’ (los modos multicolores o extendido los dejare para bastante mas adelante… si llego…). Seleccionamos el color con que queremos pintar de la opcion [11] Character colour (las otras se utilizan en los demas modos, y la de background no tiene sentido especificarla.)

Mi pantalla de fondo

Finalmente solo queda guardarla en un archivo, vamos a File, y seleccionamos ‘Save’. Se nos guardara en el ‘Project Explorer->Screen Designs’. En el próximo tutorial vamos a ver como exportar esta pantalla con sus colores, y como utilizarla en nuestro proyecto.

Como ya saben a esta altura el codigo del proyecto con lo que tenemos desarrollado hasta el momento lo pueden bajar desde Github:
https://github.com/moonorongo/jp_wars.git

Orden en el código

En este post vamos a organizar un poco el proyecto. Hasta ahora estabamos trabajando todo en un solo archivo, lo cual si bien es comodo al principio, en cuanto metemos un par de cositas se vuelve inmanejable.
Para ello vamos a separar el archivo en varios modulos. CBM Prg Studio nos permite fraccionar el codigo en distintos archivos, y luego especificar como estos archivos tienen que estar ordenados (al momento de compilar). Esto es necesario porque, por ejemplo, las definiciones de macros deben estar ANTES de ser invocadas.

Vamos a separar todo nuestro codigo en 6 archivos diferentes: main.asm, macros.asm, vars.asm, sprites.asm, animatePlayer1.asm, animatePlayer1Fire.asm. A medida que el proyecto crezca se iran agregando mas archivos. Los archivos vamos a ordenarlos de la siguiente manera (esta ventana es la de Project Properties):

 

En el frame ‘Project Files (shown in build order)’ tenemos que seleccionar ‘use specific build order’, asi de esa manera le decimos al compilador el orden correcto de los archivos.
Nótese que el primer archivo no es el main.asm, sino sprites.asm, y si observamos mas en detalle podemos ver que la columna ‘Start’ de dicho archivo dice ‘$840’. Ahi está especificando que el contenido de dicho archivo lo cargue en la posicion $840 de memoria. De esta manera nos evitamos las macros movedata y la duplicacion de datos (en las definiciones del sprite que luego copiabamos a una direccion accesible por el VIC II). Asi queda mas eficiente, mas bonito y nos ahorramos unos cuantos bytes.

Archivos

sprites.asm

*         = $0840
; dejamos 2 kbytes para la definicion de los sprites, ya quedan mapeados en un area de memoria que
; puede acceder el VIC II

jetpac    
          bits           ..........BBBB..........
          bits           .........BBB............
          bits           .......B.BB.BBBB........
          bits           .......B.BBB.BBB........
          bits           .......B.BBB.BBB........
          bits           ......BB..BBBBB.........
          bits           ......BB................
          bits           ......B.B.BBBB..........
          bits           ......BBB.B..BB.B.......
          bits           ......B.B.B..BB.B.......
          bits           ......BBB.B....BBBBB....
          bits           ......B.BB.BBBB.........
          bits           ......BBBB.BBB..........
          bits           .......BBBB.BBBBBB......
          bits           .......B.B..BBBBBB......
          bits           ......B.B.B.....BB......
          bits           ......BBBBB.............
          bits           ................BBB.....
          bits           .......BB.B.....BBBB....
          bits           .......B..B.............
          bits           .....B..B...............
          byte           0                          ; byte que se desperdicia

jetpacFire
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           B..B..BBBBBBBBBBBBBBBB.B
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................
          bits           ........................

Notese que al final de la definicion jetpac hay un byte, con valor 0, que no estaba antes. Esto es asi ya que el puntero de sprites del VIC accede de a 64 bytes, y el sprite necesita 63 bytes, por lo que necesariamente se va a desperdiciar 1 byte en cada sprite. Mas adelante veré si le puedo dar alguna utilidad (guardar algun estado, que se yo…)

vars.asm

raster    = $d012
cborde    = $d020
cfondo    = $d021
spractive = $d015
sprxpandX = $d01d          

; direcciones jetpac 
sprcolor  = $d027
sprpoint  = $07f8
sprx      = $d000
spry      = $d001
joy2      = $dc00
fire1     = $02         ; zero page direccion sin usar

; direcciones disparo
sprcolorFire  = $d028
sprpointFire  = $07f9
sprxFire      = $d002
spryFire      = $d003

skiprt    = $03         ; para pausar la ejecucion

Notese: agregamos una variable skiprt, con un valor arbitrario de $03. Esta variable se utiliza en una nueva rutina para controlar la velocidad de ejecucion. Anteriormente frenabamos la ejecucion en cada refresco de pantalla, lo cual era excesivamente lento. Ahora lo que hacemos es frenar cada 3 refrescos, con lo que aceleramos un poco el juego. Seguramente mas adelante quitaremos la macro waitrt, y la sustituiremos por una interrupcion, lo que seguramente es lo correcto de hacer. De momento es lo que hay…

macros.asm

defm      waitrt
          ldx            skiprt    
          cpx            #$00
          bne            @next     
          ldx            #$03      
          stx            skiprt    

@loop     ldx            raster
          cpx            #$0       
          bne            @loop     
@next     
          dec            skiprt    
          endm

defm      initVars
          lda            #$00      ; borde y fondo negro
          sta            cborde     
          sta            cfondo

          lda            #$03      ; inicializo skiprt (conteo hasta hacer un wait retrace)
          sta            skiprt

          ldx            #172      ; posicionamos jetpac
          stx            sprx
          ldx            #139       
          stx            spry

          ldx            #196      ; posicionamos disparo
          stx            sprxFire
          ldx            #139       
          stx            spryFire

          lda            #$01      
          sta            spractive ;activamos el sprite 0 

          lda            #$01      
          sta            sprcolor  ; jetpac color blanco

          lda            #$07      
          sta            sprcolorFire; fire color amarillo

          lda            #$02      
          sta            sprxpandX ; expandimos X el disparo.

          lda            #$21      
          sta            sprpoint  ; sprite 0 en $0840

          lda            #$22 
          sta            sprpointFire  ; sprite 0 en $0880

          lda            #$0       ; fire flag 0      
          sta            fire1     
          endm

main.asm

*         = $1000

          initVars

main_loop
          jsr            animatePlayer1
          jsr            animatePlayer1Fire

          waitrt
          jmp            main_loop 

exit
          rts                      ; BASIC

animatePlayer1.asm

animatePlayer1
          lda            joy2      
          cmp            #127      
          beq            next      

          lda            joy2      
          and            #1        ; up
          bne            chkDown   
          dec            spry      

chkDown   lda            joy2      
          and            #2        ; down
          bne            chkLeft   
          inc            spry      

chkLeft   lda            joy2      
          and            #4        ; left
          bne            chkRight  
          dec            sprx      

chkRight  lda            joy2      
          and            #8        ; right
          bne            chkFire   
          inc            sprx      

chkFire   lda            joy2      
          and            #16       ; fire
          bne            next      

          ldx            fire1     
          cpx            #0        
          bne            next      ; checkeo si ya disparo

          lda            #1        ; setea status fire1 
          sta            fire1     
          lda            #$03      
          sta            spractive ;activamos el sprite 1 

;         end check joystick          

next
          rts

animatePlayer1Fire.asm

animatePlayer1Fire
          ldx            fire1     
          cpx            #$0       
          bne            @initFire
          rts                      ; vuelve si status fire es 0

@initFire
          ldx            fire1     
          cpx            #$1       
          bne            @loop     
                                   ; init fire (status 1)          
          ldx            spry
          stx            spryFire  

          lda            sprx      
          adc            #12
          sta            sprxFire
          inc            fire1     ; paso a status 2 (disparando)

@loop
          lda            #1        
          adc            sprxFire  ; incremento 3 x disparo
          sta            sprxFire  

          bcs            @setStatus0
          jmp            @next     

@setStatus0          
          ldx            #$0       ; si no seteo flag en 0
          stx            fire1     
          lda            #$01      
          sta            spractive ; desactivamos el sprite 1 

@next          
          rts

Como es un bodrio poner todos estos archivos, a partir de ahora tengo todo subido a github, pueden clonarse el repo, y actualizar a medida que vamos avanzando con el juego:
https://github.com/moonorongo/jp_wars.git
En los próximos posts trataré de centrarme en el bloque de codigo que trabaje, y el resto se lo bajan de github.