La Caverna Informática

Princess Rescue, desarrollo del Juego en Basic para Zx Spectrum

Princess Rescue pantalla de carga

Arrancando el Juego Princess Rescue en Basic

La verdad es que muchas veces no queda claro como se empiezan las cosas, y en este caso tampoco es que lo tenga muy claro, pero siempre hay situaciones que nos empujan en una dirección concreta y en este caso me impulsó a desarrollar el juego Princess Rescue en Basic.

En mi caso, una entrada en el foro de Vade Retro donde se comentaba un concurso de programación de videojuegos en Basic fue suficiente para ilusionarme y meterme en faena.

Si habéis seguido un poco las entradas del Blog o de mi canal de Youtube, habréis visto que para el Next, ya he realizado varios desarrollos en un lenguaje muy parecido al Basic original, pero claro, no había sufrido las peculiaridades de nuestros zx spectrum originales.

Eso sobre todo fue lo que me animó a meterme en vereda y comenzar con el desarrollo de un juego para presentarlo al concurso de ByteManiacos a la categoría de Basic compilado. Ojo, dicha categoría no permite hacer uso de ciertas cosas para intentar hacer un código de Basic puro y que se pueda ejecutar directamente desde un Spectrum sin necesidad de estar compilado. Ya veremos más adelante que es eso de estar compilado.

Evidentemente, dada mi poca experiencia, seguro que hay cosas que se pueden hacer de otra forma, pero yo os voy a contar sobre como las abordé yo.

Pensando una temática

Pues desde luego antes de picar una línea de código, más vale darle vueltas a que tipo de juego vamos a hacer. Hay que tener mucho cuidado en este punto, porque mucha gente deja volar su imaginación y acaba teniendo un proyecto que quizá no sea capaz de acabar nunca. No digo que no haya que ser ambicioso, pero desde luego lo que si hay que ser es realista.

En mi caso, como persona totalmente iniciada en el tema, decidí adoptar una estética de las primeras hornadas de juegos de revistas tipo Microhobby y que la mayoría estaban programados en Basic.

Dejando divagar la mente para encontrar una temática para el Juego Princess Rescue

La temática, pues algo clásico, un caballero tiene que rescatar a una princesa presa en la torre del castillo. Ya con eso teníamos una primera idea de como seguir avanzando. Evidentemente faltaban por definir cosas como el tipo de movimiento, enemigos, saltos, escaleras, pantallas, objetos…

Una cosa que si tenía muy clara, es que quería que el juego fuera mas fluido que aquellos juegos de antaño, que si bien podían estar muy bien diseñados, muchos adolecían de velocidad debido a las limitaciones en este sentido del Basic.

Compilador Zx Boriel al rescate

Como ya os había comentado, mi objetico era doble. No solo quería recrear el aire viejuno de aquellos juegos de la primera era del Spectrum, sino que quería que fuera muy fluido, casi como si fuera una consola.

Para ello, y además así de camino aprendía otra cosa, como el concurso daba la opción, me decanté por el uso del maravilloso compilador de Boriel.

Para los que no lo sepan, un compilador, no solo existe el de Boriel, se llevan usando desde hace mucho tiempo, lo que hace a groso modo es convertir nuestras sentencias de Basic en código máquina, muchísimo mas rápido en su ejecución.

De esta forma, nos permite programar tranquilamente desde nuestros comandos de Basic, y tras pasarle el fichero al compilador, obtenemos la traducción de nuestras sentencias en otro nuevo pero ya traducido al código máquina y listo para ser ejecutado desde nuestro Spectrum.

Cosas básicas para el desarrollo del Juego Princess Rescue en Basic

Bien, pues ya que hemos dejado claro tanto el tipo de juego que queremos conseguir como las herramientas con las que vamos a contar, es necesario dejar claro las cosas básicas que vamos a tener que hacer.

Definir gráficos de usuario o UGD’s

Bueno pues si queremos hacer una cosa medianamente digna, no podemos usar los caracteres gráficos que vienen predefinidos en el Spectrum. Ojo, que muchos juegos de la época usaban solo estos caracteres y algún que otro juego era resultón y todo, ya sabéis lo que importa es la idea. Pero bueno, lo suyo es crear uno sus propios gráficos, para ello podemos definir hasta 21 caracteres de usuario. Es decir, disponemos de 21 matrices de 8×8 que es el tamaño de un carácter en Spectrum, en donde poder pintar nuestros grafiquitos.

Diseños iniciales de algunos UGS’s del Juego Princess Rescue

Para ello tendremos que cargar en memoria nuestro diseños, que como podéis ver se pueden realizar sin ninguna herramienta.

