Windgate's BennuGD Tutorial
Windgate's BennuGD Tutorial
Windows
La forma ms sencilla de ejecutar un videojuego programado con Bennu en Windows es a partir de su fichero de cdigo con extensin .prg Con el Bennupack instalado, los ficheros .prg se abren automticamente con Notepad++, el entorno de programacin que adems usaremos para programar nuestros videojuegos. Con el fichero .prg, abierto en Notepad++ pulsamos la tecla F6, esto abrir una ventana del sistema en la se realizar una verificacin de que el cdigo es correcto y no tiene errores, esta tarea se denomina compilacin. Si la compilacin es correcta, se generar un fichero .dcb y se ejecutar el videojuego, en caso contrario tendremos que solucionar el problema de compilacin modificando el cdigo del videojuego. Todos los videojuegos de ejemplo asociados a cada tema del tutorial compilarn y se ejecutarn sin problemas.
Linux
Para ejecutar un videojuego programado con Bennu en Linux tendremos que abrir una terminal. Con el paquete de Bennu para Linux instalado, en la terminal introduciremos en primer lugar el comando bgdc seguido de la ruta del fichero .prg que queremos compilar. Si la compilacin es correcta, se generar un fichero .dcb, y deberemos introducir el comando bgdi seguido de la ruta del fichero .dcb generado para ejecutar el videojuego.
Tanto en Linux como en Windows no habr problemas de compilacin para ejecutar los videojuegos de ejemplo. Ser cuando editemos nuestro propio cdigo o modifiquemos el de los videojuegos existentes cuando empezaremos a encontrarnos con los primeros errores de compilacin. A lo largo de este tutorial aprenderemos a utilizar el lenguaje de programacin BennuGD y solucionar los problemas de compilacin que pronto nos encontraremos durante nuestra experiencia como programadores. Introduccin Ejecutar los videojuegos Pg. 1
1. Grficos I Si vamos a programar un videojuego, lo primero que vamos a necesitar son los grficos. Podramos empezar con simples dibujos hechos a mano con Paint o cualquier otro programa bsico de dibujo, pero ya que vamos a dedicar varias horas de trabajo a nuestro videojuego, es mejor que seleccionemos unos grficos atractivos, que pueden ser por ejemplo nuestros personajes favoritos o los escenarios de aqul videojuego con el que pasamos cientos de horas jugando aos atrs. En primer lugar crea una carpeta personal donde guardar tu trabajo, vamos a aprender a seleccionar grficos. 1.1 Sprites Un spriteset es una tabla de grficos con todas las animaciones de un determinado personaje: Tendrs que elegir varios spritesets. Puedes encontrar una amplia coleccin en internet, ya sea por ejemplo en webs como http://spritedatabase.net o simplemente buscando en Google Imgenes con las palabras claves sprite y ripped, esta ltima palabra significa extrado de un videojuego. Tambin puedes buscar en el DVD de recursos de la actividad, en la seccin Biblioteca GFX. Al seleccionar nuestros spritesets debemos tener en cuenta: Perspectiva Animaciones Resolucin Lateral, superior o isomtrica. Es recomendable comenzar con spritesets en perspectiva lateral. Nuestro personaje NO podr realizar ms acciones que las que tiene su spriteset. El tamao en pxeles de cada animacin (Sprite) es una buena indicacin de la calidad del spriteset.
Observa que en la Imagen 1, tenemos un spriteset de perspectiva isomtrica, sin animacin de salto y con una resolucin media, ni muy alta como pudiera ser la de Street Fighter II ni muy baja como pudiera ser la del primer Super Mario Bros.
Tema 1 Grficos I
Pg. 1
1.2 Backgrounds Un background es el grfico del escenario donde transcurrir la accin de nuestro videojuego. Selecciona varios backgrounds. Nuevamente podrs encontrarlos en webs como http://vgmaps.com, en Google Imgenes con las palabras clave scroll o background, y tambin ripped. Y de la misma forma tambin encontrars muchos en el DVD de recursos de la actividad, en la Biblioteca GFX, en la seccin de Scrolls, fondos y escenarios. Al igual que los spritesets, los backgrounds tambin pueden tener perspectiva lateral, superior o isomtrica. Selecciona backgrounds con la misma perspectiva que tus spritesets. Recuerda que, como ya hemos dicho, es mejor comenzar con un videojuego en perspectiva lateral. En cuanto a la resolucin, evita backgrounds con una resolucin demasiado pequea (Inferior a 320x240 Imagen 2: Ejemplo de bakground pxeles) porque probablemente no lleguen a cubrir la pantalla, y evita tambin resoluciones demasiado grandes (Superiores a 4096x4096 pxeles) porque consumiremos muchos recursos y ralentizaremos el PC, tanto a la hora de trabajar con ellos como a la hora de moverlos dentro de nuestro videojuego. En el peor caso podremos escalar el tamao de los backgrounds, pero es algo que tenderemos a evitar por ahora. Observa que los backgrounds y los sprites son algo totalmente separado. En ocasiones nos encontraremos con backgrounds que conservan algunos elementos como enemigos o items. stos se denominan backgrounds sucios, ya que probablemente tengamos que terminar limpiando esas impurezas antes de incluirlos en nuestro videojuego. Observa que en la Imagen 2, tenemos un background de perspectiva superior, con una resolucin algo baja, y que podemos considerar sucio si nuestra idea es que esos arbustos puedan ser cortados.
Tema 1 Grficos I
Pg. 2
2. Grficos II En el tema anterior hicimos una cuidadosa seleccin de grficos para nuestro videojuego. Ahora vamos a trabajar con ellos para dejarlos listos para ser incluidos en un videojuego.
2.1 Recorte de spritesets Cada spriteset almacena una gran cantidad de animaciones (Sprites) distintas. Lo primero que tenemos que hacer es recortarlas para dejar cada sprite en un archivo de imagen distinto. Es algo que podramos hacer con Paint, seleccionando un rectngulo, copindolo, pegndolo en un archivo nuevo... Pero esa sera una tarea demasiado costosa, es por eso que haremos uso de un programa especfico para esta tarea de recorte: Castle Split Image. Este programa nos permite dividir cualquier imagen en filas y columnas, tantas como queramos, de manera que pulsando despus un botn el programa se encarga de generar todos los recortes del spriteset, cada uno en un archivo distinto. Observa que en el Dibujo 1 hemos usado 2 reglas verticales para separar las animaciones, es aconsejable que cada regla quede lo ms cercana posible a la animacin, lo ideal sera por ejemplo a 1 pixel de distancia.
2.2 Eliminar el color de fondo Para poder hacerlo es imprescindible que hayas generado los cortes en formato .png. Ests a tiempo de asegurarte, ya que si no estn en ese formato todo lo que hagas de aqu en adelante no servir de nada. Para eliminar el color de fondo usaremos Paint.NET, un programa con decenas de funcionalidades que iremos viendo poco a poco, aunque por ahora nos bastar con una de ellas: La herramienta Varita Mgica. La varita mgica permite seleccionar una zona de color uniforme haciendo clic sobre un color de la imagen, podemos eliminar esa zona pulsando la tecla Supr. Slo queremos eliminar el color de fondo, as que configuraremos la Tolerancia a 0% para evitar que seleccione otros colores similares. Vers la barra de tolerancia en la parte central superior del programa. Ten en cuenta que en ocasiones el color de fondo no est contiguo, sera el caso de un grfico en forma de donut, cuyo centro tambin tendr el color de fondo. Para evitar tener que hacer 2 (O ms) veces el mismo proceso, podemos configurar la saturacin en modo global haciendo clic en el icono en forma de Relmpago, que pasar a tomar forma de Planeta indicando que se seleccionar todo el color en toda la imagen.
Debes eliminar el color de fondo en todos y cada uno de tus recortes. Puedes agilizar mucho esta tarea si seleccionas todos tus recortes y los arrastras al lienzo de Paint.NET. El programa los abrir todos a la vez y podrs trabajar mucho ms rpido. Cuando hayas terminado, en lugar de guardar uno por uno los recortes, puedes cerrar directamente el programa y te avisar de si quieres guardar todo el trabajo. Las tareas de recorte de grficos y de eliminacin del color de fondo son bastante montonas, pero son imprescindibles si queremos hacer un videojuego 100% personalizado por nosotros. Es recomendable recortar los spritesets completamente as como eliminar el color de fondo de todos los recortes, as no tendremos que volver a repetir esta tarea.
Tema 2 Grficos II
Pg. 2
2.3 Crear un fichero para grficos (FPG) Todos los grficos que vamos a usar en nuestro videojuego deben estar almacenados en un fichero de extensin FPG, en el que se guardan asignndoles un nmero distinto a cada uno. Para realizar esta tarea usaremos el programa FPG Edit. FPG Edit tiene una seccin superior en la que podemos navegar por nuestras carpetas buscando nuestros grficos, mientras que en la seccin inferior tenemos las imgenes que contiene nuestro FPG, con un nmero asociado en lugar de su nombre de archivo original. Un botn verde con forma de flecha (Add) nos permite aadir nuevas imgenes, cuyo nmero podemos indicar modificando el valor que hay a la derecha de la barra central del programa y pulsando a continuacin el botn '#' que hay a su lado.
Tema 2 Grficos II
Pg. 3
3. Introduccin al lenguaje de programacin Bennu En el tema anterior construimos un fichero FPG con todos los grficos que vamos a utilizar en nuestro videojuego. Ya estamos listos para comenzar a hacer uso de l y empezar a ver en pantalla nuestros grficos.
3.1 Concepto de lenguaje de programacin Un lenguaje de programacin consiste en una serie de palabras clave que, correctamente asociadas y ordenadas, nos permiten dialogar con el procesador expresando el comportamiento que queremos obtener para nuestro programa o videojuego. El lenguaje Bennu posee una gran cantidad de palabras clave, con distinta utilidad, potencia y complejidad, que iremos introduciendo poco a poco durante este tutorial. Si es la primera vez que te encuentras con un lenguaje de programacin debes saber que en general son una mezcla entre la lengua inglesa y las matemticas. No te dejes intimidar por su aparente complejidad, seguro que vas a disfrutar mucho utilizndolo.
3.2 Concepto de proceso en Bennu Existen muchos tipos de lenguajes de programacin. Java es un lenguaje orientado a objetos, C es un lenguaje imperativo, Bennu en cuestin es un poco especial, segn la teora de lenguajes de programacin puede considerarse un lenguaje orientado al proceso. Para hacernos una idea, todo elemento que haya en pantalla es un proceso: Nuestro protagonista, cada disparo, cada enemigo... El lenguaje nos permite definir el comportamiento de un proceso, por ejemplo el de un disparo, y ste puede ser invocado tantas veces como queramos de una manera tan sencilla como es escribir su nombre. Es algo que veremos a fondo en los siguientes temas, por ahora vamos a trabajar a fondo con un nico proceso, que definiremos como queramos, y que ser invocado tan slo 1 vez.
Pg. 1
3.3 Aspectos bsicos de un proceso en Bennu Todo proceso en Bennu tiene una serie de aspectos que determinan cmo lo visualizaremos en la pantalla, o incluso si estar visible en la pantalla o fuera de ella. A continuacin vamos a enumerar los aspectos bsicos de un proceso en Bennu. Todos los aspectos tienen un determinado valor numrico. Te dars cuenta de que con ellos y con nuestro FPG tenemos lo suficiente para modelar prcticamente cualquier comportamiento que hayas visto en cualquier videojuego comercial en 2 dimensiones. Aspecto graph Funcionamiento Es el aspecto fundamental de un proceso. Es el nmero de grfico del FPG que nuestro proceso utilizar para mostrarse en pantalla. Por ejemplo, si hacemos que graph valga 201 y ese nmero corresponde al grfico de un misil en nuestro FPG, entonces nuestro proceso se mostrar en pantalla con el aspecto de ese misil. Si no le decimos a nuestro proceso cul es su valor de graph, entonces graph valdr 0 y el proceso ser invisible. Sirve para determinar la posicin de un proceso en pantalla. Generalmente la pantalla tiene una resolucin de 640x480 pxeles, aunque Bennu nos permitir cambiarla ms adelante. El valor de x es el pxel, medido desde el extremo izquierdo de la pantalla, en el que se mostrar el proceso. Por ejemplo, si hacemos que x valga 320, entonces el proceso se mostrar en el centro de la pantalla horizontalmente. Si no le decimos a nuestro proceso cul es su valor de x, entonces x valdr 0 y el proceso aparecer en el extremo izquierdo de la pantalla. De forma similar a x, sirve para determinar la posicin de un proceso en pantalla. El valor de y es el pxel, medido desde la parte superior de la pantalla, en el que se mostrar el proceso. Por ejemplo, si hacemos que y valga 240, entonces el proceso se mostrar en el centro de la pantalla verticalmente. Si no le decimos a nuestro proceso cul es su valor de y, entonces y valdr 0 y el proceso aparecer en el extremo superior de la pantalla. Determina cul es el tamao con el que se dibujar el proceso en pantalla. Observa que la pantalla tiene una cierta resolucin en pxeles y cada grfico del FPG un cierto tamao en pxeles. El valor de size indica en tanto por ciento (%) el tamao con el que veremos el grfico en pantalla. Por ejemplo, si hacemos que size valga 200 y el graph del proceso tiene un tamao de 20x20 pxeles, entonces el proceso se dibujar en pantalla con un tamao de 40x40 pxeles, un 200% ms grande que su tamao original. Si no le decimos a nuestro proceso cul es el valor de size, entonces size valdr 100 y el proceso se dibujar con el tamao original de su grfico.
size
Pg. 2
angle
Indica cul es el ngulo de giro con el que se dibujar el proceso en pantalla. Gracias a este aspecto, con un nico grfico de un baln podremos hacer que ste gire. El valor de angle indica en milsimas de grado la inclinacin con la que se dibujar el grfico en pantalla . Por ejemplo si hacemos que angle valga 180000 el proceso se ver girado 180 grados, boca abajo. Si no le decimos a nuestro proceso cul es su valor de angle, entonces angle vale 0 y el proceso se dibujar normalmente. La razn por la cual angle se mide en milsimas de grado es para permitir giros con mucha precisin, sobre todo en grficos grandes, como pudiera ser un escenario.
3.4 Aspectos avanzados de un proceso en Bennu A continuacin una serie de aspectos de un proceso que no utilizaremos por el momento, pero que nos permitirn ms adelante programar comportamientos ms complejos de una manera mucho ms cmoda. Aspecto flags Funcionamiento Este aspecto avanzado permite que el proceso sea dibujado aplicando distintos efectos grficos. Su valor numrico no tiene ninguna unidad de medida, simplemente cada nmero ofrece un efecto distinto y hay que consultar esta tabla para conocer su funcionamiento. Valores posibles de flags son: 0 para dibujado normal, 1 para dibujar el grfico espejado horizontalmente, 2 para dibujar el grfico espejado verticalmente, 4 para dibujar el grfico con una transparencia del 50%, 8 para dibujar el grfico con una transparencia del 90%, y el resto los probaremos ms adelante, en general no se usan los flags a partir del 8. Los valores de flags corresponden a potencias de 2, esto permite que puedan sumarse entre ellos obteniendo la aplicacin de todos los valores sumados. Por ejemplo si hacemos que flags valga 7 estaremos aplicando 1+2+4, y el proceso se dibujar espejado horizontalmente, espejado verticalmente y adems con una transparencia del 50%. Si no indicamos cul es el valor de flags, entonces flags vale 0 y el proceso se dibuja normalmente. Este aspecto avanzado es til cuando tenemos una gran cantidad de procesos distintos en pantalla. En esos casos observaremos que unos procesos son dibujados por encima o por debajo de otros, y no siempre obtendremos el orden de dibujado esperado. El valor de z indica la profundidad de dibujado, de forma que procesos con valores mayores de z se dibujarn por debajo de los procesos con menores valores de z. Por ejemplo si a nuestro proceso protagonista le damos un valor de z que vale 1 y a un proceso aura situado sobre el protagonista le damos un valor de z que vale 2, entonces veremos el aura detrs de nuestro protagonista. Sin embargo si intercambiamos los valores de z veremos el aura por delante de nuestro protagonista, ocultando su grfico. Si no especificamos el valor de z, entonces z vale 0 y los procesos se dibujan con el orden que decida el procesador.
Pg. 3
Aspecto ctype
Funcionamiento Este aspecto avanzado es til cuando tenemos un escenario o scroll cuyo tamao en pxeles es superior a la resolucin en pxeles de la pantalla. En ese caso el scroll no cabe en la pantalla y slo podemos ver una determinada zona del scroll cada vez. Si esto sucede, es probable que nos interese que los aspectos bsicos x,y de nuestro proceso no tengan como origen la esquina superior izquierda de la pantalla, sino que tengan como origen la esquina superior izquierda del escenario o scroll. Si no especificamos el valor de ctype, entonces ctype vale 0 y las coordenadas x,y tienen como origen la esquina superior izquierda de la pantalla. En cambio si hacemos que ctype valga 1, entonces las coordenadas x,y tienen como origen la esquina superior izquierda de nuestro scroll.
3.5 Ejercicio terico: Los aspectos de un proceso Bennu en un videojuego comercial Piensa en Galaxian, un sencillo arcade de los aos 80, e intenta adivinar el posible valor de los aspectos de proceso en Bennu que corresponderan para cada uno de los elementos que muestra en pantalla. Piensa despus en The Legend of Zelda, un RPG ms complejo desarrollado para SNES en los aos 90, e imagina cmo cambiaran los valores de los aspectos, especialmente los avanzados como ctype, que facilitara calcular las coordenadas x,y de los diferentes procesos que recorren el escenario o que se mantienen fijos en pantalla.
4. El lenguaje: Asignaciones En el tema anterior explicamos los aspectos bsicos de cualquier proceso grfico en Bennu. Estos eran: Aspectos bsicos graph x,y size angle flags ctype z file Su nmero de grfico dentro del fichero .fpg que creamos en las primeras sesiones. Su posicin horizontal y vertical medida en pixels respecto de la esquina superior izquierda de la pantalla. Su tamao medido en % respecto del tamao original de su graph (Por defecto 100). Su ngulo medido en milsimas de grado (Por defecto 0). Efectos visuales (0 = Normal, 1 = Espejo horizontal, 2 = Espejo vertical, 4 = Transparencia 50%, etc.) Indica si las coordenadas x,y son relativas a la pantalla o al scroll (Valores 0 1 respectivamente). Indica la profundidad de dibujado, que indica si un proceso se dibuja por encima o por debajo de otro. Especifica el fichero .fpg en el que se buscar el graph (Slo cuando usemos mltiples ficheros .fpg).
Aspectos avanzados
4.1 Definicin de asignacin Una asignacin establece un valor numrico a cualquiera de los aspectos de un proceso. Su sintaxis es la siguiente: Aspecto = Valor; //Es importante no olvidar el smbolo ';' (Un punto y coma) que indica el fin de la asignacin. A continuacin aadiremos las siguientes asignaciones entre las etiquetas BEGIN y END del proceso principal de nuestro videojuego y comprobaremos que el funcionamiento es exactamente el indicado en la tabla anterior. graph=1; x=100; y=200; size=50; angle=90000; El proceso toma el grfico nmero 1 de nuestro fichero .fpg. Posiciona el proceso a 100 pixels del lateral izquierdo de la pantalla. Posiciona el proceso a 200 pixels de la parte superior de la pantalla. Cambia el tamao del proceso a un 50% de su tamao original. El proceso se muestra rotado 90 grados (Recuerda que angle se mide en milsimas de grado).
Pg. 1
4.2 Primeras pruebas con nuestro videojuego Bennu En la carpeta Videojuego que lleva asociada este tema, encontrars un programa tan sencillo que no puede considerarse un videojuego, pero que nos servir para realizar todas las pruebas iniciales y ser la base para el videojuego que crearemos a medida que avancemos en este tutorial. El contenido de la carpeta es el siguiente: DLL images videojuego.prg En esta carpeta se encuentra toda la maquinaria (Archivos DLL) que hace que nuestro videojuego pueda ejecutarse. Por el momento no tocaremos nada de esa carpeta. En esta carpeta se guarda el/los ficheros para grficos (FPG) del videojuego. Por ahora trabajaremos con el FPG que tiene y en el prximo tema lo sustituiremos por el nuestro. Este archivo contiene el cdigo fuente que editaremos para aadir nuevas instrucciones. Podras editarlo perfectamente con el Bloc de Notas, aunque si instalas la utilidad Bennupack podrs editarlo con el programa Notepad++, que resulta mucho ms cmodo de utilizar e incluye algunas otras utilidades interesantes. Comprueba errores en el cdigo (sto se llama compilar) y si todo es correcto ejecuta el videojuego. Lo mismo que lo anterior, pero funciona en el sistema operativo Linux. Este fichero se genera a partir del cdigo cuando compilamos, y es lo que realmente se ejecuta.
Ya ests listo para hacer las pruebas propuestas en este tema y otras que se te puedan ocurrir. No olvides guardar el archivo .prg cada vez que modifiques el cdigo, no olvides asegurarte de que el resultado de la compilacin no muestra errores, y durante tus primeros pasos como programador, no olvides que las instrucciones deben terminarse con un ';'. Los errores anteriores sern el 99% de tus posibles problemas a la hora de empezar a programar, el resto los iremos viendo ms adelante.
Pg. 2
5. El lenguaje: Incrementos En el tema anterior establecimos valores numricos fijos a los diferentes aspectos de un proceso. La asignacin de valores fijos no es suficiente para poder crear nuestro videojuego, as que ahora aprenderemos a hacer que esos aspectos se modifiquen automticamente durante la ejecucin gracias a las instrucciones de incremento.
5.1 Definicin de incremento Una incremento establece una variacin sobre cualquiera de los aspectos de un proceso a partir de una operacin matemtica que generalmente es muy sencilla, por ejemplo una suma. Su sintaxis es la siguiente: Aspecto = Aspecto + Valor; //Una vez ms, no olvides el ';' (Punto y coma).
Prueba a aadir las siguientes asignaciones entre las etiquetas LOOP y END del proceso protagonista de tu videojuego y comprueba que el incremento da resultado. graph=graph+1; x=x+4; y=y-1; size=size+2; angle=angle-1000; El proceso toma el siguiente nmero de grfico del fichero .fpg cada vez que se muestra en pantalla. El proceso se desplaza 4 pxeles hacia la derecha cada vez que se muestra en pantalla. El proceso sube 1 pxel cada vez que se muestra en pantalla. El proceso aumenta en un 2% su tamao cada vez que se muestra en pantalla. El proceso rota 1 grado en sentido antihorario cada vez que se muestra en pantalla.
5.2 Aclaraciones sobre las asignaciones y los incrementos Tanto las asignaciones como los incrementos son instrucciones, y por tanto siempre deben situarse entre las etiquetas BEGIN y END, que indican el principio y el final de la zona de instrucciones que ejecuta un proceso. Los procesos tiene un nico bloque de instrucciones BEGIN END, y para facilitar su ubicacin, la etiqueta BEGIN se tabula exactamente a la misma altura que la etiqueta END que la cierra. Dentro del bloque BEGIN END, justo al final del mismo, encontraremos un bloque LOOP END. Las instrucciones que vienen antes del bloque LOOP END se ejecutan una nica vez al iniciar el proceso y suelen ser asignaciones, mientras que las instrucciones dentro del LOOP END se ejecutan todo el tiempo y suelen ser incrementos. Tema 5 El lenguaje: Incrementos Pg. 1
5.3 Ejercicio: Uso de los aspectos bsicos de un proceso Con lo aprendido hasta ahora ya deberas saber hacer que tu proceso protagonista aparezca inicialmente en la esquina superior izquierda de la pantalla, y se desplace hasta la esquina inferior derecha al tiempo que gira y aumenta de tamao. Truco: La proporcin de pantalla en pxeles es 4:3, por tanto avanzando 4 pxeles en horizontal (Eje X) y 3 pxeles en vertical (Eje Y) debera recorrer exactamente la diagonal de la pantalla. Ejemplos de resolucin de pantalla que cumplen la proporcin 4:3 son 640x480, 800x600 y 1024x768. Estas resoluciones pueden establecerse modificando los valores de la instruccin set_mode(800,600,16,MODE_WINDOW); que encontrars en el primer BEGIN del cdigo de tu videojuego.
5.4 Ejercicio: Uso de los aspectos avanzados de un proceso El aspecto flags de un proceso produce efectos visuales muy interesantes. Aprovechando lo aprendido sobre incrementos puedes hacer que tu proceso incremente en 1 el valor de su variable flags con la instruccin: flags=flags+1; De esta forma podremos ver la totalidad de los efectos visuales que podremos aplicar modificando esta variable. Truco: Los diferentes flags son apilables, esto significa que cuando flags vale 5 estamos aplicando flags=1 (Espejo horizontal) y flags=4 (Transparencia 50%) al mismo tiempo, puesto que 1+4=5. Otro ejemplo ms complejo sera cuando flags vale 209, que aplicara los flags 128+64+16+1 y cuyo efecto sera bastante extrao... En general no usaremos flags ms all del 16.
Pg. 2
6. El lenguaje: Condiciones En el tema anterior establecimos incrementos en los aspectos del proceso principal de nuestro videojuego logrando que su grfico, su posicin, su tamao, etc. Variasen durante la ejecucin. El uso de instrucciones de incremento ofrecen dinamismo en el videojuego, pero el comportamiento que obtenemos siempre es el mismo y no hay ningn tipo de interaccin. Para lograr interaccin y un resultado mucho ms sofisticado debemos hacer uso de las condiciones.
6.1 Definicin de condicin Su nombre exacto es condicional. Consiste en establecer una condicin, de manera que, slo cuando sta se cumpla se ejecutarn una o ms instrucciones, que pueden ser asignaciones, incrementos o incluso otras condiciones. Los condicionales son algo que utilizaremos extensivamente durante toda nuestra vida como programadores, as que es conveniente que te quede totalmente claro su funcionamiento. Su sintaxis es la siguiente: IF (<condicin>) <Asignaciones, incrementos u otras condiciones, cada una en una lnea> END Observa que hemos encerrado entre <> la condicin sin explicar su sintaxis exacta. Esto se debe a que puede haber una gran diversidad de ellas. Vamos a ver unas cuantas en este tema, y ms adelante veremos una descripcin ms formal de todas las condiciones que podemos llegar a usar. key(_right) Se cumple cuando se pulsa la tecla direccional derecha (_right) del teclado. Podemos sustituir _right por cualquier otra tecla direccional, cualquier letra o nmero, _enter, _space, etc. El nombre de la tecla siempre va precedido por el carcter '_'. Se cumple cuando el aspecto x (La posicin horizontal en pxeles) del proceso tiene un valor menor que 0. Esto ocurre cuando el proceso se sale por la izquierda de la pantalla. Se cumple cuando el aspecto y (La posicin vertical en pxeles) del proceso tiene un valor mayor que 600. Esto ocurre cuando el proceso se sale por la parte inferior de la pantalla. Siempre y cuando estemos ejecutando con una resolucin de pantalla de 800x600 pxeles. Se cumple cuando el aspecto graph (El nmero de grfico del FPG) del proceso tiene un valor exactamente igual a 9. Esta condicin nos permite por ejemplo detectar cundo termina una animacin. Observa que se usan 2 smbolos '=' para comprobar (leer), y 1 smbolo '=' para asignar (escribir). Pg. 1
x<0 y>600
graph==9
6.2 Instrucciones a ejecutar cuando se cumple una determinada condicin Vamos a ver unos cuantos ejemplos tpicos de uso de las condiciones: IF (x>800) x=0; END IF (key(_down)) y=y+4; END IF (key(_right)) graph=graph+1; IF (graph>9) graph=1; END END Cuando el aspecto x del proceso tiene un valor mayor que 800 pxeles (Se sale por la derecha de la pantalla), se le asigna un 0 a su aspecto x para hacer que vuelva a aparecer por la izquierda. Cuando se pulsa la tecla direccional abajo, el proceso incrementa su aspecto y en 4, bajando 4 pxeles cada vez. Servira para desplazar nuestro proceso por la pantalla. Cuando se pulsa la tecla direccional derecha, el proceso incrementa su grfico en 1 (Recorre 1 a 1 los grficos del FPG y por tanto se anima). Si adems de pulsar la tecla direccional derecha el valor de graph se hace mayor que 9, volvemos a asignar el graph 1, logrando as que el proceso utilice la animacin cuyos grficos van del 1 al 9.
Ten en cuenta que las nuevas instrucciones condicionales que estamos aadiendo pueden entrar en conflicto con incrementos o asignaciones anteriores que pudiera haber en nuestro proceso. Asegrate de que entiendes lo que ocurrir cuando aadas nuevas instrucciones, y si notas algn comportamiento extrao, no dudes en borrar todas las instrucciones y volver a empezar.
6.3 Ejercicio: Programar los controles de un juego de naves Con las indicaciones dadas en los ejemplos anteriores ya deberas ser capaz de aadir las condiciones necesarias en el proceso principal de tu videojuego para que se mueva hacia arriba, abajo, izquierda y derecha con las teclas direccionales correspondientes. Adems puedes programarlo de manera que, si el proceso se sale por arriba, por abajo, por la izquierda o por la derecha de la pantalla, vuelva a aparecer por el lado opuesto. No te compliques demasiado programando la animacin, ya que es un tema ms complejo de lo que parece a simple vista y lo veremos con detenimiento ms adelante.
Pg. 2
7. Procesos I Ya hemos modificado el proceso principal de nuestro videojuego y hemos practicado con l las instrucciones fundamentales para modificar los aspectos de un proceso en Bennu. En el punto 3.2 de este temario comentamos que era posible definir el comportamiento de un proceso y a continuacin poderlo invocar tantas veces como queramos. Hasta ahora slo hemos modificado el proceso principal de nuestro videojuego. Hemos definido cual era su comportamiento, y no hemos tenido necesidad de invocarlo porque el proceso principal se invoca siempre (Y una sola vez.) cuando ejecutamos el videojuego.
7.1 Definir un proceso en Bennu Vamos a definir un proceso. Sus instrucciones sern exactamente las mismas que habamos incluido en el proceso principal de nuestro videojuego, la diferencia es que ahora podemos darle un nombre, el que nosotros queramos, por ejemplo protagonista. La sintaxis de un proceso en Bennu es la siguiente: PROCESS <Nombre del proceso> ( ) BEGIN <Asignaciones iniciales> LOOP <Asignaciones, incrementos y condiciones a ejecutar siempre> FRAME; END END Despus del END del proceso principal (Al final del cdigo), podemos aadir todas las definiciones de proceso que queramos, aunque por ahora slo vamos a aadir una. Observa que el nombre del proceso debe ir seguido de los caracteres ( ), un parntesis de apertura y uno de cierre que pronto veremos para qu sirven. Entre las etiquetas BEGIN y END iran las instrucciones que determinan el comportamiento del proceso. Puedes cortar directamente todas las instrucciones entre el BEGIN END que tenas en el proceso principal de tu videojuego y pegarlas aqu, a excepcin de las instrucciones set_mode(); y load_fpg(); que slo se deben ejecutar al principio para configurar el modo de vdeo y cargar el archivo FPG con los grficos, respectivamente. Tema 7 Procesos I Pg. 1
7,2 Invocar un proceso en Bennu Si has definido correctamente el proceso anterior, al compilar no deberas tener errores, pero al ejecutar es probable que no veas nada en pantalla. Esto se debe a que todava no has invocado el proceso, y tu videojuego no hace nada ms que establecer el modo de vdeo con set_mode();, cargar el archivo FPG con load_fpg(); y terminar. Para invocar un proceso basta con escribir su nombre, es lo que haremos despus de cargar el archivo FPG. Si nuestro proceso se llamaba protagonista, entonces la invocacin tendra la siguiente sintaxis: protagonista ( ) ; Observa nuevamente que la invocacin tambin incorpora los caracteres ( ), y adems un ';' al final, como las instrucciones de asignacin e incremento que vimos en los temas anteriores. Antes de seguir adelante, si no consigues resultados, recuerda que cada tema lleva asociado un videojuego donde puedes comprobar el cdigo correcto. Si tienes algn problema lo ms probable es que te hayas dejado alguna de las etiquetas END, que sirven para indicar el fin de los bloques de cdigo que abren las etiquetas BEGIN, LOOP e IF, entre otras.
7.3 Ejercicio: Invocar un proceso disparo Una vez hayas conseguido crear tu proceso Bennu correctamente, vamos a hacer la prueba de crear otro proceso adicional que ser el disparo de nuestro protagonista. Lo llamaremos disparo (Por ejemplo.), y su definicin se encontrar justo despus del ltimo END del proceso protagonista. Puedes darle las asignaciones iniciales que creas convenientes, por ahora no te preocupes por su posicin inicial ya que ms adelante aprenderemos a hacer que los disparos aparezcan en la posicin exacta donde se encuentra el protagonista. El disparo debe tener necesariamente un bloque LOOP END donde se incluyan los incrementos necesarios para desplazarlo por la pantalla. Adems, antes del END, deberemos tener una instruccin FRAME;, esto generalmente se cumplir para todos los procesos dentro de su bloque LOOP END, ya que de lo contrario no mostrarn nada en pantalla, e incluso haremos que el videojuego se quede bloqueado. Ms adelante explicaremos la instruccin FRAME; en profundidad. Para invocar el disparo aadiremos el siguiente condicional dentro del bloque LOOP END del protagonista: IF ( key(_space) ) disparo ( ) ; END Tema 7 Procesos I Pg. 2
8. Procesos II Ya hemos aprendido lo ms bsico para definir e invocar nuevos procesos. Es importante que en este punto tengas bien clara la diferencia entre definir un proceso e invocarlo: Definir un proceso: Consiste en escribir todo su cdigo en un bloque PROCESS - END. Slo se hace una vez. El proceso no aparece en pantalla hasta que sea invocado.. Invocar un proceso: Consiste en escribir su nombre seguido de ( ); Podemos hacerlo tantas veces como queramos. Hace que el proceso aparezca en pantalla.
Si has realizado correctamente el programa del tema anterior, habrs comprobado que el proceso disparo no era capaz de ser invocado en la misma coordenada x,y en la que se encontraba el protagonista. En este tema vamos a ver una sencilla forma de lograr esto.
8.1 Jerarqua de procesos en Bennu Ahora que distinguimos entre definir e invocar un proceso, podemos comenzar con uno de los aspectos ms avanzados del lenguaje Bennu. Se trata de la jerarqua de procesos, y es que todos los procesos en Bennu se organizan exactamente igual que un rbol genealgico. El padre que gobierna toda la jerarqua de procesos es el proceso principal, se que est definido dentro del primer bloque BEGIN END y que por ahora se encarga de ejecutar las instrucciones set_mode(); y load_fpg();. El proceso protagonista era invocado dentro del BEGIN END del proceso principal, por tanto podemos decir que el protagonista es hijo del proceso principal, y obviamente, que el proceso principal es el padre del protagonista. Finalmente el proceso disparo era invocado dentro del BEGIN END del proceso protagonista, por tanto decimos que el proceso disparo es hijo del proceso protagonista, y de la misma manera, el proceso protagonista es padre del proceso disparo. Proceso principal Proceso protagonista Proceso disparo
Tema 8 Procesos II
Pg. 1
8.2 Herencia de procesos en Bennu Apoyndose en la jerarqua de procesos, Bennu nos ofrece un mecanismo muy sencillo para hacer que ciertos procesos puedan heredar aspectos (Como graph, x, y, size, etc.) de sus padres. Esto nos servir para, por ejemplo, hacer que el proceso disparo herede la coordenada x, y de su padre (El protagonista) y as lograr que los disparos nos ofrezcan la sensacin correcta, apareciendo siempre en la posicin del protagonista, sea cual sea. Para ello usaremos uno de los aspectos ms avanzados de un proceso Bennu. Se trata de un aspecto llamado father, que mediante el operador '.' nos permite acceder a los aspectos bsicos del proceso padre segn esta table: father.graph father.x father.y ... Indica el nmero de grfico en el archivo FPG de nuestro proceso padre. Indica la posicin horizonal en pxeles de nuestro proceso padre. Indica la posicin vertical en pxeles de nuestro proceso padre. ...y lo mismo con father.size, father.angle, father.flags, etc.
Para hacer que el disparo herede la coordenada x, y de su padre (El protagonista) al ser invocado, podemos usar la asignacin, asignando a su x el valor de father.x y asignando a su y el valor de father.y. Estas instrucciones estaran dentro del bloque BEGIN END del proceso disparo, antes del bloque LOOP END, ya que nos interesa que la herencia slo se realice una vez nada ms ser invocado el disparo. PROCESS disparo() BEGIN x=father.x; y=father.y; graph=45; LOOP x=x+10; FRAME; END END //Definicin del proceso disparo: //Inicio de las instrucciones del proceso //Asignamos la x del padre //Asignamos la y del padre //Asignamos el grfico que queramos //A partir de aqu repetimos siempre //Incrementamos la x en 10 //Mostramos resultado en la pantalla //Fin de la repeticin //Fin de las instrucciones del proceso
Adems de father, podemos acceder a la jerarqua en otras direcciones utilizando son (El ltimo hijo de un proceso), smallbro (El hermano anterior de un proceso) y bigbro (El siguiente hermano de un proceso). Tambin podramos acceder a un abuelo utilizando father.father, pero no es necesario complicarse, ya que en la prctica nos bastar con usar father solamente.
Tema 8 Procesos II
Pg. 2
9. Prcticas Es un buen momento para hacer un uso ms avanzado de todos los conceptos que hemos visto sobre el lenguaje Bennu. El inters principal de este tema no es introducir conceptos nuevos, sino ms bien aprovechar lo que ya sabemos para mejorar ciertos aspectos de nuestro videojuego, especialmente el apartado de animacin. Como nica novedad explicaremos algunos usos ms avanzado de los condicionales.
9.1 Condicionales con varias alternativas En ocasiones puede interesarnos crear un condicional en el que se evalen varias condiciones, pero slo se ejecuten las instrucciones asociadas a la primera que se cumpla, ignorando las siguientes. El ejemplo ms sencillo es el caso de las teclas direccionales izquierda y derecha. Vamos a utilizar los condicionales de manera que, cuando se pulsen ambas teclas a la vez, nuestro videojuego interprete slo una. Para ello podemos utilizar un condicional mltiple, cuya sintaxis es la siguiente: IF (<condicin 1>) <instrucciones 1> ELSIF (<condicin 2>) <instrucciones 2> ELSIF ELSE <instrucciones N> END //Primera condicin a evaluar //Ejecutaremos esto si se cumple //Segunda condicin a evaluar //Ejecutaremos esto si se cumple //Siguientes condiciones a evaluar... //Si no se cumple ninguna de las anteriores condiciones //Ejecutaremos esto //Fin del condicional mltiple
Es nmero de ELSIF que sirven como alternativas a la primera condicin es ilimitado, podemos poner tantos como queramos. Tambin es importante que entiendas que todas las condiciones se evalan por orden, y una vez se cumple la primera de ellas, se ejecutan sus instrucciones asociadas y se sale del condicional mltiple. Esto quiere decir que slo se interpreta como mximo una condicin. Y finalmente debes saber que es posible tener un condicional mltiple con ELSE pero sin ningn ELSIF o bien con uno o ms ELSIF pero sin el ltimo ELSE.
Tema 9 Prcticas
Pg. 1
9.2 Ejercicio: Aplicar todo lo anterior a nuestro videojuego Gracias a todo lo que hemos visto hasta ahora en este temario, debemos ser capaces de lograr que nuestro protagonista sea capaz de mirar a izquierda y a derecha (Modificando su aspecto flags) cuando pulsamos la tecla correspondiente. Tambin deberamos poder conseguir que se anime, utilizando su animacin de andar. Con el disparo ocurre lo mismo, deberamos ser capaces de lograr que los disparos avancen en la direccin a la que mira nuestro protagonista (Nuevamente gracias al aspecto flags). Tambin deberan poder animarse con su animacin correspondiente. Aplica todo esto a tu videojuego. Si tienes dudas puedes mirar el videojuego de ejemplo de este tema. Sobre todo evita copiar y pegar las instrucciones del videojuego de ejemplo en tu videojuego. Aunque resulta tentador, en general no trae ms que problemas, ya que el objetivo no es hacer que el videojuego funcione correctamente, sino que t entiendas cmo hacer que el videojuego funcione correctamente.
Tema 9 Prcticas
Pg. 2
10. Un Matamarcianos Si has completado con xito el reto del tema anterior ya estas preparado para realizar tu primer videojuego jugable. Gracias a la generacin de nmeros aleatorios de Bennu, su sistema de deteccin de colisiones entre procesos y unas breves nociones sobre destruccin (Formalmente muerte) de procesos, dotaremos a nuestro videojuego de todo lo necesario para ser un matamarcianos de los aos 80.
10.1 Introduccin a la generacin de nmeros aleatorios en Bennu La generacin de nmeros aleatorios es un aspecto bsico en cualquier videojuego para garantizar que el resultado de la ejecucin no sea siempre el mismo. En nuestro caso vamos utilizar los nmeros aleatorios para crear un proceso enemigo capaz de moverse aleatoriamente en los ejes x, y de manera que su movimiento sea siempre impredecible. La sintaxis de la generacin de un nmero aleatorio es la siguiente: rand ( <valor_mnimo> , <valor_mximo> ); Donde valor_mnimo y valor_mximo son los valores numricos del intervalo dentro del que ser generado el nmero aleatorio. Unos ejemplos de aplicacin de los nmeros aleatorios seran los siguientes: Rand ( 1 , 100 ); x = x + rand ( -4 , 4 ); IF ( rand ( 0 , 1 ) == 0 ) x = x + 4; END Devuelve un valor aleatorio entre 1 y 100. Es el ejemplo ms sencillo, pero utilizada por s sola esta instruccin no tiene ningn efecto. El proceso modifica su posicin en el eje x una cantidad aleatoria de pxeles comprendida entre -4 y 4. El proceso ejecuta la instruccin x = x + 4; una de cada dos veces. Es como tirar una moneda al aire, y si sale cara, realizar una accin.
Con estas nociones crea un nuevo proceso llamado enemigo, con lo que has aprendido hasta ahora puedes echarle imaginacin o bien puedes consultar el enemigo en el videojuego de ejemplo asociado a este tema.
Tema 10 Un Matamarcianos
Pg. 1
10.2 Introduccin a la muerte de procesos en Bennu Existen varias formas de matar a un proceso en Bennu, aunque vamos a ver la ms sencilla de todas. Generalmente nos interesar que un proceso muera cuando se cumpla una determinada condicin. Dado que nuestros enemigos son capaces de moverse libre y aleatoriamente por la pantalla, vamos a proponer un ejemplo de muerte muy sencillo: Si uno de los enemigos sale fuera de los lmites de pantalla, entonces morir.
Con esto evitaremos que los enemigos salgan de pantalla y se vayan hasta el infinito, ya que si se diese ese caso estaran consumiendo memoria y recursos del PC innecesariamente. Para matar a los enemigos haremos que utilicen la instruccin BREAK; La instruccin BREAK; no mata directamente al proceso, pero hace que ste abandone su bloque LOOP-END, y como despus del LOOP-END no hay ms instrucciones el proceso deja de ejecutar instrucciones y muere. Es una forma relativamente sencilla y limpia de matar procesos. El cdigo a aadir en nuestro enemigo, dentro de su bloque LOOP-END, sera el siguiente: IF ( x < 0 ) BREAK; ELSIF ( x > 800 ) BREAK; ELSIF ( y < 0 ) BREAK; ELSIF ( y > 600 ) BREAK; END //Si sale de la pantalla por la izquierda //Abandona el LOOP y muere //Si sale de la pantalla por la derecha //Abandona el LOOP y muere //Si sale de la pantalla por arriba //Abandona el LOOP y muere //Si sale de la pantalla por abajo //Abandona el LOOP y muere //Fin de las condiciones
Otra forma que ofrece Bennu para conseguir el mismo resultado sera la siguiente, haciendo uso de los operadores lgicos, en este caso OR, que sirve para activar una condicin cuando se cumpla una cualquiera de sus subcondiciones: IF ( ( x < 0 ) OR ( x > 800 ) OR ( y < 0 ) OR ( y > 800 ) ) BREAK; END De la misma forma que OR, podemso usar el operador lgico AND, que servira para activar una condicin cuando se cumplan t todas sus subcondiciones. Aunque es algo que ya iremos viendo a medida que avance el temario. Tema 10 Un Matamarcianos Pg. 2
10.3 Deteccin de colisiones entre procesos El funcionamiento de nuestro matamarcianos ser as de sencillo: Si un enemigo colisiona con nuestro protagonista, entonces nuestro protagonista morir. Si un enemigo colisiona con un disparo, entonces el enemigo morir.
Para ello haremos uso de la funcin collision(); cuya sintaxis es la siguiente: collision ( type <nombre_del_proceso> ) La expresin anterior activar un condicional si el proceso que la ejecuta est colisionando en pantalla con cualquier proceso del tipo dado por <nombre_del_proceso>. Por ejemplo, para que los enemigos mueran al colisionar con un proceso de tipo disparo deberan ejecutar el siguiente cdigo dentro de su bloque LOOP-END: IF ( collision ( type disparo ) ) BREAK; END
10.4 Generacin aleatoria de enemigos Hemos definido el proceso enemigo, pero no lo hemos invocado. De momento haremos que sea el protagonista quien genere aleatoriamente enemigos en el escenario, no es la mejor solucin pero s la ms sencilla. Para ello el protagonista debera ejecutar las siguientes instrucciones: IF ( rand ( 1 , 10 ) == 1 ) enemigo ( ) ; END //Si un dado de 10 caras saca como resultado 1 //Invocamos un enemigo //Fin del condicional
Y para que los enemigos aparezcan aleatoriamente en las esquinas de la pantalla podemos aadir estas instrucciones iniciales, antes de su bloque LOOP-END: x = rand ( 0 , 1 ) * 800; y = rand ( 0 , 1 ) * 600; //Su posicin x podr valer o bien 0 o bien 800 //Su posicin y podr valer o bien 0 o bien 600
Nota que el carcter '*' (Asterisco) es el signo de multiplicacin en Bennu. Tema 10 Un Matamarcianos Pg. 3
11. Variables I Has aprendido a crear un matamarcianos y ya deberas tener base suficiente como para explotar tu imaginacin y aadir otros elementos como explosiones, distintos enemigos, incluso un segundo protagonista para que se trate de un juego para 2 jugadores, siempre que cuentes con los grficos necesarios. Pero todava nos queda muchsimo por descubrir del lenguaje Bennu. En este tema vamos a aprender a tratar conceptos un poco ms abstractos de un videojuego, generalmente valores numricos diversos como pueden ser la puntuacin o los puntos de vida de un determinado proceso.
11.1 Concepto de variable Una variable es un pedazo de la memoria de nuestro PC en el que podemos almacenar un valor, generalmente numrico, aunque ms adelante veremos otras posibilidades. El ejemplo ms claro son los aspectos bsicos de un proceso en Bennu como graph, size, etc. No son ms que variables, simples nmeros que se almacenan en la memoria de nuestro PC y a los que podemos hacer referencia con su nombre. Todos los aspectos bsicos de un proceso en Bennu que hemos visto hasta ahora son tiles para crear un videojuego bsico, pero no nos permiten recrear aspectos ms complejos como la puntuacin o los puntos de vida. Para ello necesitaremos crear nuevas variables, como veremos a continuacin.
11.2 mbito de una variable Como ya hemos dicho, una variable puede almacenar datos como la puntuacin o los puntos de vida. Es importante que observes que se trata de valores que se comportarn de manera muy distinta si tenemos en cuenta que la puntuacin es un valor nico, mientras que cada enemigo puede tener un valor de vida distinto...
Tema 11 Variables I
Pg. 1
Para salvar estas dificultades se utiliza el mbito de las variables, que por ahora distinguiremos entre global o privado, segn la siguiente definicin: GLOBAL: Una variable tiene este mbito cuando almacena un valor nico y comn para todos los procesos del videojuego. El caso ms claro es la puntuacin del juego, ya que hay slo una zona de memoria donde se almacena ese dato. PRIVATE: Una variable tiene este mbito cuando almacena un valor que puede ser distinto para distintos procesos. El caso ms claro son los puntos de vida, ya que puede haber varios enemigos y cada uno tendr una zona de memoria donde almacenar su valor de vida. Este concepto nos permitir inventar nuevas variables a las que podremos dar el nombre y los valores iniciales que queramos.
11.3 Variables GLOBAL Las variables GLOBAL se incluyen en la zona de cdigo que se sita al principio del programa, justo debajo de la sentencia PROGRAM y antes de la primera sentencia BEGIN. Por ejemplo, para aadir una variable encargada de almacenar la puntuacin, que tenga un valor inicial 0, nuestro programa quedara as: PROGRAM videojuego; INCLUDE "DLL\import.prg"; GLOBAL puntos=0; ... BEGIN set_mode(800,600,32,MODE_WINDOW); END //Establecemos el modo de vdeo //Incluye las DLL necesarias
Tema 11 Variables I
Pg. 2
Una vez incluida una variable GLOBAL, cualquier proceso es capaz de modificar su valor como si se tratase de cualquiera de sus aspectos bsicos. Ejemplos de uso de la variable GLOBAL puntos seran los siguientes: Puntos=100; puntos=puntos+1; IF (puntos>1000) BREAK; END Asigna el valor 100 a la variable puntos. Suma 1 al valor de la variable puntos. Si la variable puntos almacena un valor mayor que 100 el proceso muere.
11.4 Variables PRIVATE Las variables PRIVATE se incluyen en la zona de cdigo entre el PROCESS que encabeza la declaracin de un proceso y el BEGIN que indica el comienzo de sus instrucciones. Por ejemplo, para aadir una variable encargada de almacenar un valor de 100 en la vida inicial de cada enemigo, nuestro proceso enemigo quedara as: PROCESS enemigo(); PRIVATE vida=100; BEGIN END Una vez incluida una variable PRIVATE, cada proceso es capaz de modificar su valor como si se tratase de cualquiera de sus aspectos bsicos, siendo posible que distintos procesos conserven distintos valores. Ejemplos de uso de la variable PRIVATE puntos seran los siguientes: vida=100; vida=vida-1; IF (vida<=0) BREAK; END Asigna el valor 100 a la variable vida del proceso. Suma 1 al valor de la variable vida del proceso. Si la variable vida del proceso almacena un valor menor o igual que 0 el proceso muere.
Tema 11 Variables I
Pg. 3
11.5 Aplicando GLOBAL y PRIVATE Crea la variable GLOBAL que almacena la puntuacin y la variable PRIVATE que almacena la vida de cada proceso enemigo. Haz el uso adecuado de estas variables para conseguir que los enemigos sean capaces de resistir 10 golpes antes de morir, y que antes de morir sumen 1 a la puntuacin. Un ejemplo bsico de enemigo que haga uso de las variables sera: PROCESS enemigo(); PRIVATE vida=10; BEGIN LOOP ... IF ( collision (type disparo) ) vida = vida -1; END IF ( vida <= 0 ) puntos = puntos + 1; BREAK; END ... FRAME; END END No olvides crear la variable puntos en la seccin GLOBAL, el hecho de crear una nueva variable se denomina formalmente declarar, y en adelante hablaremos de declarar nuevas variables, ya sean GLOBAL o PRIVATE cuando sea necesario. Observars que, a pesar de tener 10 puntos de vida, los enemigos mueren demasiado rpido... Esto se debe a que detectan la colisin durante varios FRAMES, y en cada uno de ellos restan vida puesto que es lo que hemos impuesto. Ms adelante veremos cmo resolver ese tipo de problemas.
Tema 11 Variables I
Pg. 4
12. Variables II Ya sabes cmo declarar nuevas variables en tu videojuego, pero todava tenemos muchsimo que aprender sobre ellas y sus aplicaciones. Habrs observado que el comportamiento de tus enemigos no tiene demasiado sentido, ya que aparecen en las esquinas de la pantalla y su movimiento aleatorio no suele ser capaz de hacerlos llegar al centro de la misma. En este tema vamos a hacer uso de las variables para conseguir que los enemigos vayan siempre al centro de la pantalla.
12.1 Una variable tiene el significado que nosotros queramos darle Todos los aspectos bsicos de un proceso en Bennu como pueden ser graph, size o angle tienen un significado concreto: Indican su nmero de grfico dentro del FPG, su tamao en tanto por ciento, su ngulo en milsimas de grado, etc. En cambio las nuevas variables que creamos no tienen un significado por s mismas sino que debemos ser nosotros, a travs del cdigo, los que les demos un significado concreto, por ejemplo haciendo que sucedan determinadas cosas cuando una de las variables que hemos declarado tome un determinado valor. Recuerda que en el tema anterior hemos declarado la variable PRIVATE vida y nos ha servido para lograr que los enemigos sean capaces de resistir varios golpes antes de morir... Observa que mediante el lenguaje Bennu cada vez nos resulta ms fcil expresar comportamientos ms y ms complejos. Ahora vamos a crear una nueva variable PRIVATE que llamaremos direccin (Sin acento), y nos servir para indicar dnde aparecer cada nuevo enemigo y hacia dnde debe moverse. Por qu la direccin es PRIVATE y no GLOBAL? Pues porque cada enemigo podr tener una direccin distinta, recuerda que el mbito GLOBAL slo nos sirve para declarar un valor que resulte nico para todos los procesos del videojuego.
Tema 12 Variables II
Pg. 1
12.2 Ejemplo del significado que le podemos dar a una variable Haciendo uso de la funcin rand(), vamos a hacer que la nueva variable PRIVATE direccion de nuestro enemigo tome un valor inicial aleatorio entre 1 y 4 antes de entrar en el bloque LOOP-END. Consultando el valor obtenido, el enemigo decidir su posicin inicial y la direccin hacia la que se debe mover, segn la siguiente tabla: direccion == 1 direccion == 2 direccion == 3 direccion == 4 El enemigo aparece en la esquina superior izquierda y debe moverse hacia la esquina inferior derecha. El enemigo aparece en la esquina superior derecha y debe moverse hacia la esquina inferior izquierda. El enemigo aparece en la esquina inferior derecha y debe moverse hacia la esquina superior izquierda. El enemigo aparece en la esquina inferior izquierda y debe moverse hacia la esquina superior derecha.
Para aplicar este concepto, el cdigo del enemigo deber cambiar ligeramente a la hora de decidir su posicin inicial (Antes del bloque LOOP-END) as como deber modificar su comportamiento al desplazarse por la pantalla (Dentro del bloque LOOPEND).
Tema 12 Variables II
Pg. 2
Un ejemplo de utilizacin de la variable direccin sera el siguiente: PROCESS enemigo() PRIVATE vida=10; direccin; BEGIN graph=50; direccion=rand(1,4); IF ( direccion == 1 ) x = 0; y = 0; ELSIF ( direccion == 2 ) x=800; y=0; ELSIF ( direccion == 3 ) x=800; y=600; ELSIF ( direccion == 4 ) x=0; y=600; END //Nos posicionamos abajo a la izquierda //Alternativa: Si la direccin es 4 //Alternativa: Si la direccion es 3 //Nos posicionamos abajo a la derecha //Alternativa: Si la direccin es 2 //Nos posicionamos arriba a la derecha //Toma un valor de direccin aleatorio entre 1 y 4 //Condicin: Si la direccin es 1 //Nos posicionamos arriba a la izquierda //Datos privados de cada proceso enemigo //vida con un valor inicial de 10
Tema 12 Variables II
Pg. 3
LOOP IF ( direccion == 1 ) x = x + 4; y = y + 4; ELSIF ( direccion == 2 ) x = x - 4; y = y + 4; ELSIF ( direccion == 3 ) x = x - 4; y = y - 4; ELSIF ( direccion == 4 ) x = x + 4; y = y - 4; END FRAME; END END //Alternativa: Si la direccin es 4 //Nos desplazamos hacia arriba a la derecha //Alternativa: Si la direccin es 3 //Nos desplazamos hacia arriba a la izquierda //Condicin: Si la direccin es 1 //Nos desplazamos hacia abajo a la derecha //Alternativa: Si la direccin es 2 //Nos desplazamos hacia abajo a la izquierda
Tema 12 Variables II
Pg. 4
13. Textos I Hasta ahora hemos aprendido a mostrar procesos grficos por pantalla y a modificar su comportamiento cambiando los valores de sus variables. Un videojuego tiene otros elementos adems de los procesos grficos, uno de los ms importantes son los textos que se muestran por pantalla, ya que entre otras cosas, nos permiten ver los valores que almacenan algunas variables en cada momento: El nmero de grfico, la posicin, los puntos de vida... En este tema aprenderemos a escribir textos bsicos en pantalla de una forma muy sencilla.
13.1 Fuente de texto La fuente de texto es el tipo de letra que se utilizara para imprimir por pantalla los textos de nuestro videojuego. Lo primero que vamos a necesitar es disear nuestra propia fuente de texto. Es muy sencillo si utilizamos el programa FNT Edit, incluido en el Bennupack. Este programa nos permite seleccionar cualquiera de las fuentes del sistema, como por ejemplo Arial, Verdana u otras que hayamos instalado. Nos permite seleccionar el tamao de la fuente, su color, su tipo y color de sombra y su tipo y color de borde, permitiendo as disear prcticamente cualquier tipo de letra, bien sea para un pequeo marcador de puntuacin o para el ttulo de presentacin del videojuego.
Tema 13 Textos I
Pg. 1
Su interfaz permite generar la fuente para previsualizarla, y una vez hayas terminado, puedes pulsar Guardar para guardarla en formato .fnt, que es el mas sencillo soportado por Bennu. Genera unas cuantas fuentes y guardarlas con nombres apropiados, como por ejemplo fuente_texto.fnt, fuente_titulo.fnt, etc. Al igual que el FPG de nuestro videojuego esta guardado en un subdirectorio llamado images, conviene que las fuentes tambin se guarden en un subdirectorio. En el caso del videojuego de ejemplo de este tema el subdirectorio se llama fonts y a la hora de cargar el/los archivos de fuente tendremos que tener en cuenta que se encuentran dentro de ese subdirectorio.
13.2 Cargar fuentes de texto con Bennu Antes de poder utilizar una de nuestras fuentes, es necesario cargarla en el videojuego. Es algo que haremos desde el cdigo haciendo uso de la funcin de Bennu load_fnt() y una variable GLOBAL a la que llamaremos fuente. Para declarar la variable: GLOBAL int fuente; //Crea una variable llamada fuente, el prefijo int indica que ser un nmero, aunque no //es obligatorio ya que las nuevas variables por Bennu son por defecto nmeros
Para cargar la fuente utilizaremos la instruccin load_fnt(), as que recuerda que las instrucciones siempre deben encontrarse dentro de un bloque BEGINEND. En nuestro caso usaremos el primer bloque BEGIN-END de nuestro videojuego y cargaremos la fuente justo despus de cargar el fpg de esta forma: BEGIN fuente = load_fnt ( fonts/fuente_texto.fnt ); END Esto no producir todava ningn efecto visual en nuestro videojuego, pero a partir de ahora podremos hacer uso de la fuente para escribir lo que queramos, como veremos a continuacin. Fjate que la sintaxis de la carga de fuentes es muy sencilla. Formalmente se dice que load_fnt() es una funcin a la que se le pasa como parmetro la ruta de un archivo .fnt entre comillas, y nos retorna un identificador numrico, un entero. Tema 13 Textos I Pg. 2
En el manual de referencia se especifica formalmente su sintaxis abreviada as: int load_fnt ( string ) Un string es un texto entre comillas, pero no te preocupes demasiado por ese detalle porque es algo que tendremos tiempo de ver a fondo mas adelante.
13.3 Escribiendo textos en la pantalla Aunque se trata de una operacin muy sencilla, hay que tener en cuenta una serie de cosas a la hora de escribir en la pantalla: Por ejemplo no haremos lo mismo para escribir un texto fijo que para escribir el valor de una puntuacin que podr cambiar de valor a medida que avance el juego, ahora mismo veremos por que. La funcin write() de Bennu sirve para escribir textos fijos, aunque en el prximo tema le daremos tambin otra utilidad interesante. Para escribir un texto necesitamos especificar una serie de cosas, que sern los parmetros de write, estas cosas son: N de parmetro Parmetro 1 Tipo de parmetro Ejemplos
La variable donde hemos almacenado la fuente de texto que queremos utilizar. fuente_texto Podemos poner un 0 si queremos utilizar la fuente por defecto de Bennu, aunque fuente_presentacion resulta poco agradable. 0 Coordenada horizontal donde aparecer el texto, especificada en pxeles como si 0 se tratase de la x de un proceso. 400 800 Coordenada vertical donde aparecer el texto, especificada en pxeles como si se tratase de la y de un proceso. 0 300 600
Parmetro 2
Parmetro 3
Parmetro 4
Valor de centrado, que nos indica si el texto estar alineado a izquierda, centro o Puede ser de 0 a 8, derecha, tanto horizontal como verticalmente, respecto de la posicin x,y que le ver el hemos dado. funcionamiento en la imagen al pie de la tabla. El texto que queremos escribir, entre comillas. Hola Puntos: Has muerto
Parmetro 5
Tema 13 Textos I
Pg. 3
Alineacin horizontal
Izquierda
Alineacin vertical
Centro
1 4 7
Derecha
2 5 8
0 3 6
Para poner en prueba todo esto, especialmente los diferentes valores de centrado, ya que es el concepto que al principio parece ms complicado, vamos a hacer un pequeo cambio en nuestro videojuego. Recuerdas que los enemigos hasta ahora aparecan en las esquinas? Pues vamos a poner en cada esquina un texto de advertencia. La coordenada de cada esquina la daremos de forma absoluta, pero con el valor de centrado correcto para que el texto cuelgue adecuadamente y no se salga de la pantalla. Todava no lo sabemos todo sobre el manejo de textos, de momento ten en cuenta que consumen ms recursos de lo que parece, y por eso debemos evitar poner instrucciones de escritura de textos dentro de un LOOP. Pondremos las siguientes instrucciones dentro del primer bloque BEGIN-END, por ejemplo despus de cargar la fuente: BEGIN write write write write END Puedes poner algn texto ms si lo deseas y as practicar un poco mejor con el valor de centrado. Es ms sencillo de lo que parece. ( fuente_texto, ( fuente_texto, ( fuente_texto, ( fuente_texto, 0, 800, 800, 0, 0, 0, 600, 600, 0, 2, 8, 6, Enemigos ); Enemigos ); Enemigos ); Enemigos );
Tema 13 Textos I
Pg. 4
14. Textos II La escritura de textos fijos resulta interesante, pero no nos servir de gran ayuda si no somos capaces de escribir informacin sobre valores de variables como puede ser la puntuacin o la vida. Vamos a aprender un poco ms sobre textos y ya estaremos casi listos para escribir cualquier tipo de informacin por pantalla.
14.1 Escribir valores de variables en pantalla La funcin write_var() tiene un funcionamiento prcticamente idntico a la funcin write() que hemos estudiado en el tema anterior. La nica diferencia es su ltimo parmetro, ya que en lugar de tratarse de una cadena de texto entre comillas, en ese parmetro podemos poner el nombre de cualquier variable de nuestro videojuego, y ste ser escrito en pantalla. Qu ocurre si el valor de la variable cambia? Pues no hay ningn problema, ya que write_var() es una funcin que se encarga de actualizar automticamente el valor, sin mayores complicaciones. Vamos a sacarle un poco ms de partido al uso de los valores de centrado. Imagina que quieres colocar en la parte superior de la pantalla un texto fijo PUNTOS y a su derecha el valor de la variable puntos. Observa que el valor de la variable puede crecer ms y ms a medida que avanza el juego, y no sera en absoluto deseable que el nmero pase a sobreescribir el texto PUNTOS. El valor de centrado nos ayudar a evitar ese tipo de cosas si centramos ambos textos en el mismo punto pero con distinta alineacin horizontal, por ejemplo as: BEGIN write write_var ... END ( fuente_texto, ( fuente_texto, 400, 400, 0, 0, 2, 0, PUNTOS: ); puntos );
14.2 Una especificacin ms formal de la sintaxis de write() En el tema anterior hemos comentado que la escritura de textos consume muchos recursos y no es recomendable incluir instrucciones write() dentro de un bloque LOOP-END.
Tema 14 Textos II
Pg. 1
En algunas ocasiones puede interesarnos tener un texto capaz de moverse por la pantalla. En esos casos no nos queda ms remedio que utilizar continuamente la funcin write() con distintas coordenadas x,y dentro de un LOOP, pero a cada nueva llamada a la funcin write() necesitaremos borrar el texto escrito anteriormente... Para eso write() nos retorna un identificador numrico del texto, que podemos pasar como parmetro a la funcin delete_text() para borrar el texto escrito. Formalmente la sintaxis de write() y delete_text() es la siguiente: int write ( int, int, int, int, string) delete_text (int ) // write retorna un identificador numrico // delete_text recibe el identificador numrico retornado por write()
El funcionamiento es algo especial, ya que la escritura debe realizarse antes de la sentencia FRAME; y el borrado despus, para asegurar que el texto se borra despus de haber sido mostrado, y no antes. Para probar todo esto vamos a crear un nuevo proceso encargado de mostrar el nombre de nuestro videojuego de izquierda a derecha de la pantalla: PROCESS titulo() PRIVATE int texto; BEGIN x = 0; y = 300; LOOP x = x + 10; IF ( x > 1600 ) BREAK; END texto = write ( fuente_texto, x, y, 5, BIENVENIDO A MI VIDEOJUEGO ); FRAME; delete_text ( texto ); END END Observa que comprobamos cuando la coordenda x es suficiemente grande como para que el texto haya salido por la derecha de la pantalla, y es entonces cuando el proceso muere para no seguir escribiendo ms el ttulo.
Tema 14 Textos II
Pg. 2
15. Tipos de Dato I Por ahora ya sabemos lo suficiente sobre el muestreo de textos por pantalla, aunque por supuesto a falta de practicar un poco. Vamos a cambiar un poco de temtica y vamos a ver otros aspectos avanzados del lenguaje que nos permitirn hacer cosas ms complicadas. Hasta ahora slo hemos visto conceptos sobre variables cuyos valores eran numricos. Si haces memoria recientemente hablamos de aplicar el prefijo int a la hora de definir una variable, con ello indicbamos que en ella slo queramos guardar valores enteros (integer), y por supuesto no siempre ser suficiente con ello, de eso trata este nuevo tema.
15.1 Tipos de dato predefinidos Toda variable en Bennu lleva asociado el tipo de dato que se va a almacenar en ella. Si no lo especificamos, la variable slo servir para almacenar enteros, pero por supuesto hay otras cosas que podemos guardar en una variable, como por ejemplo un texto o un nmero real. Bennu y la mayora de los lenguajes de programacin vienen con una serie de tipos de dato predefinidos que podemos utilizar con diferentes fines. En el caso particular de Bennu, para especificar el tipo de dato de una determinada variable basta con aadirle un prefijo determinado a la hora de declararla.
Pg. 1
Los prefijos que podemos aadir para declarar variables con los tipos de dato predefinidos en Bennu son los siguientes: Prefijo int Utilidad del tipo de dato Almacena un nmero entero, es decir, nmeros positivos y negativos includo el cero, sin decimales. Es el tipo de dato por defecto de toda variable definida en Bennu, as que si omitimos este prefijo la variable que declaremos ser de tipo int. Para qu sirve entonces aadir este prefijo? Pues para aclararnos ms fcilmente a la hora de leer nuestro cdigo, es algo que debemos tener en cuenta a largo plazo, la legibilidad de nuestro cdigo. float Almacena un nmero real, es decir, nmeros positivos y negativos, con o sin decimales. Un float puede almacenar cualquier tipo de dato int, pero no al revs. Si queremos tener una variable de tipo float s que es obligatorio aadir el prefijo float. Lo ms probable es que para los primeros videojuegos que programemos no tendremos necesidad de utilizar este tipo de dato. char Almacena un carcter de teclado, ya sea alfanumrico, un char letra = 'a'; smbolo o incluso un espacio, la tecla de salto de lnea o la char otra_letra = 'w'; de borrado. Los caracteres almacenables pertenecen al denominado cdigo ASCII y son 256 en total, aunque cambian segn la distribucin de teclas. Para asignar un valor a las variables de este tipo es necesario escribirlo entre comillas simples ( ' ). No debes darle mayor importancia a este tipo de dato ya que su utilidad actual es muy limitada. string Almacena un texto de cualquier longitud. Es como una serie de char concatenados y este tipo de dato s que nos resultar til para nuestro videojuego. Para asignar un texto a las variables de este tipo es necesario escribirlo entre comillas dobles ( ). Tema 15 Tipos de Dato I Pg. 2 string nombre = Secret of Evermore; string texto = GAME OVER; float porcentaje = 0,0; float precio = 19,95; Ejemplo de declaracin int puntos = 0; int vida = 100;
Debes tener en cuenta que seguimos hablando de variables, idnticas a todas aquellas con las que hemos trabajado hasta ahora: Pueden ser GLOBAL y PRIVATE, podemos asignarles un valor inicial o no, debemos aadir siempre al final de su declaracin un punto y coma ( ; ), etc. Prueba a aadir unas cuantas variables adicionales a tu videojuego dentro de sus secciones GLOBAL y PRIVATE y experimenta con la funcin write_var() para comprobar cmo almacenan valores que ya no son simples enteros. Es muy importante que compruebes que el compilador no muestra ningn error, eso significa que el lenguaje Bennu acepta estas nuevas expresiones que estamos incluyendo.
15.2 Arrays o vectores Aunque su nombre no resulta muy ilustrativo y en la primera toma de contacto se tiene a pensar que un array o vector es algo muy complicado, se trata de un aspecto muy sencillo del lenguaje que facilita algunas tareas de programacin. Hasta ahora declarbamos las variables de 1 en 1, esto es, cada vez que queramos tener una variable donde almacenar un valor necesariamente tenamos que escribir la lnea de cdigo correspondiente. Imagina que queremos que los enemigos de nuestro videojuego puedan tener 10 valores de puntos de vida distintos, no simples valores aleatorios, sino valores fijos como por ejemplo: 10, 25, 50, 100, 250, 750, etc. La serie de nmeros anterior podra generarse a partir de nmeros aleatorios dados por la funcin rand(), aunque si lo piensas vers que llevara algunas complicaciones adicionales. Para almacenar esa serie o lista de nmeros podemos utilizar un array o vector (En adelante lo llamaremos siempre vector). Un vector no es ms que una lista de variables, tantas como queramos, cada una de ellas con su valor asociado. En el caso de la lista anterior necesitamos una lista de 10 nmeros enteros, cada uno con un valor distinto. Observa que el tipo del dato a almacenar es un simple entero (Prefijo int), la nica diferencia es que queremos una variable que no almacene un nico entero sino 10 en nuestro caso.
Pg. 3
Podemos declarar una lista de 10 enteros en la seccin GLOBAL de nuestro videojuego as: GLOBAL int puntos_de_vida [ 10 ]; Observa que al final de la declaracin hemos aadido, entre corchetes, el nmero de variables que queremos declarar. La sintaxis formal de la declaracin de un vector, sea cual sea su tipo de dato, es la siguiente: <tipo_del_dato> nombre_del_vector [ <longitud_del_vector> ] ; Donde el tipo de dato puede ser cualquiera de los vistos anteriormente como int, float o string, y la longitud del vector puede ser cualquier nmero positivo, teniendo en cuenta que cuanta mayor sea la longitud mayor ser le memoria que consuma nuestro videojuego. Por ejemplo, si tenemos en cuenta que un nmero entero ocupa 4 bytes en memoria, un vector de 1024 enteros ocupara 4096 bytes, que son 4 kilobytes. Fjate que es un valor despreciable teniendo en cuenta que las memorias actuales se miden en gigabytes. Para almacenar en nuestro vector los valores de vida de los enemigos mencionados anteriormente podemos hacerlo en el mismo momento de su declaracin de la siguiente forma: GLOBAL int puntos_de_vida [ 10 ] = ( 10, 25, 50, 100, 250, 750, 1000, 2000, 5000, 10000 ); Fjate que los valores que almacenamos en la lista van separados por comas y encerrados entre parntesis. Qu ocurrira si almacenamos ms nmeros de los que hemos declarado en la lista? Simplemente evita hacerlo, ya que podra provocar errores de ejecucin en nuestro videojuego, ms adelante veremos por qu. Slo nos queda un detalle: Cmo podemos hacer que cada enemigo tenga una vida distinta de entre los valores almacenados en nuestro vector?
Pg. 4
Para ello necesitamos acceder a los datos del mismo y asignarlos a su vida. Lo haremos con una simple instruccin de asignacin que podremos incluir dentro de su bloque BEGIN END como cualquier otra instruccin. Por ejemplo: Instruccin vida = puntos_de_vida [ 0 ]; Utilidad Asignara a la vida de nuestro enemigo el primero valor del vector de puntos de vida, en nuestro caso el valor que almacena es 10. Asignara a la vida de nuestro enemigo el sptimo valor del vector de puntos de vida, en nuestro caso el valor que almacena es 2000. Asignara a la vida de nuestro enemigo un valor aleatorio, entre la posicin 0 y la posicin 9 de nuestro vector de puntos de vida. Aclaraciones La posicin 0 es la primera posicin de todo vector declarado. Para un vector de longitud 10 podemos acceder a sus posiciones desde la 0 hasta la 9. No olvides que para un vector de longitud N sus posiciones van desde la 0 hasta la N-1, es algo que siempre debers tener en cuenta. S, podemos utilizar la funcin rand() para este tipo de propsitos, al fin y al cabo es una funcin muy sencilla que simplemente devuelve un valor aleatorio dentro del intervalo dado.
vida = puntos_de_vida [ 6 ];
Y con lo aprendido sobre muestreo de textos tambin podramos hacer que la vida de cada enemigo aparezca sobre el mismo, Te atreves a intentarlo?.
Pg. 5
16. Tipos de Dato II En el tema anterior aprendimos a crear variables de tipo vector, que no son otra cosa que listas de variables predefinidas como int, float o string, con la longitud que queramos. En este tema vamos a continuar con los tipos de dato, aunque vamos a cambiar un poco el concepto, ya que ahora aprenderemos a crear nuestros propios tipos de dato en los que podremos almacenar informacin ms compleja, como podran ser las estadsticas completas del protagonista de un RPG, todo en una nica variable.
16.1 Estructuras o types Bennu nos ofrece la posibilidad de agrupar varias variables, sean del tipo que sean, incluso vectores, para crear un nuevo tipo de dato. Un tipo de dato con estas caractersticas se denomina estructura o type. En un type podramos incluir diversos aspectos, como por ejemplo una string con el nombre de un enemigo, un int con sus puntos de vida, etc. La declaracin de un type se realiza antes de la etiqueta PROGRAM, esto es, al principio del cdigo, y su sintaxis es la siguiente: TYPE <nombre_del_tipo> <tipo1> <variable1> ; <tipo2> <variable2> ; END Una vez declarado un type, el nombre del tipo puede utilizarse como prefijo a la hora de declarar variables, al igual que int, string y el resto de tipos de dato predefinidos. Es importante que veas la diferencia entre declarar un nuevo tipo de dato y declarar una variable asociada al nuevo tipo de dato, es un error comn pensar que una vez declarado el type ya tenemos una variable, pero no es as, para tener una variable tenemos que declararla dentro de GLOBAL o PRIVATE, como hemos hecho hasta ahora. Vamos a crear un nuevo tipo de dato donde almacenar unas estadsticas bsicas para nuestro protagonista. Podemos dar el nombre que queramos al nuevo tipo de dato, aunque es recomendable que empiece por tp_, ya que esto nos ayudar a distinguir el nombre del tipo de dato del nombre de las variables de ese tipo.
Tema 16 Parmetros
Pg. 1
TYPE tp_estadisticas string nombre; int vida; int velocidad; END Observa que las variables que componen el type se declaran exactamente igual que el resto de variables que hemos definido hasta ahora en las secciones GLOBAL y PRIVATE. Las variables que componen un type se denominan formalmente campos, y pueden ser vectores o incluso otros types definidos anteriormente. Por supuesto, tambin podramos asignar un valor por defecto a los campos, por ejemplo: TYPE tp_estadisticas string nombre = Sonic ; int vida = 100 ; int velocidad = 4 ; END Recuerda que hemos definido un nuevo tipo de dato, pero todava no tenemos ninguna variable de ese tipo. Podemos aadirla en la seccin PRIVATE de nuestro protagonista, quedara as: PROCESS protagonista () PRIVATE tp_estadisticas estadisticas; BEGIN END Cmo accedemos a los campos de un type? Si recuerdas lo visto en temas anteriores sobre la variable father de todo proceso en Bennu, utilizbamos el operador punto ( . ) para acceder a los distintos campos. La variable father es un type, y en todo type se accede as a sus campos. <nombre_de_la_variable> . <nombre_del_campo>
Tema 16 Parmetros
Pg. 2
16.2 Haciendo uso de nuestro nuevo tipo de dato Modifica el protagonista de tu videojuego para que haga uso de los campos de la variable estadsticas y reduzca su campo vida al colisionar con los enemigos y se desplace a la velocidad que marca el campo velocidad. Tambin puedes hacer uso de la funcin write_var() sobre estadisticas.vida para tener un marcador en pantalla donde mostrar sus puntos de vida restantes. Tambin puedes usar adecuadamente las funciones write_var() y delete_text() para conseguir que el nombre de Sonic permanezca siempre situado sobre nuestro protagonista, Por supuesto haciendo uso del campo nombre de nuestra variable estadisticas. Para trabajar con ms detenimiento todo lo aprendido sobre tipos de dato tambin puedes asociar a cada enemigo un TYPE con su nombre y sus puntos de vida, de forma que antes de comenzar a ejecutar su bloque de instrucciones dentro de LOOP END asigne aleatoriamente su nombre y sus puntos de vida, y los muestre en pantalla. Para ello puedes reaprovechar el TYPE tp_estadisticas o bien crear un tipo de dato nuevo.
Tema 16 Parmetros
Pg. 3
17. Parmetros Durante el tema anterior pudiste comprobar la potencia que ofrecen las variables en Bennu: Comprobaste que pueden almacenar datos con diversas caractersticas, incluso grandes cantidades de ellos, el principal lmite de todo ello es nuestra imaginacin. El tema que viene a continuacin pretende darle todava ms utilidad al concepto de variable en Bennu, puesto que aprenderemos a ofrecer a los distintos procesos los valores que queremos que tomen para su ejecucin. En primera instancia podremos comunicar a un proceso aspectos bsicos como cul es su grfico o su posicin a la hora de invocarlo, pero tambin veremos que podemos ofrecerle informacin ms compleja como las estadsticas de fuerza, agilidad o inteligencia que queremos para l, de la misma forma que se hara en un RPG.
17.1 Paso de parmetros Como ya sabes, la primera lnea de la declaracin de cualquier proceso en Bennu tiene la siguiente estructura: PROCESS nombre_del_proceso ( ) ... BEGIN END El parntesis de apertura y cierre que aadimos junto al nombre del proceso tiene una utilidad especial, ya que en l podemos enumerar una serie de parmetros que el proceso recibir a la hora de ser invocado. Por ejemplo, en los parmetros podremos indicar que el proceso va a recibir un entero indicando su posicin inicial o una string indicando su nombre. Cul es la utilidad de todo sto?, pues que gracias a ello, con una nica declaracin de proceso podremos lograr distintos comportamientos segn cules sean los parmetros que reciba a la hora de ser invocado. Para ilustrar todo esto con un ejemplo, vamos a disear un sencillo proceso disparo que recibir como parmetros los valores de grfico inicial y final sobre los cules deber animarse. Para ello indicaremos en la declaracin del proceso que recibiremos 2 valores enteros (De tipo int) a los cuales llamaremos graph_ini y graph_fin. (Debes notar que el nombre de los parmetros lo damos nosotros, vers que son algo muy similar a las variables.)
Tema 17 Parmetros
Pg. 1
La declaracin de nuestro proceso disparo quedara as: PROCESS disparo ( int graph_ini , int graph_fin ) Fjate que los parmetros se aaden con la misma sintaxis con la que se declaraban variables, esto es una gran ventaja, ya que nos permite declarar procesos que reciben parmetros de cualquier tipo. Ahora la invocacin del proceso cambiar, ya que cada vez que lo invoquemos deberemos darle los valores enteros que necesita para graph_ini y graph_fin. Ejemplos de invocacin seran los siguientes: disparo ( 1, 10 ); disparo ( 25, 40 ); ...
17.2 Dando utilidad a los parmetros Antes de darle el uso adecuado a esos nuevos valores graph_ini y graph_fin, comprueba que el programa compila correctamente. Comprobars que la ejecucin no muestra ninguna diferencia de comportamiento, y es que hemos aadido parmetros, pero no hemos incluido dentro del proceso disparo las instrucciones adecuadas que nos permitan sacar provecho de ellos. Lo que queramos conseguir con los nuevos parmetros era algo similar a: Cuando reciba los valores 1 y 10 (Por ejemplo), el dispar se animar dentro de ese intervalo de grficos, mientras que cuando reciba los valores 25 y 40 se animar dentro de ese otro intervalo. Dentro del bloque BEGIN END del proceso disparo podemos referirnos a graph_ini y graph_fin de la misma manera que nos referimos a cualquier variable, de hecho cualquier parmetro de un proceso se comporta como una variable PRIVATE del mismo, para cada proceso invocado puede tener distintos valores.
Tema 17 Parmetros
Pg. 2
PROCESS disparo ( int graph_ini , int graph_fin ) BEGIN graph = graph_ini; //El proceso toma como valor de grfico el dado por el parmetro graph_ini LOOP graph = graph + 1; //El proceso suma 1 al valor de graph para animarse IF ( graph > graph_fin ) //Si graph se hace mayor que el valor de graph_fin graph = graph_ini; //Entonces vuelve a valer lo que vale graph_ini END //Fin de la condicin FRAME; END END Para que comprendas mejor el funcionamiento del cdigo anterior, piensa que invocamos el disparo de la siguiente forma: disparo ( 1 , 10 ); El resultado de la ejecucin del disparo anterior sera equivalente al de este proceso: PROCESS disparo ( ) BEGIN graph = 1; //El proceso toma como valor de grfico el dado por el parmetro graph_ini LOOP graph = graph + 1; //El proceso suma 1 al valor de graph para animarse IF ( graph > 10 ) //Si graph se hace mayor que el valor de graph_fin graph = 1; //Entonces vuelve a valer lo que vale graph_ini END //Fin de la condicin FRAME; END END Observa que simplemente hemos sustituido graph_ini y graph_fin por 1 por 10. De la misma manera, el resultado de la invocacin del proceso disparo pasndole como parmetros los valores 25 y 40 sera el equivalente a sustituir graph_ini y graph_fin por 25 y por 40. Si has entendido esto enhorabuena, ya puedes invocar cualquier disparo sencillo haciendo uso del mismo proceso simplemente cambiando los valores que se le pasan como parmetros.
Tema 17 Parmetros
Pg. 3
18. Scroll Tienes muchsimo que experimentar con todo lo que hemos visto hasta ahora, sin lugar a dudas ya debes tener un buen concepto de en qu consiste el mundo de la programacin. Pero por supuesto nos sigue quedando muchsimo por aprender. Podemos tener miles de procesos distintos en pantalla, ejecutando sus instrucciones y reproduciendo los comportamientos que hemos programado para ellos, pero: Que hay del mundo en el que se mueven? El scroll es un aspecto fundamental para nuestro videojuego y Bennu nos ofrece un soporte excelente para utilizarlo. A continuacin veremos cules son todas sus posibilidades.
18.1 El vector de types scroll[] Por qu hemos esperado hasta el tema 18 para hablar del scroll? No es porque se trate de un tema muy complejo, realmente el uso de un scroll no nos supondr una complicacin adicional y por supuesto ofrecer un aspecto mucho ms sofisticado a nuestro videojuego. Si hemos esperado hasta ahora es porque resulta muy recomendable estar al tanto de todos los conceptos que hemos visto sobre variables antes de ponernos a trabajar con el scroll. El concepto de scroll en Bennu hace un sencillo uso de los vectores y de los types. Bennu nos brinda la posibilidad de tener varios scroll en pantalla. sto nos permitira tener uno para el minimapa, varios para un videojuego en pantalla partida, o cualquier cosa que se nos pueda ocurrir en ese sentido. Un scroll no es ms que un grfico que se desplaza a travs de una determinada regin de pantalla, y Bennu tiene un type asociado para gestionar esta idea. De hecho tiene un vector de types con el que podremos gestionar hasta 10 scroll distintos. Cada uno de esos scroll es una estructura almacenada en cada una de las posiciones de un vector llamado scroll [ ]. Ese vector se comporta como GLOBAL, es decir, es comn a todos los procesos y podemos referirnos a l en cualquier punto de nuestro cdigo. A continuacin vamos a estudiar un poco ms a fondo las diferencias que supone para nuestro videojuego el hecho de que tenga uno o ms scrolls en pantalla.
Tema 18 Scroll
Pg. 1
18.2 Coordenadas absolutas y relativas Hasta ahora mismo habamos visto cmo las coordenadas x,y de cualquier proceso tenan como base la esquina superior izquierda de la pantalla. Esa base resulta muy til cuando trabajamos con procesos cuyo posicionamiento es relativo a la pantalla, Pero qu ocurre si queremos programar un videojuego con scroll en el cual la posicin de nuestros procesos no depende solamente de la pantalla, sino que depende de un gigantesco scroll. Para que comprendas mejor de lo que estamos hablando: Piensa que la coordenada x = 2000 se encuentra fuera de pantalla, pero dentro de un scroll de ms de 10000 pxeles de anchura sera posible que nuestro protagonista se encontrase en esa coordenada, y por supuesto debera seguir mostrndose en el centro de la pantalla. Esto nos sugiere la idea de que podremos mantener centrada una cmara en nuestro proceso protagonista, de forma que siempre permanezca visible en pantalla, sea cual sea su posicin en el scroll. Es algo que vamos a estudiar a fondo.
18.3 Los campos de scroll[] Como hemos mencionado anteriormente, sin necesidad de haber declarado ninguna variable previamente, Bennu tiene predefinida una variable GLOBAL llama scroll que en realidad es un vector que almacena la informacin de 10 scroll distintos. Obviamente por el momento tenemos suficiente con un scroll, as que por ahora trabajaremos exclusivamente con scroll [0], el primero de la lista, hasta que tengamos capacidad suficiente para sacar partido a varios scroll simultneos. Cada una de las componentes de ese vector, en nuestro caso la componente 0, es un type cuyos campos son los siguientes: Campo Scroll [0] . x0 Tipo Int Utilidad Almacena la coordenada x del grfico de scroll que actualmente se muestra en la posicin x = 0 de la pantalla. Si hacemos uso del campo camera no tendr mucho sentido moficar este valor, tan slo lo consultaremos. Almacena la coordenada y del grfico de scroll que actualmente se muestra en la posicin y = 0 de la pantalla. Si hacemos uso del campo camera no tendr mucho sentido moficar este valor, tan slo lo consultaremos.
Scroll [0] . y0
Int
Tema 18 Scroll
Pg. 2
Tipo Int
Utilidad Almacena la coordenada x del grfico de scroll de fondo que actualmente se muestra en la posicin x = 0 de la pantalla, con cierto retraso respecto del grfico principal de scroll. Si hacemos uso del campo camera no tendr mucho sentido moficar este valor, tan slo lo consultaremos. Almacena la coordenada x del grfico de scroll de fondo que actualmente se muestra en la posicin x = 0 de la pantalla, con cierto retraso respecto del grfico principal de scroll. Si hacemos uso del campo camera no tendr mucho sentido moficar este valor, tan slo lo consultaremos. Almacena el identificador del proceso que queremos que permanezca siempre en el centro de la pantalla, generalmente, el protagonista. Cuando modifiquemos las coordenadas x,y de ese proceso el scroll se desplazar. El uso de un proceso centrado sustituye al uso de los campos x0, y0, x1, y1 y en general resulta ms cmodo. Tiene la misma utilidad que el aspecto flags de cualquier proceso en Bennu, slo que aplicado al scroll principal. Tiene la misma utilidad que el aspecto flags de cualquier proceso en Bennu, slo que aplicado al scroll de fondo. Indica el porcentaje de avance en el desplazamiento del scroll de fondo respecto del scroll principal. Su valor por defecto es 200 y consigue una sensacin de profundidad, que es lo que se desea generalmente a la hora de establecer un scroll con fondo.
Scroll [0] . y1
Int
int
18,4 Inicializar un scroll El vector de types tiene una complejidad superior a lo que estamos acostumbrados. Afortunadamente, Bennu nos ofrece algunas funciones para inicializar un scroll sin necesidad de tener que manipular directamente los valores almacenados en scroll [ ]. La principal funcin para el tratamiento de scrolls es start_scroll ( ), cuya sintaxis es la siguiente: int start_scroll ( int , int , int , int , int , int ) Observa en primer lugar que start_scroll ( ) es una funcin que nos retorna un valor numrico, su utilidad es muy importante y la explicaremos al final de este tema. Tema 18 Scroll
Pg. 3
En cuanto a los parmetros que recibe start_scroll ( ), aunque casi siempre los usaremos de la misma manera, tienen la siguiente utilidad: Parmetro 1 Tipo Int Utilidad Indica el ndice del vector scroll [ ] que vamos a inicializar. Recuerda que disponemos de ndices del 0 al 9, pero por seguir un convenio comenzaremos inicializando el scroll 0, as que en general pondremos un 0 como valor para este parmetro. 2 Int Indica el fichero .fpg del que vamos a obtener los grficos del scroll. Actualmente slo tenemos un nico fichero .fpg en el que se encuentran todos nuestros grficos. Para usarlo pondremos un 0 como valor para este parmetro, as haremos uso del ltimo fichero .fpg cargado. Si utilizamos varias funciones load_fpg ( ) y almacenamos su valor de retorno en una variable, podemos poner dicha variable en este parmetro y as leer los grficos de un .fpg distinto. Esto ltimo lo explicaremos a fondo ms adelante. 3 int Indica el nmero de grfico dentro del fichero .fpg que queremos establecer como grfico principal del scroll. Si seguimos el convenio de numeracin explicado en los 2 primeros temas de este tutorial, pondremos 501 como valor para este parmetro, ya que decidimos numerar los scrolls con los valores comprendidos entre el 501 y el 700. 4 int Indica el nmero de grfico dentro del fichero .fpg que queremos establecer como grfico de fondo para el scroll. Para que este grfico sea visible es imprescindible que el grfico de scroll principal tenga transparencia. Si seguimos el convenio de numeracin explicado en los 2 primeros temas de este tutorial, pondremos 502 como valor para este parmetro, ya que decidimos numerar los scrolls con los valores comprendidos entre el 501 y el 700. 5 int Indica la regin de pantalla en la cual inicializaremos el scroll. Todava no hemos creado regiones de pantalla y es algo que veremos ms adelante, as que por el momento pondremos un 0 como valor para este parmetro. As conseguiremos que el scroll se inicialice en la regin de pantalla por defecto, que es la pantalla al completo.
Tema 18 Scroll
Pg. 4
Parmetro 6
Tipo Int
Utilidad El nmero introducido en este parmetro determina opciones que afectan al funcionamiento del scroll. De momento pondremos un 0 como valor para este parmetro y su comportamiento ser normal, pero tambin podemos poner la suma de uno o ms valores siguientes: 1 El scroll principal se repite horizontalmente. 2 El scroll principal se repite verticalmente. 4 El scroll de fondo se repite horizontalmente. 8 El scroll de fondo se repite verticalmente.
Con los parmetros sugeridos en la tabla anterior pondremos en marcha un scroll por defecto, ms adelante podremos modificar los valores segn lo propuesto para conseguir diferentes efectos. En cuanto al valor de retorno de start_scroll ( ), haremos uso del mismo para determinar cul ser el proceso en el cual se centrar la cmara de nuestro scroll, como ya habamos comentado generalmente este proceso ser nuestro protagonista. Todava no hemos explicado a fondo el funcionamiento de la invocacin de procesos en Bennu. Si recuerdas funciones como write ( ) o load_fnt ( ), eran funciones que retornaban un valor numrico con el identificador bien sea del texto o bien de la fuente de texto cargada. Con los procesos podemos proceder de la misma forma, si asignamos a una variable de tipo int la invocacin de un proceso, almacenaremos en la variable el identificador de ese proceso. Por ejemplo si modificamos as nuestro cdigo: GLOBAL int ID_protagonista; BEGIN ID_protagonista = protagonista ( ); END Conseguiremos invocar al protagonista igual que antes y adems almacenaremos en la variable ID_protagonista su identificador de proceso. //Almacenamos en ella el identificador del proceso //Declaramos una variable
Tema 18 Scroll
Pg. 5
Si a continuacin invocamos a la funcin start_scroll ( ), el cdigo quedara as: GLOBAL int ID_protagonista; BEGIN ID_protagonista = protagonista ( ); //Almacenamos en ella el identificador del proceso ID_protagonista = start_scroll ( 0 , 0 , 501 , 502 , 0 , 0 ); END El identificador del protagonista ser el que quedar centrado en la cmara del scroll que acabamos de invocar. //Declaramos una variable
18.5 Establecer coordenadas relativas al scroll A partir de ahora todas las coordenadas x,y de los procesos debern ser relativas al scroll. Bennu nos ofrece una forma sencilla de hacerlo gracias a los aspectos bsicos de todo proceso. Existe un aspecto llamado ctype, es de tipo entero, y debemos realizar la siguiente asignacin a este aspecto en todo proceso cuyas coordenadas deseemos que sean relativas al scroll y no a la pantalla: BEGIN ctype = C_SCROLL; END C_SCROLL es un valor constante que equivale a 1, en breve explicaremos ms a fondo los conceptos de valores constantes en Bennu. En el caso de los textos tenemos una excepcin, ya que las funciones write ( ) y write_var ( ) atienden siempre a coordenadas relativas a pantalla. Convertir una coordenada de proceso a coordenada de pantalla es fcil si recordamos los campos existentes en scroll [ 0 ]: x scroll[0] . X0 //Nos devuelve la x relativa a pantalla que corresponde a un proceso cuya x es relativa a scroll. y scroll[0] . y0 //Nos devuelve la y relativa a pantalla que corresponde a un proceso cuya y es relativa a scroll. Observa el cdigo fuente del videojuego de ejemplo si tienes dudas con las conversiones de coordenadas. Tema 18 Scroll Pg. 6
19. Mdulos Nuestro videojuego cada vez tiene mejor aspecto y estamos muy cerca de poder crear un videojuego comercial como los que se crearon para las consolas de 8 y 16 bits en los 90. La mayor complicacin que tenemos ahora mismo es que nuestro cdigo no para de crecer y cada vez nos resulta ms difcil localizar una determinada lnea de cdigo, aadir una nueva funcin o simplemente entender el cdigo que habamos escrito. El archivo de cdigo puede llegar a tener varios miles de lneas si queremos crear un buen videojuego, y esto es impracticable. Un videojuego de tal envergadura requerira el trabajo de varias personas, Y cmo iban a trabajar varias personas en paralelo sobre un mismo archivo de cdigo? En este tema veremos cmo es posible separar cada uno de los elementos de nuestro videojuego en un archivo .prg independiente. Veremos las ventajas que supone a nivel de legibilidad, facilidad de modificacin y amplicacin e incluso reutilizacin en otros videojuegos. Por supuesto, tambin descubriremos alguna desventaja.
19.1 Concepto de mdulo Un mdulo de cdigo en Bennu es un archivo .prg que ofrece una funcionalidad determinada. En un mdulo podemos incluir un determinado proceso, una serie de variables GLOBAL, una declaracin de TYPES, etc. Al separar un videojuego en mdulos siempre debe resultar un mdulo principal. En nuestro caso nuestro mdulo principal ser el arvhivo videojuego.prg que mantenamos hasta ahora, slo que sus procesos (Bloques PROCESS END), su declaracin de TYPES y sus variables GLOBAL ahora pasarn a formar parte de archivos .prg distintos con nombres representativos, como por ejemplo enemigo.prg, disparo.prg, global.prg y type.prg. Una vez hecho esto slo nos resta incluir en videojuego.prg una serie de instrucciones que indican que los nuevos archivos .prg tambin forman parte de nuestro videojuego. Para ello utilizaremos sentencias INCLUDE, cuya sintaxis es la siguiente: INCLUDE nombre_del_archivo.prg ; Por cada archivo .prg que queramos incluir deberemos aadir una sentencia INCLUDE. Las sentencias INCLUDE se ubican justo debajo de la sentencia PROGRAM, que era la primera lnea de todo videojuego en Bennu. Realiza la tarea de separar tu videojuego en mdulos, no debera tomarte demasiado tiempo y sin duda te ser de muchsima utilidad mantener esta organizacin en mdulos durante el resto de trabajo que nos queda por hacer.
Tema 19 Mdulos
Pg. 1
20. Un RPG El matamarcianos del tema 10 fue un logro considerable. Ahora nos enfrentamos a un reto todava mayor, y es que gracias a todos los conceptos que hemos estado viendo (Textos, variables, mdulos, etc.) podemos ponernos manos a la obra para implementar un videojuego con las caractersticas bsicas de un RPG. Antes de empezar con la tarea de programacin del RPG es conveniente que introduzcamos algunos conceptos previos que sin duda nos facilitarn mucho la tarea.
20.1 Trabajar con varios FPG Como ya comentamos en el apartado 18.4 de este tutorial, es posible trabajar con varios FPG si almacenamos en distintas variables de tipo int los identificadores que nos devuelve la funcin load_fpg ( ) cada vez que cargamos un FPG. Trabajar con varios FPG tiene la misma utilidad que trabajar con varios mdulos de cdigo, ya que gracias a ello podremos dividir las tareas grficas entre varias personas y si mantenemos cada elemento grfico del videojuego en un FPG distinto nos ser mucho ms cmodo reutilizarlos ms adelante. A partir de ahora puedes olvidarte de los intervalos numricos que asignamos a cada uno de los elementos grficos del videojuego (Ver Tabla del apartado 2.3 de este tutorial). Ahora al tener cada elemento en un FPG separado podemos asignar a cada grfico el identificador numrico que queramos, aunque generalmente asignaremos los nmeros del 1 en adelante. Para realizar la carga de nuestros nuevos FPG crearemos un mdulo de cdigo muy sencillo que se ocupar de la tarea, pues debemos recordar que cuanto ms separemos las distintas partes de nuestro videojuego ms fcil se nos har continuar trabajando con l. Como novedad, el fichero .prg encargado de la carga de los FPG no lo escribiremos nosotros ntegramente, sino que partiremos del que viene incluido en el videojuego de ejemplo asociado a este tema. Obviamente tendremos que modificarlo parcialmente, as que vamos a explicar un poco su contenido:
Tema 20 Un RPG
Pg. 1
GLOBAL
Tendremos un vector GLOBAL de tipo int al que llamaremos fpgs [ ]. En cada una de las posiciones de este vector almacenaremos cada uno de los identificadores de los FPGs que cargaremos, por tanto debemos hacer un vector con posiciones suficientes para todos ellos.
CONST
La bloque CONST es una zona especial que todava no habamos visto, pero que introduciremos poco a poco a partir de ahora porque nos ser til para facilitar ciertas cosas. Una CONST no es ms que un determinado texto que el compilador sustituye por el valor que queramos (Un nmero entero, un real, una string...). En nuestro caso tenemos un vector de identificadores y en cada una de sus posiciones tenemos el identificador de un FPG determinado. Observa que si usamos CONST nos ser mucho ms sencillo referirnos a fpgs [ _SONIC ] fpgs [_SHADOW] que referirnos a fpgs [ 0 ] fpgs [ 1 ]. Es por ello que hemos definido una serie de CONST que asocian el nombre del personaje a cada posicin del vector. Observa que aadimos el carcter '_' al inicio del nombre de cada CONST para evitar que sus nombres puedan coincidir con los nombres de algn proceso. Tambin observa que las escribimos en maysculas, esto nos servir para NO confundirlas con variables, ya que las CONST no pueden cambiar de valor, son constantes.
Nota: El compilador exige que el bloque CONST se escriba antes del bloque GLOBAL, en esta tabla se explica despus porque el vector GLOBAL es el que nos crea la necesidad de usar CONST y as es ms fcil comprender lo que se explica.
PROCESS Finalmente y con todo lo anterior, tenemos un proceso llamado inicializar_fpgs ( ) que se encargar de ejecutar tantos load_fpg ( ) como sea necesario para cargar todos nuestros FPGs y almacenar el identificador de cada uno en su posicin correspondiente del vector fpgs [ ].
Aade los FPGs que creas convenientes al directorio /images de tu videojuego, copia el fichero de cdigo fpg.prg en el directorio /prg de tu videojuego y procede a modificar su cdigo para que se adapte a tu videojuego y los nuevos FPGs que has incluido en l. Es importante que no olvides aadir la correspondiente sentencia INCLUDE para que el fichero de cdigo fpg.prg tambin sea compilado, y ten en cuenta que el proceso inicializar_fpgs ( ) requerir ser invocado para que se carguen los FPGs. Finalmente, para que los procesos ya existentes en tu videojuego sepan cul es el FPG que deben usar para localizar su grfico ser necesario que a partir de ahora siempre asignes a su aspecto file el identificador que les corresponde, por ejemplo. file = fpgs [ _SONIC ];
Tema 20 Un RPG
Pg. 2
20.2 Modificando un poco el enemigo Nuestro proceso enemigo actual ha quedado obsoleto, apenas hace poco ms que moverse de manera lineal por la pantalla y no atiende a la existencia de nuestro protagonista. Todava no vamos a entrar en temas de inteligencia artificial ni mucho menos, pero s vamos a conseguir que al menos el enemigo haga lo posible por perseguir a nuestro protagonista, al menos cuando ste est cerca. Para ello vamos a eliminar todas las instrucciones del enemigo referentes a su movimiento y direccin, y en su lugar vamos a implementar su movimiento en base a 4 nuevas funciones de Bennu que nos ayudarn bastante, especialmente a la hora de implementar comportamientos ms complejos: Funcin exists ( ) Parmetro De la misma forma que la funcin collision ( ), esta funcin recibe como parmetro la palabra type seguida de un espacio y el nombre del proceso sobre el cul queremos comprobar si existe al menos uno de ellos activo actualmente en el videojuego. Devuelve Devuelve verdad (TRUE 1) activando por tanto una condicin dentr de un IF si existe al menos un proceso de ese tipo activo en el videojuego. En otro caso devuelve falso (FALSE 0).
get_id ( )
De la misma forma que exists ( ) recibe como Devuelve un identificador de proceso de cualquiera de parmetro la palabra type seguida de un los procesos activos del tipo dado como parmetro, si espacio y el nombre del proceso del cul no hay ninguno nos devolver 0. queremos obtener un identificador. Generalmente primero haremos la comprobacin de si hay algn proceso activo mediante exists ( ), y en caso afirmativo usaremos get_id ( ). Si hay varios procesos activos del mismo tipo nos devolver el identificador de cualquiera de ellos, y si la invocamos de manera consecutiva durante el mismo FRAME nos retornar los identificadores de los distintos procesos uno por uno, hasta terminar y devolver 0.
get_angle ( )
Recibe como parmetro un identificador de Devuelve el ngulo en milsimas de grado que forma el proceso activo, obtenido mediante get_id ( ), proceso que ha invocado get_angle ( ) con el proceso por ejemplo. cuyo identificador ha sido pasado como parmetro. Si asignamos ese ngulo a angle podemos mantener al proceso mirando al proceso dado como parmetro.
Tema 20 Un RPG
Pg. 3
Funcin advance ( )
Parmetro
Devuelve
Recibe como parmetro un valor de tipo int No devuelve ningn valor til, simplemente tiene el cuya utilidad es indicar el avance en pxels efecto mencionado en la columna Parmetro sobre el que queremos que realice en pantalla el proceso que lo invoca. proceso que invoca esta funcin. El avance se realizar en la direccin del ngulo que indica su aspecto angle. Es muy til para realizar desplazamientos que no vayan exclusivamente en el eje x o en el y.
El uso adecuado de las 4 funciones nos permitir conseguir que el enemigo siempre persiga al protagonista, dotndolo de un mnimo de inteligencia. Si el proceso enemigo tiene una variable PRIVATE de tipo entero a la que llamaremos objetivo ( int objetivo; ) podemos hacer lo siguiente: IF ( exists ( type protagonista) ) objetivo = get_id ( type protagonista ); angle = get_angle ( objetivo ); advance ( 2 ); END // Condicin: Si hay un proceso protagonista activo // Obtenemos su identificador de proceso // Obtenemos el ngulo que formamos con l // Avanzmos 2 pxeles en ese ngulo // Fin de la condicin
Recuerda que el identificador de un proceso es como una estructura o TYPE. Mediante el operador '.' nos permitir acceder a cualquiera de sus aspectos bsicos como pueden ser graph, x, y, etc. Si comprobamos adecuadamente la diferencia entre la x del enemigo y la x del protagonista y entre la y del enemigo y la y del protagonista podremos conseguir que los enemigos slo avancen en la direccin del protagonista cuando ste se encuentre suficientemente cerca. Intenta implementar tambin este comportamiento en tu videojuego. Sera aconsejable que aadas x e y como parmetros al proceso enemigo para que a partir de ahora sea invocado en posiciones aleatorias de todo el scroll, no nicamente en las esquinas del mismo.
Tema 20 Un RPG
Pg. 4
20.3 Prctica: Programar personajes amigos y dilogos La caracterstica principal de un RPG es la presencia de personajes no jugadores con los que podemos interactuar. En nuestro caso vamos a crear un nuevo mdulo para gestionar este tipo de personajes y en l incluiremos un par de personajes muy sencillos con los que podremos dialogar cuando nos acerquemos a ellos y pulsemos la tecla Enter. sto podrs programarlo fcilmente si haces uso de las funciones collision ( ) y key ( ). El primero de los personajes se encargar de curar a nuestro protagonista cuando su vida no se encuentre al mximo. Tambin nos dir una frase de entre 3 distintas que almacenaremos en un vector de strings. El segundo de los personajes se encargar de aumentar nuestra salud mxima cada vez que nuestra puntuacin supere los 100 puntos. Intenta programar este comportamiento. Recuerda que la vida de nuestro protagonista es una de sus variables PRIVATE y que por tanto ningn otro proceso es capaz de modificar su valor. Ser nuestro protagonista quien por su parte detectar la colisin con el personaje amigo y se ocupar de modificar los valores. Por supuesto Bennu ofrece soluciones mucho ms elegantes que lo anterior para modificar las variables de un proceso, pero al nivel en el que nos encontramos tenemos suficiente con saber programarlo de esa manera.
Tema 20 Un RPG
Pg. 5
21. Efectos de Sonido Cada 10 temas tenemos un reto cada vez ms complicado de superar y el anterior ha puesto a prueba todos los conocimientos que hemos adquirido adems de hacernos pensar diferentes estrategias para escribir un buen cdigo que cumpla unos determinados requisitos. Los siguientes temas no sern tan complicados y adems, gracias al concepto de mdulo, tendremos a mano recursos de cdigo reutilizables que nos ayudarn a programar nuestro videojuego actual y muchos de los que programemos al margen de este tutorial. En el caso de este tema puedes echar un vistazo al fichero de cdigo audio.prg que localizars en el subdirectorio /prg del videojuego de ejemplo asociado a este tema, aunque es recomendable que lo hagas una vez hayas ledo y practicado lo que aqu se propone.
21.1 Seleccionar Sonidos La carga de efectos de sonido en Bennu se realiza de forma muy similar a la carga de fuentes FNT o ficheros para grficos FPG. La nica diferencia es que los efectos de sonido pueden venir en diversos formatos y Bennu no los soporta todos. Lo primero que debemos hacer es seleccionar una buena coleccin de efectos de sonido que podamos usar en nuestro videojuego, para ello puedes acceder al repositorio de recursos (Biblioteca SFX) o bien buscar por internet. Una pgina web recomendada es www.soundsnap.com, que nos permite crear una cuenta gratuita con la que descargar hasta 5 efectos de sonido al da, permitindonos escucharlos previamente. Los formatos de sonido soportados por Bennu son los siguientes, con las siguientes caractersticas: Formato .midi .wav .ogg Caractersticas Msicas compuestas por notas en formato digital a partir de sus partituras. La calidad no es muy buena pero ocupan muy poco y es muy fcil encontrar canciones en este formato en internet. Recomendado para efectos de sonido de corta duracin, la calidad es muy alta y al ser un formato no comprimido es rpido de reproducir, aunque a costa de ocupar bastante espacio. Un formato muy similar al MP3, pero de carcter libre. Es perfecto para canciones ya que comprime para evitar un tamao excesivo de archivo.
Para convertir entre los formatos .mp3, .wav y .ogg puede usarse cualquier conversor gratuito.
Pg. 1
21.2 Cargar Efectos de Sonido Como hemos dicho anteriormente, para cargar un efecto de sonido en Bennu lo haremos de forma muy similar a como cargbamos fuentes de texto y FPGs. Tenemos dos funciones distintas para ello: int load_wav ( string ) Carga el fichero de audio en la ruta pasada como parmetro. Observa que se trata de una string y por tanto debe escribirse entre comillas. Esta funcin devuelve el identificador del efecto de sonido que hemos cargado, ser el identificador lo que nos permita reproducirlo en cualquier momento en nuestro videojuego. Se recomienda usar esta funcin para la carga de efectos de sonido en formato .wav. Carga el fichero de audio en la ruta pasada como parmetro. Observa que se trata de una string y por tanto debe escribirse entre comillas. Esta funcin devuelve el identificador de la cancin que hemos cargado, ser el identificador lo que nos permita reproducirla en cualquier momento en nuestro videojuego. Se recomienda usar esta funcin para la carga de melodas en formato .ogg y .midi.
Es recomendable usar cada funcin para lo que est preparada, esto es, load_wav ( ) para efectos de sonido y load_song ( ) para msicas de fondo. Entre otras cosas la carga de canciones con load_song evitar que cuando haya muchos efectos de sonido simultneos nuestra cancin deje de sonar, ya que el nmero de canales de sonido que ofrece Bennu para reproducir simultneamente varios efectos est limitado.
Pg. 2
21.3 Reproducir Efectos de Sonido Una vez realizada la carga de efectos de sonido podemos reproducirlos en cualquier momento. Generalmente pondremos la msica de fondo al principio de la ejecucin de nuestro videojuego (Bloque BEGIN-END de PROGRAM), despus de haber cargado todos los sonidos, mientras que por otra parte reproduciremos los efectos de sonido cuando corresponda, ya sea al lanzar un disparo, mostrar un mensaje de texto o simplemente caminar con nuestro protagonista. De la misma manera que con la carga, Bennu distingue 2 instrucciones distintas para la reproduccin de efectos de sonido y melodas, respectivamente: int play_wav ( int , int ) Reproduce el fichero de audio cuyo identificador pasamos como primer parmetro, debemos pasar el identificador que es una variable de tipo entero (int), no la ruta del fichero puesto que ste ya ha sido cargado previamente. En el segundo parmetro pasaremos un valor numrico que indicar el nmero de reproducciones consecutivas que queremos para el efecto de sonido (0 para reproduccin normal, 1 para 1 reproduccin extra, 2 para 2 reproducciones extra, etc.). Tambin podemos poner el valor especial -1 en ese parmetro para hacer que el sonido se repita constantemente. El valor de retorno de la funcin es un identificador de la reproduccin actual del sonido que nos permitir detenerlo, entre otras cosas, no lo confundas con el identificador devuelto por load_wav ( ). Int play_song ( int , int ) Reproduce el fichero de audio cuyo identificador pasamos como primer parmetro, debemos pasar el identificador que es una variable de tipo entero (int), no la ruta del fichero puesto que ste ya ha sido cargado previamente. En el segundo parmetro pasaremos un valor numrico que indicar el nmero de reproducciones consecutivas que queremos para la cancin (0 para reproduccin normal, 1 para 1 reproduccin extra, 2 para 2 reproducciones extra, etc.). Tambin podemos poner el valor especial -1 en ese parmetro para hacer que la msica se repita constantemente. El valor de retorno de la funcin es un identificador de la reproduccin actual del sonido que nos permitir detenerlo, entre otras cosas, no lo confundas con el identificador devuelto por load_wav ( ).
Pg. 3
21.4 Detener la reproduccin de efectos de sonido En algunos casos puede resultarnos conveniente detener la reproduccin de un determinado efecto de sonido, especialmente las melodas de fondo y los efectos de sonido que estamos reproduciendo con cierto nmero de repeticiones. Bennu tambin nos ofrece funciones sencillas para hacer esto:
Detiene la reproduccin del efecto de sonido cuyo identificador devuelto por play_wav ( ) pasamos como parmetro. No debemos pasar ni el identificador devuelto por load_wav ( ) ni la ruta del fichero de audio puesto que ste ya ha sido cargado previamente. Si pasamos como parmetro la CONST predefinida ALL_SOUND se detendrn todas las reproducciones de efectos de sonido activas. El valor de retorno de la funcin nos indica si la detencin se realiz o no con xito, aunque es algo por lo que en general no deberemos preocuparnos.
Int stop_song ( )
Detiene la reproduccin de la meloda que fue puesta en funcionamiento con play_song ( ). No debemos pasar ningn parmetro, puesto que la cancin de fondo es nica. El valor de retorno de la funcin nos indica si la detencin se realiz o no con xito, aunque es algo por lo que en general no deberemos preocuparnos.
21.5 Ms sobre audio Existen muchas otras funciones tiles para trabajar con el audio, aunque son tantas y tan diversas que se escapan al objetivo introductorio de este tutorial. Si quieres ver el resto de funciones de audio disponibles en Bennu puedes dirigirte al Manual de Referencia del Lenguaje o bien al lista de funciones disponibles, que se encuentra en el directorio de instalacin del Bennupack, por defecto C:/devBennu/bin y el documento de texto se llama function_list.txt.
Pg. 4
22. Animaciones I No hay lugar a dudas de que hasta ahora has estado intentando conseguir cambios de animacin especialmente en tu personaje protagonista. En este tutorial hemos hecho poco ms que explicar cmo controlar una nica animacin, generalmente la de andar, pero nos interesa que nuestros personajes se mantengan quietos, anden, salten, disparen, etc. Si hemos esperado hasta ahora es porque queramos que comprendieses perfectamente cmo gestionar las animaciones, ya que habrs comprobado que no es tan sencillo como poda parecer en un principio. Para ello hemos separado en dos temas esta parte: En este primer tema aprenderemos a gestionar las animaciones gracias a variables y un proceso auxiliar, mientras que en el siguiente te ofreceremos un fichero de cdigo .prg con todo hecho que sin duda te facilitar muchsimo las cosas.
22.1 Variables necesarias para gestionar una animacin El simple uso de asignaciones, incrementos y condiciones sobre la variable local graph de todo proceso en Bennu no ofrece todo lo necesario para una correcta gestin de animaciones complejas. Necesitaremos algunas variables auxiliares, y para comenzar poco a poco tendremos nicamente dos en la seccin PRIVATE de todo proceso en el que queramos tener cambios de animacin: PRIVATE int graph_ini; int graph_fin; Observa que los nombres graph_ini y graph_fin son nombres inventados, podamos haber usado otros, pero son suficientemente representativos para la utilidad que van a tener. Partiremos de que inicialmente las variables graph_ini y graph_fin guardan los valores que corresponden a la animacin de quieto de nuestro personaje, en el caso de nuestro protagonista tendremos: PRIVATE int graph_ini = 1; int graph_fin = 9;
Tema 22 Animaciones I
Pg. 1
Hasta ahora, para animar a nuestro protagonista tenamos varias instrucciones de incremento del nmero de grfico como sta: graph = graph + 1; Y esa instruccin vena siempre seguida de una comprobacin para que el nmero de grfico no se pasase del intervalo correcto, por ejemplo as: IF ( graph > 9 ) graph = 1; END Vamos a olvidarnos de toda esa parte, y haremos que nuestro protagonista NO vuelva a hacer referencia a su variable graph, sino que ser un proceso el que se haga cargo de ello. Nuestro protagonista a cada FRAME invocar el siguiente proceso: PROCESS animar ( int primero , int ultimo ) BEGIN father.graph = father.graph + 1; IF ( ( father.graph < primero ) OR ( father.graph > ultimo ) ) father.graph = primero; END END La anterior funcin, dados dos valores de nmero de grfico, se ocupa de incrementar en 1 el nmero del grfico del proceso padre, y a continuacin, comprobar si su nmero de grfico o bien se pasa del valor dado por ltimo o bien es menor que el valor dado por primero, para retornar en ese caso al valor dado por primero. Qu significa sto? Que si desde nuestro proceso protagonista invocamos continuamente la funcin animar dando como parmetros el valor de graph_ini y graph_fin tendremos a nuestro protagonista costantemente realizando la animacin de quieto: LOOP animar ( graph_ini , graph_fin );
Tema 22 Animaciones I
Pg. 2
22.2 Implementar cambios en la animacin Recuerda que hemos dicho que nuestro protagonista ya no se ocupara nunca ms de modificar el valor de su variable LOCAL graph, sino que ahora sera el proceso animar ( ) el encargado de ello. Segn lo anterior, observa que tal y como tenemos ahora mismo nuestro videojuego nuestro protagonista siempre se anima entre los valores de nmero de grfico dados por graph_ini y graph_fin. Esto supone una ventaja, y es que si queremos modificar la animacin de nuestro personaje basta con que modifiquemos los valores de graph_ini y graph_fin y la invocacin de la funcin animar ( ) se encargar de hacer que nuestro personaje se anime en el intervalo que queramos. Un ejemplo bsico para hacer que nuestro protagonista se mantenga quieto cuando no pulsamos ninguna tecla y camine cuando pulsemos la tecla _LEFT o _RIGHT sera: PROCESS protagonista ( ) PRIVATE int graph_ini = 0; int grapg_fin = 9; BEGIN LOOP animar ( graph_ini , graph_fin ); IF ( key (_LEFT) ) graph_ini = 10; graph_fin = 25; x = x 8; ELSIF ( key (_RIGHT) ) graph_ini = 10; graph_fin = 25; x = x + 8; ELSE graph_ini = 0; graph_fin = 9; END FRAME; END END
Tema 22 Animaciones I
Pg. 3
23. Animaciones II En algunas ocasiones conviene utilizar un fichero de cdigo realizado por otra persona para solucionar una tarea compleja. En el caso de la animacin, este tema tiene un fichero llamado animacion.prg que incluye todos los procesos, tipos de dato, constantes, etc. Necesarias para gestionar animaciones complejas. Si tienes conocimientos suficientes sobre tipos de dato es probable que comprendas su funcionamiento viendo su cdigo, pero dado que introduciremos algn concepto nuevo sobre tipos de dato es recomendable que atiendas a lo que se explica en este tema sobre matrices.
23.1 Una estructura de datos apropiada Para gestionar todas las posibles animaciones de todos los posibles personajes de nuestro videojuego necesitaremos almacenar informacin sobre varias animaciones, que pueden llegar a ser realmente muchas. Adems, almacenando nicamente el grfico inicial y final de la animacin no tenemos suficiente, ya que habrs observado que cambiar de grfico a cada FRAME hace que la animacin parezca demasiado acelerada, sera recomendable mantener informacin sobre la latencia en FRAMES que debemos esperar antes de cambiar de grfico. El tipo de dato que utilizaremos para almacenar la animacin ser el siguiente: TYPE tp_animacion int graph_ini; int graph_fin; int latencia; END Y para almacenar todas las animaciones que necesitaremos conviene que distingamos entre los diferentes personajes o elementos que animaremos y sus diferentes estados de animacin. Si suponemos que cada personaje viene numerado como 0, 1, 2, etc. y cada animacin tambin viene numerada como 0, 1, 2, etc. Nos viene a la mente que podemos usar un vector, pero en realidad usaremos una matriz, que no es ms que un vector de vectores y que nos ser realmente til. Para definir una matriz de animaciones podemos hacerlo as: GLOBAL tp_animacion animaciones [ 16 ] [ 16 ];
Tema 23 Animaciones II
Pg. 1
Observa que slo con aadir un nuevo [ ] podemos aumentar las dimensiones de la estructura de datos. En este caso es una matriz, pero con un [ ] adicional podramos hacer un cubo, y con otro [ ]... Tendramos una estructura de datos difcil de representar mentalmente. Hemos definido una matriz donde almacenar hasta 16 personajes con hasta 16 animaciones distintas. Sern suficientes para nuestro videojuego? Es recomendable que definamos 2 valores constantes donde guardar el nmero de personajes y de estados, y que usemos las constantes para definir el tamao de la matriz: CONST _MAX_PERSONAJES = 16; _MAX_ESTADOS = 16; GLOBAL tp_animacion animaciones [ _MAX_PERSONAJES ] [ _MAX_ESTADOS ]; Cmo utilizamos una estructura de datos tan compleja? A partir de ahora cada personaje tendr una variable PRIVATE entera donde almacenar el nmero que indica su nombre y otra donde almacenar el nmero que indica su estado. El personaje har uso de esas variables para acceder a la posicin de la matriz donde se encuentra la informacin de la animacin. Por ejemplo, si tenemos un personaje numerado con 3 y que se encuentra en su animacin 8 bastar con que asignemos a las variables nombre y estado lo siguiente: nombre = 3; estado = 8; Y accedamos a la siguiente posicin de la matriz: animaciones [ nombre ] [ estado ] Para encontrar los datos referentes a la animacin que debe realizar. Puede que te parezca demasiada complicacin para realizar simples animaciones, pero debes pensar que podemos llegar a tener una gran cantidad de personajes y animaciones distintas y que sin duda esta estructura de datos nos facilitar enormemente trabajar con ellos.
Tema 23 Animaciones II
Pg. 2
23.2 Constantes para indexar Hasta ahora, gracias a las variables PRIVATE de cada personaje que nos indican su nombre y su estado de animacin podamos acceder a la posicin de la matriz donde se encuentra su animacin. Pero tenemos un pequeo problema, y es que resultar complicado estar pensando en cul nmero indica el nombre del personaje que estamos programando y cul nmero indica el estado de animacin que queremos que tome. Para ello podemos hacer un buen uso de las CONST, ya que no son ms que sustituciones textuales y nos servirn para poder hablar de _SONIC o de _SHADOW en lugar de usar un 1 o un 2 para determinar el nombre del personaje. De la misma manera podemos proceder con los estados de animacin, por ejemplo: CONST _SONIC = 0; _SHADOW = 1; _QUIETO = 0; _ANDAR = 1; _GOLPEAR = 2; Gracias a lo anterior, cuando queramos que nuestro personaje Sonic realice la animacin de andar bastar con que accedamos a: animaciones [ _SONIC ] [ _ANDAR ] Para obtener los datos de la animacin. Para mayor aclaracin sobre lo expuesto en este punto, la tarea de acceder a una determinada posicin de un vector o de una matriz se denomina indexar y es algo que resulta cmo hacer utilizando una variable o una constante.
Tema 23 Animaciones II
Pg. 3
23.3 El proceso animar Como ya comentamos en el tema anterior, animar ser una accin que todo personaje estar realizando todo el tiempo, y ya no slo depender de que pulsemos alguna tecla, sino que siempre tendremos a nuestro personaje animndose, bien sea cuando est quieto, cuando est andando o cuando est realizando cualquier otra accin. Por eso vamos a implementar un PROCESS que acceder a father.graph y a la matriz de animaciones y que ser invocado por todo personaje, en todo FRAME, para que se ocupe de su animacin. Hemos dicho que todo personaje tendr una variable PRIVATE con su nombre y otra con su estado, por ejemplo: PRIVATE int nombre = _SONIC; int estado = _QUIETO; Y sern esas variables las que pasaremos como parmetro a la funcin animar para que indexe la matriz de animciones y nos imponga en grfico que nos corresponde, la funcin quedara as: PROCESS animar ( int nombre , int estado ) BEGIN father . graph++; IF ( father . graph > animaciones [ nombre ] [ estado ] . graph_fin OR father . graph < animaciones [ nombre ] [ estado ] . graph_ini ) father . graph = animaciones [ nombre ] [ estado ] . graph_ini; END END La idea es la siguiente: Siempre incrementamos en 1 nuestro grfico, y si para el nombre y el estado que tenemos nuestro grfico NO se encuentra dentro del intervalo de animacin correcto, entonces restauramos el grfico inicial. Cmo hacemos uso correcto de la funcin animar ( )? La invocaremos incondicionalmente en cada FRAME y le pasaremos nuestro nombre y nuestro estado. Si procedemos as bastar con cambiar el valor de nuestra variable estado para que la funcin animar haga que nos animemos dentro del nuevo intervalo de animacin, simplificando la tarea infinitamente, un ejemplo de cambio de animacin entre los estados _QUIETO y _ANDAR sera:
Tema 23 Animaciones II
Pg. 4
LOOP ... animar ( nombre , estado ); ... IF ( key ( _LEFT ) ) estado = _ANDAR; ELSIF ( key ( _RIGHT ) ) estado = _ANDAR; ELSE estado = _QUIETO; END ... END Observa que ya no tenemos que volver a preocuparnos por el valor de nuestra variable graph, sino que slo tenemos que preocuparnos por el valor de nuestra variable estado, ya que el proceso animar ( ) har el resto.
23.4 La latencia de animacin Al principio de este tema hemos dicho que lo deseable para nuestra animacin sera que no se realizase en cada FRAME sino que tuviese en cuenta una latencia de animacin de forma que slo se cambiase el valor de graph cada varios FRAMES. Para que esto funcione tendremos que modificar ligeramente el proceso animar ( ) de forma que haga uso del campo latencia de la matriz de animaciones. Cmo podemos hacer que algo suceda cada N FRAMES? Podemos tener una variable GLOBAL llamada tiempo que comience valiendo 0 y el proceso arbitro ( ) la incremente en 1 a cada FRAME. Si hacemos esto, adems de tener controlados los FRAMES que han transcurrido desde el inicio de la ejecucin, tambin podemos hacer otras cosas tiles. Observa que si dividimos el tiempo entre la latencia de animacin (Divisin de nmeros enteros) siempre tenemos un resto que vale 0 exactamente cada vez que transcurren tantos FRAMES como indica la latencia.
Tema 23 Animaciones II
Pg. 5
Por ejemplo para latencia 4, si vamos dividiendo el tiempo entre 4 a cada FRAME slo obtenemos resto 0 cada 4 FRAMES, esta idea podra servirnos, Pero cmo obtenemos el resto de una divisin? Hay una operacin llamada mdulo que aunque no se explica en los estudios bsicos de matemticas tiene una gran utilidad en programacin, ya que obtiene el resto de una divisin, y no el cociente que es lo habitual. El operador que realiza esta operacin en Bennu es '%' y podemos usarlo dentro de nuestro proceso animar as: PROCESS animar ( int nombre , int estado ) BEGIN IF ( tiempo % animaciones [ nombre ] [ estado ] . latencia == 0 ) father . graph++; END IF ( father . graph > animaciones [ nombre ] [ estado ] . graph_fin OR father . graph < animaciones [ nombre ] [ estado ] . graph_ini ) father . graph = animaciones [ nombre ] [ estado ] . graph_ini; END END El operador % tambin tiene otras aplicaciones como conseguir que nuestro personaje no pueda disparar una vez por FRAME, si no que tenga una latencia de disparo que slo le permita disparar cada N FRAMES. Es algo que puedes intentar implementar en tu videojuego.
23.5 Inicializar las animaciones Ya hemos explicado qu es lo que almacenar nuestra matriz de animaciones y cmo haremos uso de ella gracias al proceso animar ( ), pero nos falta un detalle importante, y es que la matriz de animaciones no ha sido inicializada y no almacena ningn valor. Tendremos que completar todos los campos de la matriz con los valores de grfico inicial y final y la latencia que queramos para cada animacin de cada personaje. Es una tarea aburrida, pero no te tomar mucho tiempo. Para evitar ensuciar nuestro cdigo haremos un nuevo PROCESS en animacion.prg que se encargar de inicializar las animaciones con los valores que deseemos. El proceso ser as:
Tema 23 Animaciones II
Pg. 6
PROCESS inicializar_animaciones ( ) BEGIN animaciones [ _SONIC ] [ _QUIETO ] . graph_ini = 1; animaciones [ _SONIC ] [ _QUIETO ] . graph_fin = 9; animaciones [ _SONIC ] [ _QUIETO ] . latencia = 3; animaciones [ _SONIC ] [ _ANDAR ] . graph_ini = 10; animaciones [ _SONIC ] [ _ANDAR ] . graph_fin = 21; animaciones [ _SONIC ] [ _ANDAR ] . latencia = 3; ... END Ante todo no olvides invocar este proceso al inicio de tu videojuego, ya que en otro caso la matriz de animaciones estar vaca y todos los personajes que la utilicen permanecern con graph = 0;
23.6 Latencia por defecto Observars que generalmente el valor de la latencia para todas las animaciones suele ser el mismo, especialmente si todos los personajes proceden de sprites del mismo videojuego. Para evitar tener que especificar la misma latencia para todas las animaciones de todos los personajes, podemos dar una latencia por defecto de forma que, si no especificamos ningn valor para la latencia de una animacin, sta tome el valor de latencia por defecto. Para ello podemos asignar el valor por defecto en la declaracin del TYPE tp_animacion, as: TYPE tp_animacion int graph_ini; int graph_fin; int latencia = 3; END Con lo anterior, no ser necesario que especifiquemos la latencia para cada animacin, sino que bastar con especificar el valor de las latencias que no vayan a ser 3 exactamente.
Tema 23 Animaciones II
Pg. 7
23.7 Una mquina de estados Hemos simplificado bastante el trabajo con animaciones, ya que ahora slo tenemos que preocuparnos por el valor de la variable estado de cada personaje. Pero cundo debemos modificar el valor de la variable estado? Cules son las condiciones que deben cumplirse para que el funcionamiento sea totalmente correcto? Es complicado determinar los cambios de estado, y de hecho lo que necesitaremos programar se denomina formalmente mquina de estados. Una mquina de estados conviene ser dibujada para comprender su funcionamiento, y por eso en este tema tenemos asociado un diagrama en el que se muestran 3 distintos estados y todas las condiciones que deberamos programar para conseguir que nuestro personaje cambiase entre los estados bsicos _QUIETO, _ANDAR y _GOLPEAR. Observa cmo hemos diseado la mquina de estados y especialmente presta atencin a que cada nuevo estado de animacin implica tantas nuevas condiciones de cambio de estado como estados anteriores tenamos... Esto es un indicador de que cada nuevo estado que incluyamos, en el peor de los casos podr llegar a duplicar el nmero de condicionales necesarios.
Tema 23 Animaciones II
Pg. 8
24. Durezas Hasta ahora no hemos tenido en cuenta la interaccin fsica con el escenario. Se trata de algo fundamental en un videojuego el hecho de poder responder a paredes, suelos y techos. Para ello haremos uso de alguna funcin nueva y tambin tendremos que aadir unos grficos nuevos a nuestro FPG.
24.1 La funcin map_get_pixel ( ) Esta funcin obtiene la informacin de color del pxel de un grfico determinado, su sintaxis es la siguiente: int map_get_pixel ( int , int , int , int ) Parmetro 1 Parmetro 2 Parmetro 3 Parmetro 4 Valor de retorno Identificador del fichero FPG en el que se encuentra el grfico sobre el cual queremos obtener la informacin de color de uno de sus pxeles. Nmero de grfico dentro del FPG. Coordenada x del pxel que queremos leer. Coordenada y del pxel que queremos leer. Retorna un valor numrico que representa el color ledo. No podremos apreciar de qu color se trata a partir de este valor numrico, pero s que podremos utilizarlo para pasrselo a otras funciones, como veremos a continuacin.
Tema 24 Durezas
Pg. 1
24.2 Mapas de durezas Un mapa de durezas es un clon de un backgrund que utilizamos como escenario, pero tiene coloreadas las zonas que corresponden a suelos, techos, paredes, etc. En distintos tonos de color uniformes. Necesitaremos crear un mapa de durezas asociado a cada uno de nuestros escenarios, y para ello haremos una copia de los mismos y sobre ellos colorearemos con Paint.NET u otro programa similar las partes correspondientes de la siguiente manera: Rojo Techos Superficies horizontales en las que queremos que nuestro protagonista golpee y caiga hacia abajo al saltar. Sern infranqueables y no tienen por qu comportarse como suelos, a no ser que aadamos encima de ellas el color de dureza de suelos. Superficies horizontales en las que queremos que nuestro protagonista se apoye y deje de caer hacia abajo. Sern infranqueables y no tienen por qu comportarse como techos, a no ser que aadamos debajo de ellas el color de dureza de techos. Tal y como aplicaremos la gravedad, la mxima pendiente de suelo que reconocer nuestro personaje depender de su velocidad de avance. Normalmente superficies de hasta 30 grados de inclinacin funcionarn correctamente. Azul Paredes Superficies verticales que queremos que resulten infranqueables para nuestro protagonista durante su desplazamiento horizontal. Por el momento se comportarn como paredes cuando nos desplacemos a izquierda y cuando nos desplacemos a derecha, aunque ms adelante podremos cambiar sto.
Amarillo
Suelos
En todos los casos anteriores, el grosor necesario para el dibujado de las durezas depender mucho de la velocidad a la que desplacemos nuestro protagonista. Ten en cuenta que nuestro protagonista avanza varios pxeles en cada FRAME, y si avanza ms pxeles en un FRAME que el grosor que le damos a la dureza cabe la posibilidad de que la pueda ignorar... En este tutorial no entraremos en detalle sobre cmo evitar que sucedan situaciones como la anterior, aunque con todo lo que hemos visto es posible solucionarlo si usamos un poco nuestra imaginacin. De momento slo nos preocupa conseguir que nuestro protagonista sea capaz de detener su movimiento al encontrarse con una pared.
Tema 24 Durezas
Pg. 2
24.3
Para conseguir que nuestro personaje se detenga al encontrarse con una pared, tendremos que aadir unas condiciones que debern cumplirse antes de realizar su desplazamiento en el eje x. Nuestro mapa de durezas tiene la resolucin exacta de nuestro escenario, y dada una posicin x, y de nuestro protagonista en el escenario es posible, gracias a la funcin map_get_pixel ( ) obtener la informacin de color de la dureza asociada a ese punto si comprobamos esa coordenada en el mapa de durezas. La idea es sencilla: Comprobaremos la dureza en el punto al que vamos a desplazarnos, y slo nos desplazaremos en el caso de que ese punto no corresponda a una dureza de pared. Si nuestro grfico de escenario est numerado como 401, el fondo del mismo como 402 y la dureza asociada al escenario como 403 un cdigo que podra solucionar la detencin frente a paredes sera el siguiente: IF ( map_get_pixel ( fpg_escenario , 403 , x + 10 , y ) != <cdigo de color de la dureza de pared> ) x = x + 10; END Observa nuevamente que antes de desplazarnos 10 pxeles hacia la derecha comprobamos el color de la dureza en ese punto. El cdigo anterior funcionara perfectamente, slo nos falta un detalle, Cul es el cdigo de color de la dureza de pared? Para ello haremos uso nuevamente de la funcin map_get_pixel ( ). Tendremos un pequeo grfico en nuestro FPG que guardar los distintos colores de cada dureza en cada uno de sus pxeles. Tal y como los hemos expuesto en la tabla anterior podemos tener en cada pxel la informacin siguiente: x 0 1 2 y 0 0 0 Color Rojo que utilizamos para las durezas de techo Amarillo que utilizamos para las durezas de suelo Azul que utilizamos para las durezas de pared Resto de colores que podamos asociar a durezas, pueden ser puertas, transportadores, zonas de fin de escenario, pinchos, etc.
Tema 24 Durezas
Pg. 3
Con el mapa anterior, que podemos numerar con el cdigo 999 dentro del FPG de escenarios, podemos cargar dentro de algunas variables los cdigos de color asociados a cada tipo de dureza. Es lo que hacemos con la funcin inicializar_durezas ( ) en el videojuego de ejemplo asociado a este tema. Si completamos el cdigo encargado de la deteccin de paredes as: IF ( map_get_pixel ( fpg_escenario , 403 , x + 10 , y ) != durezas [_PARED] ) x = x + 10; END Por fin conseguiremos que nuestro personaje interactue correctamente con las paredes. Ten en cuenta que slo interpretamos las paredes de forma horizontal. Observa que si queremos tener un obstculo en nuestro escenario que se comporte como una columna slida, adems de las durezas de pared tendremos que aadir en su parte ms alta las correspondientes durezas de suelo, tal y como veremos en el prximo tema.
Tema 24 Durezas
Pg. 4
39. PUBLIC Ya hemos trabajado con prcticamente todas las utilidades principales de Bennu, pero nos queda un apartado muy especial que sin duda te abrir las puertas a disear lo que quieras. Observa que hasta ahora el mbito de las variables PRIVATE no nos permita acceder a ellas a travs del identificador de proceso, y eso nos impide realizar tareas tan sencillas como determinar el dao que produce un determinado disparo al colisionar con un determinado personaje. El mbito LOCAL s que lo permite, pero no lo hemos estudiado porque est muy mal implementado, tanto que modifica los identificadores de todos los procesos para alojar todas las variables LOCAL, independientemente de su tipo, incrementando innecesariamente la memoria ocupada. Para evitar lo anterior tenemos el mbito PUBLIC, que slo modifica los identificadores del proceso que tiene declaradas las variables. Parece muy til y sencillo, pero su tratamiento es especial, como veremos a continuacin.
39.1 Sentencia DECLARE Aunque podemos declarar las variables PUBLIC en el mismo punto en el que declarbamos las variables PRIVATE eso no nos permitir poder acceder a ellas a travs del identificador de proceso, ya que sigue un tipo de dato nico para todos los procesos y slo tiene en cuenta los aspectos bsicos graph, x, y, etc. Para solucionarlo, es necesario aplicar una sentencia DECLARE a los procesos que incorporan variables PUBLIC. La sentencia debe aplicarse antes de la propia declaracin del proceso. En nuestro caso vamos a declarar unas variables PUBLIC en nuestro protagonista y por tanto antes de su declaracin deberamos aadir: DECLARE PROCESS protagonista ( <Parmetros propios del protagonista> ) PUBLIC int vida; int fuerza; int agilidad; END END Puedes incluir la sentencia anterior justo antes de declarar el proceso protagonista igual que hasta ahora. Para el protagonista ser como si tuviese las mismas variables en mbito PRIVATE slo que ahora vamos a ver cmo el resto de procesos pueden acceder a ellas y modificarlas.
Tema 39 PUBLIC
Pg. 1
39.2
La sentencia DECLARE no solamente asocia las nuevas variables al proceso sino que adems modifica el identificador de proceso para que permita acceder a ellas. Observa que hasta ahora el identificador de proceso se almacenaba en una variable de tipo entero (int) y nos permita acceder a todos sus aspectos bsicos como x, y, size, etc. Como si se tratase de un TYPE. sto se seguir manteniendo y podremos utilizar el identificador de proceso como siempre para acceder a esos campos, pero ahora tenemos adems un nuevo tipo de dato que nos permitir acceder a todos los campos PUBLIC de la misma manera. El nuevo tipo de dato que genera DECLARE tiene el mismo nombre que el proceso, en nuestro caso protagonista pasa a ser tambin un tipo de dato, por lo que cualquier proceso puede tener una variable PRIVATE de tipo protagonista y con ella acceder a sus aspectos bsicos y tambin a todas sus variables de mbito PUBLIC. Cuidado: Las variables de tipo protagonista y todas las variables de tipo de dato asociadas a un proceso con variables de mbito PUBLIC no pueden modificarse antes de tener asociado un identificador de proceso vlido. Que quiere decir lo anterior? Pues que antes de poder acceder a cualquiera de los campos de una variable PUBLIC es necesario que sta almacene un identificador de proceso activo, por ejemplo, cualquier enemigo que queramos que acceda la vida de nuestro protagonista necesitar primero la variable PRIVATE de tipo protagonista: PRIVATE protagonista objetivo; Y en segundo lugar, antes de acceder a cualquiera de los campos de objetivo, es necesario que la variable est asociada al proceso protagonista y que ste est vivo. Podramos hacer: IF ( objetivo = collision ( type protagonista ) ) objetivo.vida = objetivo.vida - 1; END Una vez ms, observa que antes de poder acceder a objetivo.vida comprobamos que hay colisin y que por tanto objetivo almacena un identificador de proceso vlido. Y por supuesto, ademas de collision ( ), tambin get_id ( ), father, y en general, todo lo que retorna un identificador de proceso del protagonista resulta ahora asociable al nuevo tipo de dato generado por DECLARE.
Tema 39 PUBLIC
Pg. 2
41. Conceptos Bsicos de 3D La transicin de la programacin con Bennu en 2D a Bennu 3D suele ser ms lenta y complicada de lo habitual. Con este tema vamos a introducir una serie de conceptos bsicos a tener en cuenta que nos ayudarn a orientarnos sobre cmo se trabaja en 3 dimensiones.
41.1 Modelos En 3D ya no trabajaremos ms con sprites y backgrounds. Todos los componentes poligonales, bien sean escenarios, personas, armas, etc. se denominan modelos y no son ms que informacin numrica sobre las coordenadas x, y, z de cada vrtice que compone el modelo. Los vrtices forman polgonos y stos se cubren con una imagen denominada textura, cuyo formato puede ser cualquiera, no es ms que una imagen plana. Bennu3D soporta una gran cantidad de modelos 3D cuyos ficheros tienen distintas extensiones (md2, md3, obj, 3ds, etc.) y tambin otros tantos modelos de mapas, que tienen otras extensiones que codifican su informacin poligonal de gran tamao de una forma ms eficiente.
41.2 Coordenadas La primera impresin que tenemos al pasar a 3D es que vamos a tener una coordenada ms: La profundidad. Ya no slo dibujaremos en las componentes horizontal y vertical de la pantalla, sino que adems tendremos una componente z que nos indicar la profundidad en la que posicionaremos un modelo. En realidad es un poco ms complejo que todo sto, ya que adems de las 3 coordenadas x, y, z de cada modelo tendremos las coordenadas de la cmara y tambin las del objetivo de la cmara. Si lo pensamos detenidamente estamos pasando de manejar 2 coordenadas para cada modelo a manejar un total de 9: Las 3 del modelo, las 3 de la cmara y las 3 del objetivo al cual apunta la cmara. Por fortuna Bennu3D nos ofrece una serie de funciones que facilitarn el trabajo.
Tema 39 PUBLIC
Pg. 1
41.3 Operaciones bsicas sobre un modelo Bennu3D nos ofrece 3 operaciones bsicas sobre cualquier modelo 3D, todas ellas son aplicables respecto de todos sus ejes al mismo tiempo, pero tambin sobre uno de sus ejes por separado: Rotaciones Rotan el modelo un determinado nmero de grados en un eje. Si rotamos en el eje x rotamos de la misma forma que lo hace un pollo asado, tendido horizontalmente. Si rotamos en el eje y rotamos de la misma forma que lo hace una persona bailando, con los pies en el suelo. Si rotamos en el eje z rotamos de la misma manera que lo hacen las agujas de un reloj al que miramos de frente. Escalados Cambian el tamao de un modelo en un diferente factor de escala en un eje. Escalas mayores que 1 harn que el modelo crezca mientras que escalas menores que 1 (Sin llegar a 0) harn que el modelo decrezca. Si escalamos en el eje x hacemos que el modelo crezca o decrezca horizontalmente, a lo ancho. Si escalamos en el eje y hacemos que el modelo crezca o decrezca verticalmente, en altura. Si escalamos en el eje z hacemos que el modelo crezca o decrezca hacia el fondo. Traslaciones Modifican la posicin de un modelo en un determinado nmero de pxeles dentro del espacio 3D. Si trasladamos en el eje x hacemos que el modelo se desplace horizontalmente. Si trasladamos en el eje y hacemos que el modelo se desplace verticalmente. Si trasladamos en el eje z hacemos que el modelo se desplace hacia el fondo o hacia la cmra.
Tema 39 PUBLIC
Pg. 2
42. Mapas 3D Bennu 3D es compatible con prcticamente todos los mapas de Quake III y otros juegos compatibles. Estos mapas tienen extensin .bsp y aunque podemos editarlos nosotros mismos con los programas GtkRadiant o Quake Army Knife (QuArK), el objetivo de este tutorial es partir de mapas ya hechos por otras persoas, que podemos encontrar en internet en pginas web como http://lvlworld.com
42.1 Ficheros comprimidos Los mapas son modelos 3D con un formato muy similar al de los personajes. Los formatos de modelo 3D ocupan relativamente poco, ya que nicamente almacenan informacin sobre coordenadas de cada vrtice, vrtices que forman cada polgono, textura asociada a cada polgono y poco ms. An as el caso de los mapas es especial, ya que tienen una gran cantidad de polgonos y sus ficheros suelen ocupar varios Mb. Es por eso que suelen venir guardados dentro de un fichero comprimido que Bennu descomprimir antes de poder utilizar. Las extensiones de fichero comprimido que almacenan mapas son .zip y .pk3, este ltimo formato no es ms que un .zip renombrado que podremos descomprimir normalmente con cualquier compresor. Bennu 3D tiene una funcin que se encarga de descomprimir este tipo de ficheros al principio de la ejecucin para que posteriormente podamos hacer uso del mapa .bsp que almacenan, adems de todos los subdirectorios con las texturas, sonidos asociados y algn fichero ms. La funcin es: int M8EE_ADDZIPFILE ( string ); Como parmetro recibe la ruta del fichero .zip o .pk3 que queremos descomprimir. Una vez descomprimido podemos hacer referencia a cualquiera de los ficheros que almacenaba como si estuviese descomprimido en el directorio del juego, aunque por lo general slo haremos referencia en adelante el fichero .bsp con el mapa. El valor de retorno nos indicar si la descompresin tuvo xito (TRUE) o no (FALSE). Una posible causa de error a la hora de cargar un fichero comprimido sera que la ruta pasada como parmetro haga referencia a un fichero que no existe.
Tema 42 Mapas 3D
Pg. 1
42.2 Mapas Como ya hemos comentado anteriormente, el modelo 3D de un escenario no tiene un formato muy distinto del modelo de cualquier personaje. La principal diferencia es su alto nmero de polgonos y el tamao de los mismos, y Bennu nos ofrece funciones diferentes para cargar los mapas que resultan mucho ms eficientes al trabajar con modelos grandes: int M8EE_LOADMODELEX ( string , int ); Recibe como primer parmetro el nombre del fichero .bsp con el mapa, que puede hacer referencia al contenido de un fichero .zip o .pk3 cargado previamente con M8EE_ADDZIPFILE ( ). En el segundo parmetro introduciremos un nmero comprendido entre 0 y 255 que indicar la calidad con la que se cargar el escenario. Recuerda que hemos dicho que los escenarios se cargan de una forma especial que optimiza el tiempo de carga, y sto puede lograrse a costa de reducir la fidelidad con la que se cargar el escenario. Salvo serias limitaciones de CPU podemos poner siempre 255 en el valor de la calidad. El valor de retorno es importante, ya que se trata del identificador del modelo del mapa cargado y deberemos almacenarlo siempre dentro de una variable. Gracias al identificador que nos retorna M8EE_LOADMODELEX ( ) podremos aplicar diversas funciones para transformar el escenario y tambin podremos usar ste identificador como parmetro para otras funciones de cmara, respuesta a colisiones, etc. La funcin M8EE_LOADMODELEX ( ) tambin sirve para cargar modelos estticos que no tienen por qu ser mapas, pero que por su tamao nos resulta ms eficiente cargar haciendo uso de esta funcin. En cambio tenemos otra funcin de carga de escenarios que slo es aplicable a ficheros .bsp, y que est 100% optimizada para escenarios en este formato con respuesta a colisiones (Esto es algo que veremos ms adelante). Int M8EE_LOADQ3MAP ( string , int ); De la misma manera que la funcin anterior, el primer parmetro es el nombre del fichero .bsp a cargar como mapa y el valor de retorno es el identificador del mismo. La diferencia es que con esta funcin no es necesario pasar como parmetro un valor indicando la calidad. M88EE_LOADQ3MAP ( ) siempre realiza la carga con la mxima calidad, la principal diferencia es que el segundo parmetro es una lista de colisiones, que como veremos ms adelante, indica los modelos que debern responder fsicamente a las colisiones con suelos, paredes y techos del mapa. Por ahora podemos poner un 0 como valor de la lista de colisiones para indicar que no hay modelos que deban responder fsicamente al mapa cargado con M8EE_LOADQ3MAP ( ).
Tema 42 Mapas 3D
Pg. 2
42.3 Seleccionar mapas para nuestro videojuego Haciendo uso del repositorio de recursos de Bennu o bien buscando en http://lvlworld.com hazte con unos cuantos escenarios y prueba a cargarlos en tu videojuego usando las funciones vistas en este tema. En algunos casos observars que los mapas no cargan correctamente algunos suelos y paredes, esto se debe a que algunas de sus texturas forman parte de la instalacin de Quake III Arena u otro juego que hace uso del mismo formato de mapas, y estas texturas no estn incluidas en el fichero .zip o .pk3 que hemos descargado. En esos casos lo ms recomendable es buscar otro mapa que s use texturas 100% propias, puesto que al faltar la textura asociada a un polgono se interpreta que no hay obstculo y podemos atravesarlo, ya se trate de una pared o un suelo. Por qu el comportamiento es as cuando falta una textura? La transparencia (Tambin la falta total de textura en este caso) nos servir para tener algunas texturas a travs de las cuales podamos ver. El caso de la falta de textura es demasiado extremo, pero piensa en el caso de que queramos una pared con agujeros, bastara con aplicar un alpha en esos agujeros y podramos ver a travs de ellos e incluso atravesarlos.
Tema 42 Mapas 3D
Pg. 3
43. Modelos 3D Nuevamente, Bennu 3D es compatible con todos los modelos 3D asociados a Quake III y otros juegos compatibles, pero tambin nos ofrece soporte para otros formatos de modelo 3D habituales. La lista completa de modelos 3D puede consultarse en la documentacin de Bennu 3D (Subdirectorio /doc), pero en este tutorial solamente trataremos algunas de las extensiones que mejor resultado y compatibilidad ofrecen.
43.1 Formatos de Modelo 3D Los diferentes formatos de modelo 3D pueden o no incluir distintas animaciones del modelo en el mismo fichero y tener mejor o peor calidad. Los formatos con los que trabajaremos en este tutorial y las caractersticas principales de cada uno son las siguientes: Formato .md2 .md3 .obj .3ds .x Animacin S S No No No Calidad Media Alta Alta Alta Media Caractersticas Modelos sencillos de Quake II muy fciles de encontrar en internet. Modelos de Quake III, buena calidad pero no son fciles de encontrar en internet. Modelos de utilidad general, es un formato compatible con la mayora de programas de diseo 3D. Modelos de 3D Studio, uno de los programas de diseo 3D ms utilizados. Modelos sencillos, no todos ofrecen compatibilidad con Bennu 3D as que los probaremos antes de usarlos.
La disponibilidad de animaciones en el modelo 3D es esencial para personajes que vayan a tener cierto protagonismo o vida propia en nuestro videojuego. Para este fin utilizaremos principalmente modelos md2 dada la gran variedad de los mismos que encontraremos en internet. En cambio, para modelos estticos que puedan servir como decoracin y no precisen de animacin, podemos hacer uso del resto de formatos.
Tema 43 Modelos 3D
Pg. 1
43.2 Cargar modelos 3D La carga de modelos 3D pequeos (Recuerda que para modelos muy grandes y/o escenarios podemos usar las funciones del tema anterior), se realiza con una sencilla funcin: int M8E_LOADANIMODEL ( string ); El parmetro que recibe es la ruta del fichero con el modelo 3D a cargar, que debe tener uno de los formatos soportados/recomendados. La funcin nos retorna el identificador del modelo cargado, que deberemos almacenar dentro de una variable para luego aplicarle otras funciones para rotarlo, escalarlo, animarlo, etc. Observa que la carga del modelo 3D no especifica nada sobre su posicin, as que consideraremos que M8E_LOADANIMODEL ( ) simplemente cargar en memoria los modelos a utilizar y ser necesario que le apliquemos una serie de funciones para ubicarlo en el escenario. A diferencia de Bennu 2D, en el que las instrucciones de tipo load_xxx ( ) se ejecutaban al principio y una nica vez por cada fichero a cargar, permitiendo que varios procesos distintos hagan uso del identificador de ficher cargado, en Bennu 3D tendremos que cargar en distintas variables el mismo modelo para tener varios procesos que hagan uso de l.
43.3 Aplicar modificaciones modificaciones bsicas al modelo 3D Como ya comentamos en el tema 41, las operaciones bsicas que podemos realizar en 3D sobre un modelo son las rotaciones, escalados y traslaciones, en cualquiera de los 3 ejes. Para facilitar el trabajo con 3 coordenadas, Bennu 3D tiene predefinido un tipo de dato llamado _3dpos que es un TYPE con los campos float x, y, z. Muchas de las funciones de Bennu 3D requieren que se les pase una variable de tipo _3dpos con los valores 3D que queremos aplicar. Todas las operaciones bsicas se realizan sobre el identificador de modelo devuelto por M8EE_LOADANIMODEL ( ), y las funciones que se encargan de ello son las siguientes: M8E_MODELROTATION ( int, _pos3d ); Recibe como primer parmetro el identificador de modelo que queremos rotar, y como segundo parmetro una variable de tipo _pos3d en cuyos campos x, y, z debemos haber asignado el valor de rotacin que queremos para el correspondiente eje. En este caso, los valores de rotacin deben ser especificados en grados (No en milsimas), pero al ser campos de tipo float podemos establecer los decimales que queramos para la rotacin. Tema 43 Modelos 3D Pg. 2
M8E_MODELSCALE ( int, float, float, float ); Recibe como primer parmetro el identificador de modelo que queremos escalar, y como siguientes parmetros los valores de escalado en los ejes x, y, z que queremos aplicar al modelo. Observa que en este caso no es necesario utilizar una variable de tipo _pos3d, sino 3 valores float. M8E_POSMODEL ( int , _pos3d ); Recibe como primer parmetro el identificador de modelo que queremos escalar, y como segundo parmetro una variable de tipo _pos3d en cuyos campos x, y, z debemos haber asignado el valor de traslacin que queremos para el correspondiente eje. Para la aplicacin de las funciones anteriores, observa que tambin los escenarios vistos en el tema 42 retornan un identificador de modelo, y estas funciones tambin son aplicables a ellos, as que podemos rotar, escalar y desplazar el mapa durante la ejecucin.
Tema 43 Modelos 3D
Pg. 3
44. Introduccin a Bennu 3D Habrs observado que cambia mucho el chip cuando pasamos a Bennu 3D. Son muchas las diferencias, como por ejemplo la carencia de utilidad de todas las variables LOCAL predefinidas, la ausencia de FPG y scroll, la ausencia del buffer habitual de pantalla en el que podamos escribir cmodamente con write ( ), etc. Para asentar un poco las bases hemos introducido sobre escenarios y otros modelos, en este tema vamos a realizar unos cuantos experimentos prcticos
44.1 Modificar el escenario en tiempo real En el tema anterior ya explicamos cmo es posible aplicar funciones de rotacin, escalado y traslacin a cualquier identificador de modelo cargado con Bennu 3D, incluidos los escenarios. Ahora vamos a poner esto en prctica y vamos a utilizar funciones correspondientes para que en base a las teclas R, T, Y podamos rotar el escenario en cualquier eje, en base a las teclas F, G, H podamos escalarlo igualmente y en base a V, B, N podamos modificar su posicin en el mundo. Observa que con estas teclas podremos gestionar incrementos positivos en cada eje, para hacer decrementos necesitaramos obviamente otras tantas teclas. Se trata de un comportamiento que queremos que perdure a lo largo de la ejecucin, y por tanto debemos ir a un bloque LOOPEND, puesto que son los bloques de instrucciones en los que se repite el comportamiento. Observars que en el fichero de cdigo principal del videojuego de ejemplo tenemos un bloque LOOP-END en el que, como veremos ms adelante, se actualiza la posicin de la cmara. Ser ah donde introduzcamos el cdigo correspondiente para la modificacin del escenario. Algunas de las funciones de transformacin requieren que se les pase como parmetro una variable de tipo _pos3d en la que se almacenen los valores de transformacin a aplicar en cada eje. En primer lugar deberemos declarar unas variables PRIVATE a tales efectos: PRIVATE _pos3d rotacion; _pos3d escala; _pos3d traslacin; Tendremos que dar unos valores por defecto a las anteriores variables, en el caso de la rotacin asignaremos todos los campos a 0 para indicar que no queremos una rotacin inicial, en el caso del escalado asignaremos todos los campos a 1 para indicar que queremos un tamao normal y para la traslacin asignaremos todos los campos a 0 para indicar que no queremos que el escenario aparezca desplazado inicialmente.
Pg. 1
En cuanto a las instrucciones que tendremos dentro del bloque LOOP-END para gestionar las modificaciones, exponemos aqu el caso del escalado, puesto que todos los dems son anlogos. Suponemos que la variable mapa almacena el identificador del escenario cargado: LOOP ... IF ( key ( _F ) ) escalado.x = escala.x + 0.1; ELSIF ( key (_G) ) escalado.y = escala.y + 0.1; ELSIF ( key (_H) ) escalado.z = escala.z + 0.1; END M8E_MODELSCALE ( mapa , escala.x , escala.y , escala.z ); ... FRAME; END
44.2 Cargar modelos en la escena Vamos a cargar unos cuantos modelos en la escena y vamos a aplicarles diferentes rotaciones, escalados y traslaciones. Puedes encontrar modelos en el repositorio de Bennu, selecciona unos cuantos y no olvides tomar el fichero de imagen que llevan asociado, ya que es la textura y sin ella no podremos visualizarlos correctamente. Ya hemos visto cmo proceder para la carga, rotacin, escalado y desplazamiento, y puedes proceder de la misma forma que hasta ahora, pero nos faltar aplicar la textura. Para aplicar la textura, una vez cargado el modelo con M8E_LAODANIMODEL ( ) y almacenado su identificador en una variable, podemos hacer uso de las siguientes funciones: int M8E_LOADTEXTURE ( string ); Recibe como parmetro la ruta del fichero de imagen que guarda la textura que queremos cargar. El fichero puede tener prcticamente cualquiera de los formatos de imagen habituales (.png, .bmp, .jpg, .gif, .tga, .pcx...). La funcin retorna un identificador de la textura cargada, que podremos usar ms adelante para aplicarla a cualquier modelo que hayamos cargado. Cada modelo tiene asociada una textura y podremos editarla, teniendo siempre en cuenta que el mapeo de los polgonos sobre la textura siempre se realizar de la misma forma. Tema 44 Introduccin a Bennu 3D Pg. 2
M8E_LOADTEXMODEL ( int , int ); Asocia al modelo cargado con M8E_LOADANIMODEL ( ) cuyo identificador damos como primer parmetro la textura cargada con M8E_LOADTEXTURE ( ) cuyo identificador damos como segundo parmetro. Con todo esto slo queda realizar unas cuantas pruebas que nos servirn para seleccionar los modelos para nuestro videojuego, como ltima recomendacin no olvides que puedes separar el videojuego en mdulos de la misma forma que lo hemos hecho hasta ahora. Conviene tener todo lo ms ordenado posible, as que tambin sera recomdable que generes un directorio /map para los escenarios y otro directorio /model para los modelos.
Pg. 3