9000  REM ******GRAFICOS UGD*********
9002  FOR f = 1 TO 14*9
9003  READ b
9004  POKE USR "a" + f - 1 , b
9005  NEXT f
9007  RETURN
9008  ugd:
9010  DATA 12,12,2,60,60,24,20,20:REM A CORRER DCHA 144
9020  DATA 24,24,66,60,60,24,24,60:REM B: PARADO 1 145
9030  DATA 24,90,0,60,60,24,24,60:REM C SALTO 146
9040  DATA 48,48,64,60,60,24,40,40:REM D CORRER IZQ 147
9050  DATA 0,68,124,84,254,56,40,0:REM E ENEMIGO 148
9060  DATA 28,28,68,24,60,126,24,60:REM F PRINCESA(P) 149 IZQ
9071  DATA 189,189,0,247,247,0,189,189:REM G LADRILLO 150
9080  DATA 66,66,126,66,66,66,126,66:REM H ESCALERA 151
9090  DATA 60,66,153,189,189,153,153,153:REM I LAPIDA 152
9100  DATA 126,165,255,165,255,165,255,165:REM J PUERTA 153
9110  DATA 0,0,96,159,149,101,0,0:REM K LLAVE 154
9120  DATA 247,247,128,189,189,128,247,247:REM L Suelo IZQUIERDA 155
9130  DATA 239,239,1,189,189,1,239,239:REM M Suelo Derecho 156
9140  DATA 255,195,165,153,153,165,195,255:REM N Muro exterior 157
9145  DATA 56,56,34,24,60,126,24,60::REM F PRINCESA(P) 158 DCH

Aquí básicamente convertimos de binario a decimal cada una de las líneas de nuestro UGD, las almacenamos en una estructura DATA para poder ir leyendo antes de hacer el POKE para escribir directamente los datos en la memoria donde nuestro Spectrum almacena los UGS’s.

Buenos casi se me escapa, para convertir de binario a decimal tendremos que ir multiplicando por la potencia de dos dependiendo de las casillas que hayamos marcado, es decir.

2^7=1282^6=642^5=322^4=162^3=82^2=42^1=22^0=1
00001100
Primera línea del UGD del carácter A. Correr a la derecha. Nos daría como resultado 12

Dentro del bucle, lo que hacemos con el POKE, es situarnos en la dirección del primer carácter de usuario, meterle los datos, y en la siguiente iteración saltar a donde empieza el siguiente carácter, en este caso «b» y así sucesivamente.

Lo del «f-1» es simplemente para forzar a empezar en la «a» ya que nuestro querido Speccy no permite bucles que empiecen en cero. ¡Así es la vida!

Imprimir en pantalla nuestros preciosos gráficos

¡Ya tenemos nuestros super gráficos en memoria!

¿Bueno y ahora como los uso?

Veamos el siguiente código.

47 PRINT AT 16,7;"RESCUE THE PRICESS ":PRINT AT 16,26;CHR$ 158

Esta línea forma parte del menú de inicio del juego, y lo que hacemos mediante el comando «PRINT AT» es poder posicionar una cadena en la posición que queremos dentro de la pantalla de nuestro Spectrum.

Antes de seguir, tendremos que ver como se configura nuestra pantalla, porque sino poco vamos a entender.

La pantalla del Spectrum es capaz de representar 256×192 píxeles en pantalla, que si tenemos en cuenta que cada carácter se compone de 8×8 píxeles, tenemos que para nuestro Basic, disponemos de una pantalla de 32 columnas y 24 filas. Siendo la posición (0,0) la esquina superior izquierda nuestro punto de referencia.

(0,0)(31,0)
(0,1)
(0,2)
(0,3)
(0,4)
…..
(0,16)RESCUETHEPRINCESS
(0,17)
(0,18)
(0,19)
(0,21) (31,21)
(0,22)(31,22)
(0,23)(31,23)
Eje X 0->31 Eje Y 0->21

Con esto ya podemos ser capaces de ubicar correctamente texto y gráficos.

Realmente las últimas dos filas a priori no son accesibles directamente por el Basic sin hacer alguna triquiñuela, vamos básicamente necesitamos incluir el siguiente POKE.

POKE 23659,0

Con eso le decimos al Spectrum que no reserve las dos últimas líneas para mostrar mensajes de error, etc…

El caso, que volviendo a retomar lo que estábamos hablando, para poder imprimir nuestro gráficos UGD, hacemos uso de nuestro PRINT AT y,x , pero cuando le indicamos que carácter tiene que imprimir usamos CHR$ 158, con esto referenciamos directamente mediante los códigos de control que tiene asignados el Spectrum al carácter UGD que queramos.

Nuestros UGD’s en decimal van desde el 146 (90 en Hexadecimal) hasta el 164 (A4 en Hexadecimal)

Tenéis mas información de todos los códigos de control en la Wikipedia.

Una vez que tenemos esto ya podemos montar nuestra pantalla del menú de nuestro juego sin problemas.

20 REM ****************MENU*****************
27 PAPER 1:BORDER 1
30 CLS:INK 0:PRINT AT 0,4; CHR$ 145:PRINT AT 0,6;" PRINCESS     RESCUE ":PRINT AT 0,28;CHR$ 149
40 PRINT AT 2,5;FLASH 1; "PRESS  ANY KEY TO START"
41 PRINT AT 4,13;FLASH 1;"CONTROLS"
42 PRINT AT 6,11;BRIGHT 1;"Q":PRINT AT 6,13;" LADDER UP  "
43 PRINT AT 8,10;BRIGHT 1;"O":PRINT AT 8,11;" LEFT ":PRINT AT 8,17;BRIGHT 1;"P":PRINT AT 8,18;" RIGHT"
44 PRINT AT 10,10;BRIGHT 1;"M + O":PRINT AT 10,15;" OR ":PRINT AT 10,19;BRIGHT 1;"M + P"
45 PRINT AT 12,6;"JUMP IN THAT DIRECTION"
46 PRINT AT 14,11;FLASH 1;"INSTRUCTIONS"
47 PRINT AT 16,7;"RESCUE THE PRICESS ":PRINT AT 16,26;CHR$ 158
48 PRINT AT 18,9;"USING THE KEYS ":PRINT AT 18,25;CHR$ 154
49 PRINT AT 20,8;"TO OPEN THE DOOR ":PRINT AT 20,25;CHR$ 153
50 PRINT AT 21,5;"BEWARE OF THE GUARDIANS!"

Y aquí podemos ver el resultado.

Pantalla del Menú del Juego Princess Rescue

Como podéis apreciar no es que dé muchas opciones más que comenzar a jugar, jajaja, pero vale para poner en contexto al jugador con unas simples instrucciones de teclas y objetivos, y ya de camino usar nuestros gráficos personalizados.

Entrada desde teclado

Muy bien, pues ya hemos podido diseñar nuestros gráficos e imprimirlos en la zona de pantalla que queremos.

¿Y ahora que? ¿Pues habrá que interactuar con el usuario, no?

Bien para eso vamos a hacer uso de nuestro teclado, de forma que podamos saber que teclas está pulsando el jugador para realizar en ese momento las operaciones que necesitemos.

Volvamos con el menú que hemos visto anteriormente.

En este caso, hasta que el usuario no pulse alguna tecla no iniciaremos el juego.

Vamos a ver como leer una pulsación cualquiera.

54 IF INKEY$<>"" THEN RESTORE 9010:GOTO 70

Si ignoramos lo del RESTORE, realmente lo que nos interesa es el INKEY$<>»» y luego el GOTO 70.

En este caso le estamos diciendo que si el valor de INKEY$ es distinto de vacío, es decir hemos pulsado algo, salte a la línea 70, que es donde se inicializa el juego y comienza la partida.

Evidentemente podríamos hacer lo siguiente para leer cuando se pulsa la tecla «a»

IF INKEY$="a" THEN PRINT AT 2,2;CHR$152

De esta forma, imprimiríamos el UGD de la lápida en la posición 2,2.

Con esto pensarás que ya lo tienes….. ¡Pues no!

Hay un problema con esta forma de leer caracteres y es que solo se lee la pulsación de una tecla a la vez. Y ya con esto no nos vale para cualquier juego medio decente, ya que es bastante normal que usemos combinaciones de varias teclas para realizar acciones, como pueden ser saltar, disparar, etc…

Para poder detectar múltiples pulsaciones de teclas tendremos que leer directamente de uno de los puertos entrada/salida de los que dispone el Spectrum y que nos va a permitir leer directamente de la matriz del teclado ya que no deja de ser un periférico.

Pero… ¿Qué es eso de la matriz del teclado?

Nuestro querido ordenador, realiza la lectura de teclas a bajo nivel mediante la codificación de señales mediante una organización en semifilas.

Veamos las semifilas de las que consta nuestro teclado.

IN 65278 lee la semifila CAPS SHIFT a V
IN 65022 lee la semifila A a G
IN 64510 lee la semifila Q a T
IN 63486 lee la semifila 1 a 5
IN 61438 lee la semifila 0 a 6
IN 57342 lee la semifila P a Y
IN 49150 lee la semifila ENTER a H
IN 32766 lee la semifila SPACE a B

Si vemos una foto de un Spectrum 48K lo entenderemos mejor.

Membrana del teclado de un Zx Spectrum 48K

Entonces por ejemplo, para saber que está pasando con la pulsación de nuestra tecla «o» tendríamos que leer la semifila 57342.

Pero claro, ¿Y que valor nos tiene que devolver para saber que no se ha pulsado la «p», por ejemplo?

Pues esto se hace leyendo los bits que nos devuelve dicho puerto.

Para liarlo un poco mas, se organizan de esta forma:

X X X 4 3 2 1 0

– – – Y U I O P

El Bit 0 es el mas externo de la semifila, y las teclas pulsadas aparecerán como 0 y las no pulsadas como 1.

Así que en el caso de la «o» tendríamos XXX11101 en binario.

Y me diréis, ¿Y esas XXX? Pufff, pues resulta que algunos modelos de Spectrum no devolvían los mismos valores, así que menos en los modelos 48K issue 2 que devuelven 111, en el resto es 101. ¡Jur,jur,jur!

Podemos usar la calculadora para realizar las conversiones de binario a decimal

Bueno yo lo siento por los propietarios de estos modelos, pero si nos quedamos con el resto, tendríamos para la «o» 10111101, que pasado a decimal teniendo en cuenta que nuestro bit menos significativo está a la derecha, tendríamos 189.

Pues con esto ya podemos leer múltiples teclas como podéis ver en esta parte del código:

2010 REM ***CONTROL DE MOVIMIENTO JUGADOR****
2020 REM Tecla O
2030 IF (IN 57342=189 AND IN 32766<>187 AND POSX>1 AND FALLING=0 AND JUMPINGL=0) THEN IF ATTR (POSY,POSX-1)<>14 AND ATTR (POSY,POSX-1)<>10 THEN PRINT AT POSY,POSX;" ":LET POSX=POSX-1:PRINT AT POSY,POSX;CHR$ 147:GOSUB 6000:PRINT AT POSY,POSX;CHR$ 145:BEEP 0.02,2:GOTO 2064:REM SALTAMOS LA Q Y LA O PARA QUE NO LAS TENGA ENCUENTA AL LADO DE LAS ESCALERAS

En este caso revisamos la pulsación de la «o» la «m». Si os fijáis para la «m» leemos de la semifila 32766.

Animaciones de nuestros Sprites

Bueno, aunque con lo de imprimir nuestros caracteres en pantalla ya tenemos un gran avance. Lo suyo es de dotar de vida a nuestros Sprites.

Si, sé que hasta el momento no hemos hablado nada de Sprites, y no os asustéis para nosotros son los caracteres UGD, pero bueno, por meter un poco de nomenclatura más general.

Realmente los Sprites, viene a colación del tema que tratamos de la animación, ya que no son mas que una secuencia de imágenes que representan a nuestro jugador, enemigo, etc.. en las distintas posiciones que podrá adoptar durante el desarrollo de nuestro juego.

Sprites de animación de coronel de la Wehrmacht

Por ejemplo, una animación muy básica que podríamos realizar, es que cuando nuestro jugador se mueva hacia la derecha la cabeza del Sprite tenga la cabeza mirando en esa dirección y al contrario cuando nos movamos en la otra dirección.

En nuestro caso en particular, nuestro personaje es capaz de moverse en ambas direcciones, saltar y caer. Así si os fijáis en los UGD que definimos al principio veréis que existe uno para cada situación.

Pero hay algo evidente y es que para generar esa sensación de animación es necesario ir borrando de la pantalla o sustituyendo los gráficos. De esta forma, dependiendo de la velocidad con la que realicemos las acciones de borrado o sustitución daremos la impresión de una forma más o menos convincente esa sensación de animación.

4000 REM ******FALLING - GESTIONAMOS LA CAIDA DEL JUGADOR******
4015 IF ATTR (POSY+1,POSX)<>14 AND ATTR (POSY+1,POSX)<>15 AND POSY<27 AND JUMPINGL=0 AND JUMPINGR=0 AND LADDERV=0 THEN LET FALLING=1:PRINT AT POSY,POSX;" ":LET POSY=POSY+1:PRINT AT POSY,POSX;CHR$ 146:GOSUB 6000:PRINT AT POSY,POSX;CHR$ 145:BEEP 0.02,2

Quitando el tema del comando ATTR que ya veremos mas adelante, aquí podemos ver como gestionamos la caída del jugador desde alguna de las plataformas hasta el suelo, o en la fase final de un salto.

Si nos saltamos todas las variables de control que tenemos para saber cuando realizar dicha acción, vemos que primero realizamos un borrado de la posición actual, incrementamos la posición en el eje Y (Eso es bajar en nuestro eje de coordenadas) y luego imprimimos el UGD apropiado.



PRINT AT POSY,POSX;" ":LET POSY=POSY+1:PRINT AT POSY,POSX;CHR$ 146

Con estas nociones ya podéis hacer lo que os venga en gana a nivel de animaciones.

¡La teoría es sencilla, pero luego….. Jajajaja ¡

¡Os faltará espacio en memoria o ciclos de reloj para hacer todo lo que queréis!

Siempre tener en cuenta las limitaciones de la máquina para la que estáis diseñando el juego. Puede ser muy chulo hacer un personaje con 20 Sprites para su movimiento, pero si luego al presentarlo en pantalla va mas lento que el caballo del malo, quizá no sea tan buena idea.

Gestión de Colisiones

Bueno, bueno, ya parece que esto va tomando forma. Pero ahora que podemos presentar y mover nuestros gráficos posteriormente a que el jugador presione una o varias teclas, necesitamos dotar de alguna lógica a nuestro juego y eso pasa por detectar que está pasando en la pantalla del mismo.

Para eso es necesario gestionar las colisiones entre los distintos elementos que integran el Juego Princess Rescue.

Hay cosas que no es necesario gestionar con colisiones, como por ejemplo que el jugador salga fuera de la pantalla del juego, ya que conocemos de antemano las dimensiones del mismo y simplemente le podríamos indicar en la condición de movimiento que si la X es 0 no permita que se mueva.

Pero… ¡Ay amigo! ¿Y que hacemos con los enemigos que se están moviendo o con cada una de las plataformas que van cambiando en cada nivel?

Pues para eso necesitamos poder leer de la pantalla y decidir en base a lo que nos encontremos las distintas acciones.

Para ello el Basic nos presenta dos comandos:

SCREEN y ATTR

Veamos cada uno de ellos.

SCREEN

SCREEN nos permite recuperar el carácter impreso en la pantalla dadas unas coordenadas. Si en principio podría ser buena idea, ya que si lo pensáis si los enemigos los representamos con nuestro UGD de la letra «e» cuando nuestra posición sea la misma sabremos que el enemigo ha tocado al jugador.

Hasta aquí todo perfecto, ahora vamos a hablar de las limitaciones.

El Spectrum representa los caracteres en 8 bytes para conformar lo que es la forma y 1 byte para el tema de colores, brillo, etc.

Que es lo que pasa, que comprobar constantemente con SCREEN gasta muuuuchos recursos de nuestro querido amigo con su CPU corriendo a 3,5 MHz porque tiene que traerse toda esa información cada vez que hacemos la comparación.

¿Cómo lo podemos hacer entonces de una forma más eficiente?

ATTR

Bueno, como sois muy listos, ya os podíais imaginar que los tiros iban por aquí. Este comando nos devuelve, ahora veremos en que formato, solo ese Byte de atributos dada una posición.

Y diréis, bueno pero ¿Con eso como puedo saber si es un enemigo u otra cosa?

Pues aquí está el truco, si asignáis atributos unívocos al tipo de enemigo, muro, plataforma o lo que os de la gana, ya seréis capaces de identificarlos.

El formato que devuelve ATTR es el siguiente según que atributos tengamos definidos en nuestro Sprite.

3500 REM ***RESUMEN DE CALCULO DE COLISIONES CON ATTR*** 
3510 REM 128 is flashing, 0 is steady + 64 is bright, 0 is normal +8*paper +colour the code for the ink colour
7000 REM ****LADDERS DETECTION*****
7010 IF ATTR (POSY,POSX+1)=15 THEN LET LADDERX=POSX+1:LET LADDERY=POSY:LET LADDERH=1:GOSUB 6000

Como veis en el código, dependiendo del papel, el color, si tiene brillo o está parpadeando nos devolverá un valor u otro.

Si tenemos claro los atributos definidos, podemos ver como en este caso detectamos cuando el jugador está en una escalera para permitir que pueda subir, borrar y volver a pintar la misma cuando nos movemos.

Ojo, todo esto que estamos hablando de ATTR, evidentemente si tenemos variables o matrices que controlan las posiciones de nuestros sprites, también los podemos usar para detectar las colisiones y seguro que aún menor coste computacional, pero no podemos tener posicionados con variables todos los elementos de nuestro juego, ¿Verdad?

Lógica del Juego Princess Rescue en Basic

Si habéis llegado hasta aquí es que sois unos valientes y tenéis ganas de verdad de hacer cosas divertidas, jajaja.

Bueno, pues ya que contamos con todas las herramientas para poder llevar a cabo nuestra visión de ese juego que nunca nadie ha imaginado y que nosotros queremos llevar a cabo, ahora toca hablar de como poner un poco de orden a estas cosas.

Pantalla del Juego Princess Rescue en Basic ya en el Bucle Principal

Normalmente, un juego básico siempre, siempre, consta de un bucle de juego principal en donde mientras que no acabemos la partida no podremos salirnos.

En este bucle tendremos que ser capaces de realizar todas las acciones que hemos ido desgranando anteriormente. Es decir, entrada de teclas, movimiento de player, movimiento de enemigos, control de colisiones, puntos, vidas, etc…

Hay que tener también especial atención a que ciertas cosas tengan preferencia sobre otras. En mi caso era crucial que cuando encadenaba las animaciones del player cuando saltaba, los enemigos no podía esperar hasta que esto finalizase, por lo que era necesario llamar a la rutina de movimiento de enemigos en medio del salto del jugador.

4000 REM ******FALLING - GESTIONAMOS LA CAIDA DEL JUGADOR******
4015 IF ATTR (POSY+1,POSX)<>14 AND ATTR (POSY+1,POSX)<>15 AND POSY<27 AND JUMPINGL=0 AND JUMPINGR=0 AND LADDERV=0 THEN LET FALLING=1:PRINT AT POSY,POSX;" ":LET POSY=POSY+1:PRINT AT POSY,POSX;CHR$ 146:GOSUB 6000:PRINT AT POSY,POSX;CHR$ 145:BEEP 0.02,2

Ese GOSUB 6000 es el que está todo el rato gestionando los movimientos de los enemigos, y si miráis en el código está repartido por todos los lados.

También hay que definir unas variables iniciales antes de dicho bucle, como podrían ser las vidas iniciales, etc..

Y por su puesto, las variables que usaremos dentro del bucle principal del juego donde controlaremos todo lo que necesitemos. Posición de enemigos, jugador, estados de los mismos, si el jugador ha muerto, etc…

70 REM ************INICIALIZACION************
71 LET LEVEL=1:LET VIDA=5
100 REM ***********VARIABLES DE LOOP DE JUEGO*********
110 LET POSX=0:LET POSY=0:LET POSXI=0:LET POSYI=0:LET KEYS=0:LET DOORX=0:LET DOORY=0:LET PRINX=0:LET PRINY=0:LET X=0:LET Y=0:LET FALLING=0:LET JUMPINGL=0:LET JUMPINGR=0:LET NENE=0:LET S$="":LET LADDERX=0:LET LADDERY=0:LET LADDERH=0:LET LADDERV=0:LET EX=0:LET EY=0:LET ED=0
120 DIM E(7,3):REM FIJAMOS EL ARRAY DE ENEMIGOS
130 DIM K(3,2):REM FIJAMOS ARRAY DE LLAVES
1100 REM **********INICIALIZACION COLORES************
1105 CLS:PAPER 1:BORDER 1:INK 0

Arriba tenéis un ejemplo de inicialización de variables que deben de estar fijadas antes del inicio del bucle principal del Juego Princess Rescue.

Por traeros un poco de luz ante este galimatías de variables, aquí controlamos, la posición del player, la puerta, si estamos cayendo, si estamos saltando, el número de llaves que tiene el player en un nivel dado, etc… ¡Ya casi ni me acuerdo!

2000 REM *****INICIO DE BUCLE DE JUEGO*****
2003 IF VIDA=0 THEN GOTO 7500
2005 GOSUB 7000:REM **LADDERS DETECTION*
2010 REM ***CONTROL DE MOVIMIENTO JUGADOR****

Y aquí ya un ejemplo del inicio del bucle principal del juego. Como podéis ver la primera condición es que si el jugador se ha quedado sin vidas nos vamos a línea 7500 donde gestionamos el fin de partida.

Y para que no os quedéis con las ganas.

7500 REM *************GAME OVER**************
7510 CLS:PAPER 1:INK 0
7520 PRINT AT 11,12;"GAME OVER":FOR i=15 TO 1 STEP -1:BEEP 0.2,i:NEXT i:PAUSE 0:GOTO 10

Como veis en la gestión del Game Over, nos vamos al principio del programa para que todo empiece de nuevo.

Mantener el Orden y comentarios

Pues puede parecer una cosa obvia, pero ya sabemos… y que conste que el mío tampoco es que sea una ejemplo perfecto de orden y comentarios. Si es verdad que hay que tener en cuenta, que todo ocupa espacio, aunque cuando compilemos no debe, si vamos a hacer Basic puro sin compilación, podremos pasar algún programa como el ZxBasicus, que nos genera un listado optimizado de nuestro código original, eliminando comentarios, renombrado los nombres de las variables a la mínima expresión, etc…

También es útil sacar fuera del bucle principal, todas las rutinas que podamos para ir desarrollando por bloques.

Podéis ver un ejemplo mas abajo.

5000 GOTO 2000
5005 REM *****FIN DE BUCLE DE JUEGO*****
5100 REM ****RUTINAS DEL JUEGO******
6000 REM ****CONTROL MOVIMIENTO ENEMIGOS*****(I,3)=1 RIGHT (I,3)=2 LEFT *** 
6005 REM REVISAMOS TAMBIEN POR SI COGE LA LLAVE CAYENDO O SALTANDO YA QUE SIEMPRE QUE NOS MOVEMOS LLAMAMOS AL MOVIMIENTO DE LOS ENEMIGOS
6010 FOR i=1 TO 3
6015 IF POSX=K(i,2) AND POSY=K(i,1) THEN PRINT AT 0,14+((3-KEYS)*2);CHR$ 154:BEEP 0.2,0.2:LET K(i,1)=0:LET K(i,2)=0:LET KEYS=KEYS-1
6021 NEXT i
6100 FOR i=1 TO NENE

Se puede conseguir bastante mejoría con este tipo de programas, pero durante el desarrollo, más vale indicar todo lo que podamos. Puede ser que no retomemos el código hasta pasado un tiempo y para entonces ya no nos acordemos de nada.

¡El que avisa no es traidor!

Diseñando niveles, una visión especial

Pues en este punto desde luego casi seguro que nadie lo hace así, lo más normal, sería meter todo en una estructura de tipo DATA para luego recuperar los valores con READ, pero yo quería poder retocar el diseño de mis niveles desde el propio código así que no se me ocurrió otra cosa que esto.

Como quien dice me monté mi propio editor de niveles a bajo nivel, 😉

Abajo código del nivel 1 del Juego Princess Rescue:

7999 REM ************DISENO DE NIVELES******************
8000  PRINT AT 1,1;INK 6; CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157
8001  PRINT AT 2,1;INK 1; "N                         J IN"
8002  PRINT AT 3,1;INK 1; "N  K           E        LGGGMN"
8003  PRINT AT 4,1;INK 1; "NLGGM      LGGGGGGGM         N"
8004  PRINT AT 5,1;INK 1; "N                  LGGM      N"
8005  PRINT AT 6,1;INK 1; "N     LGM            H       N"
8006  PRINT AT 7,1;INK 1; "N         E          H    LGMN"
8007  PRINT AT 8,1;INK 1; "N        LGGGGGM     H       N"
8008  PRINT AT 9,1;INK 1; "N           H        H       N"
8009  PRINT AT 10,1;INK 1;"N           H        H       N"
8010  PRINT AT 11,1;INK 1;"N           H     LGGGM      N"
8011  PRINT AT 12,1;INK 1;"N           H  LGGM          N"
8012  PRINT AT 13,1;INK 1;"N           H             G  N"
8013  PRINT AT 14,1;INK 1;"N           H                N"
8014  PRINT AT 15,1;INK 1;"N        LGGGM         LGGGGMN"
8015  PRINT AT 16,1;INK 1;"N                            N"
8016  PRINT AT 17,1;INK 1;"N    B      E                N"
8017  PRINT AT 18,1;INK 1;"N  LGGGGM  LGM               N"
8018  PRINT AT 19,1;INK 1;"N                         LGMN"
8019  PRINT AT 20,1;INK 1;"N         LM                 N"
8020  PRINT AT 21,1;INK 1;"N    LM      E       LGM     N"
8021  PRINT AT 22,1;INK 6;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157;CHR$ 157
8022  RETURN

Como podéis ver tengo una representación de la pantalla, más o menos, y lo que hago es imprimir en pantalla pero con el mismo color que el fondo todos estos caracteres para que el usuario no se percate de la triquiñuela.

Casa uno de ellos representa el código UGC que quiero que después se sustituya, para ello una vez tengo la pantalla parseo los valores con el siguiente código, si os fijáis aquí si usamos SCREEN :

1143 PRINT AT 0,8;FLASH 1;"GENERATING LEVEL ":PRINT AT 0,25;FLASH 1;LEVEL
1144 FOR Y=2 TO 21
1145 PRINT AT Y,1;INK 6;CHR$ 157:PRINT AT Y,30;INK 6;CHR$ 157:BEEP 0.02,2:REM IMPRIMIMOS LOS BORDES DIRECTAMENTE SIN COMPARAR PARA AHORRAR COMPUTO
1146 FOR X=2 TO 29
1147 LET S$=SCREEN$(Y,X)
1148 IF S$="G" THEN PRINT AT Y,X;INK 6;CHR$ 150
1149 IF S$="E" THEN LET NENE=NENE+1:PRINT AT Y,X;INK 3;CHR$ 148:LET E(NENE,1)=Y:LET E(NENE,2)=X:LET E(NENE,3)=2
1150 IF S$="I" THEN PRINT AT Y,X;INK 5;CHR$ 149:LET PRINX=X:LET PRINY=Y
1151 IF S$="H" THEN PRINT AT Y,X;INK 7;CHR$ 151
1152 IF S$="B" THEN LET POSX=X:LET POSY=Y:PRINT AT Y,X;INK 0;CHR$ 145:LET POSXI=X:LET POSYI=Y
1153 IF S$="J" THEN PRINT AT Y,X;INK 2;CHR$ 153:LET DOORX=X:LET DOORY=Y
1154 IF S$="K" THEN LET KEYS=KEYS+1:PRINT AT Y,X;INK 4;CHR$ 154:LET K(KEYS,1)=Y:LET K(KEYS,2)=X
1155 IF S$="L" THEN PRINT AT Y,X;INK 6;CHR$ 155
1156 IF S$="M" THEN PRINT AT Y,X;INK 6;CHR$ 156
1157 IF S$="N" THEN PRINT AT Y,X;INK 6;CHR$ 157
1158 IF S$="D" THEN PRINT AT Y,X;INK 5;CHR$ 158:LET PRINX=X:LET PRINY=Y
1163 NEXT X
1164 NEXT Y

La Música y el sonido

Bueno pues una de las cosas que abordé al final del todo fue el tema de poner una melodía al juego Princess Rescue. Para ello solo disponemos del comando BEEP, que ya había utilizado para dotar de ciertos sonidos, por su puesto también muy de la época, a ciertas acciones del personaje.

2064 REM Tecla P y M
2065 IF (IN 57342=190 AND IN 32766=187 AND ATTR(POSY+1,POSX)=14 AND ATTR(POSY-1,POSX)<>15 AND FALLING=0 AND JUMPINGL=0) THEN LET JUMPINGR=1:IF ATTR (POSY-1,POSX+1)<>14 AND ATTR (POSY-1,POSX+1)<>15 THEN PRINT AT POSY,POSX;" ":LET POSY=POSY-1:LET POSX=POSX+1:PRINT AT POSY,POSX;CHR$ 144:GOSUB 6000:PRINT AT POSY,POSX;" ":PRINT AT POSY,POSX;CHR$ 145:GOSUB 6000:IF ATTR (POSY-1,POSX+1)<>14 AND ATTR (POSY-1,POSX+1)<>15 THEN PRINT AT POSY,POSX;" ":LET POSY=POSY-1:LET POSX=POSX+1:PRINT AT POSY,POSX;CHR$ 144:GOSUB 6000:PRINT AT POSY,POSX;" ":PRINT AT POSY,POSX;CHR$ 145:GOSUB 6000:IF ATTR (POSY-1,POSX+1)<>14 AND ATTR (POSY-1,POSX+1)<>15 THEN PRINT AT POSY,POSX;" ":LET POSY=POSY-1:LET POSX=POSX+1:PRINT AT POSY,POSX;CHR$ 144:GOSUB 6000:PRINT AT POSY,POSX;" ":PRINT AT POSY,POSX;CHR$ 145:GOSUB 6000:BEEP 0.02,2:GOTO 2068

Si, al final del todo podéis ver el comando BEEP duración, frecuencia.

Para los efectos no hay problema, otra cosa es cuando queremos transcribir una partitura, pero bueno vamos a intentar dar un poco de luz al tema.

Valores de Pitch con respecto a Notas y escalas

Pues realmente con la imagen de arriba ya podemos empezar a trabajar, el problema que tenemos es para el tema de los tiempos, ya que aunque podremos hacer una equivalencia para el tema de la duración de las notas según sean blancas, negras, corches, semicorcheas, etc… no nos va a valer del todo.

Anotaciones para convertir la partitura al comando BEEP

Si podéis verlo, en la esquina superior izquierda, hice anotaciones sobre el tiempo de duración que debería de tener el Beep para coincidir con las distintas notas, el problema es que cuando escuchas la melodía ves que el tema de notas sostenidas y enlazadas con otras, no las puedes representar así de fácil, por lo que no queda otra que hacerlo todo siguiendo la pauta de arriba y luego ir retocando a mano para intentar aproximarse lo máximo posible a la pieza original.

Otro problema, esta claro, es que solo disponemos de 1 voz, y como veis en la partitura, tenemos 4 instrumentos. Menos mal que en mi caso uno de ellos lleva la melodía principal y no se nota tanto, pero en otros casos puede ser bastante complicado. Intenté meter en los tiempo muertos un segundo instrumento, pero se recargaba en demasía el conjunto y desde mi punto de vista, no quedaba bien.

Para el tema de almacenar los datos de la canción, opté por usar un DATA almacenando la duración y la nota a pares. De esta forma, me era más sencillo ir siguiendo la partitura.

55 READ a,b:
56 BEEP a,b
57 IF n=69 OR n=84 OR n=99 OR n=114 THEN PAUSE 20:REM SILENCIOS
58 NEXT n
59 REM MUSICA "BAR" COMPUESTA POR HECTOR MARTINEZ ECHEVESTE
60 DATA .30,9,.20,12,.15,11,.15,9,.15,7,.5,9,.5,4,.30,9,.20,12,.15,11,.15,9,.15,7,1,9:REM TOTAL NOTAS 13
61 DATA .30,9,.20,12,.15,11,.15,9,.15,7,.5,9,.5,4,.15,5,.15,9,.15,11,.15,12,.15,11,.15,7,1,9:REM TOTAL NOTAS 14

Conclusiones finales del Juego Princess Rescue

Bueno si habéis llegado hasta aquí espero haber sido de ayuda por lo menos en los conceptos generales.

Como siempre, tenéis todo el código disponible del Juego Princess Rescue en Basic en mi Github, para descargarlo y poder analizarlo en conjunto o jugarlo simplemente, 😉

Como habréis podido ver, doy muchas cosas por supuestas dentro del Lenguaje Basic, no era mi intención hacer un tutorial completo, tan solo aquellos aspectos relacionados con la realización de un videojuego.

Si veis que os faltan nociones, os recomiendo que visitéis el curso de Basic AsteroideZX es un crack.

También hay un libro que me gustaría recomendar, y que conste que no me saco un duro, que es el «Club de programación de juegos de Zx Spectrum» de Gary Plowman. Me lo regalaron hace poco y para empezar está bien, aunque dependiendo del nivel que tengáis quizá se os pueda quedar corto rápidamente.

Pues nada, espero que os haya resultado una lectura amena y os haya podido dar alguna idea para vuestro próximo juego.

¡Venga a por ello, nos vemos en la próxima entrada!

Si ya os ha picado la curiosidad y queréis saber como es el Basic del Zx Spectrum Next lo podéis consultar en esta entrada.

Salir de la versión móvil