Tutorial. Mini Plaga Galáctica 2

De Xitai-E Wiki
Saltar a: navegación, buscar



Objetivos

  • Puntuación, records.
  • Persistencia de variables.
  • Efectos sonoros.
  • Fondo de estrellas.
Descargas. Versión 0.41


Sistema de puntuación

Los marcadores

Pantalla de menú

Empezaremos añadiendo a las pantallas pertinentes un Widget compuesto que representa los marcadores. Para no repetir el mismo trabajo en cada pantalla, vamos a definir los marcadores una vez y reutilizarlos en todas las pantallas que queramos. Aprovecharemos para ello el CloneWidget, un tipo de widget que toma su forma de un modelo. El modelo será el grupo de marcadores. Vamos a ello:

Para separar los modelos de las auténticas pantallas crearemos una carpeta en la rama Screens, que podemos llamar Modelos. Dentro de ella crearemos un objeto de clase Widget que llamaremos, claro está, "marcadores". Como hijos de éste crearemos tres objetos de la clase NumberWidget que llamaremos "score", "level" y "record". Este tipo de widget representa números y fechas. Como todos los widget que presentan textos hay que asignarles un TextStyle.

Con la ventana central en la pestaña Screen seleccionamos el widget "marcadores". Deberíamos ver tres ceros espaciados verticalmente. Como lo que queremos es que aparenzcan organizados horizontalmente en la parte de arriba de la pantalla, editaremos algunos atributos del widget "marcadores". Serán estos:

  • flow: horizontal.
  • expand: horizontal.
  • size: La altura 40, más o menos. Depende de la tipografía que usemos. La anchura no importa ya que el objeto se expandirá horizontalmente.

El resto de atributos se dejarán como vienen.

Cada uno de los marcadores puede ser de distinto color, o podemos usar una tipografía más pequeña que la que estamos usando hasta ahora. Como se explicó en el tutorial anterior, podemos definir una tipografía de distinta escala reutilizando la misma imagen y métrica de caracteres que ya tenemos.

Pantallas

El modelo así creado lo vamos a incorporar a tres pantallas: "menú", "juego" e "intermedio".

Añadiremos a la pantalla "menú" un objeto de la clase CloneWidget que colocaremos el primero de la lista de hijos. Cambiamos su atributo model para que contenga el widget "marcador". Visualizaremos la pantalla para ver como queda.

El objeto CloneWidget es un poco especial y no cambia en tiempo real cuando cambia el modelo. Por razones de eficiencia sólo se actualiza al comienzo de la representación de la pantalla donde está y mantiene una cache para posteriores usos. Por ello es necesario utilizar el botón de prueba (Start.png) para ver como queda. Este tipo de optimización también puede originar que temporalmente queden referencias ocultas a objetos.

Para que los marcadores se sitúen en la parte superior de la pantalla, debemos hacer que en el widget de la pantalla "menú" el atributo gravity de "juego" sea start y flow, vertical.

Para que la pantalla quede mejor organizada, insertaremos un widget entre los marcadores y el texto y añadiremos otro widget al final. Quedará semejante a lo que se ve en la imagen anterior.

También insertaremos un CloneWidget similar al principio de las pantallas "juego" e "intermedio". A esta última le aplicamos el mismo tratamiento que a "menú.


Las variables

En el apartado anterior hemos visto una forma de representar los marcadores, pero no tenemos la fuente de los datos, o sea, unas variables que almacenen la puntuación, cuantas pantallas hemos avanzado y el record. Estas variables deberíamos poder manipularlas mediante nuestras máquinas de estado.

La rama de objetos llamada Persistence es un poco especial. Para empezar ya viene cargada con algunos objetos obligatorios. Estos objetos tienen la peculiaridad de que muchos de sus atributos (los que tienen su nombre en negrita) son persistentes . Esto quiere decir que se conservan entre sesiones del juego. Lo que valían cuando cerramos el programa del juego es lo que valdrán cuando volvamos a abrirlo.

Hay una clase de objetos prevista solo para mantener las variables que necesitemos. Es Data. Aunque en este objeto no tenemos los atributos que necesitamos (score, level y record). Afortunadamente disponemos de los medios para agregarlos. El procedimiento consiste en crear nuestra propia clase, derivada de Data, a la que podremos agregar los atributos que deseemos. Vamos allá.

En la ventana flotante Class Browser podemos ver la lista de todas las clases que pueden ser derivadas o cuyos objetos pueden ser referenciados en el contexto. (Tendremos que abrir dicha ventana, desde el menu Windows, si no la tenemos ya abierta). Aquellas clases que tienen un icono Add.png, son las que podemos extender creando otras derivadas de ellas.

Crearemos una clase derivada de Data pulsando el icono situado a la derecha del nombre de la clase. En la ventana flotante Class Inspector veremos la definición de la clase resultante. (Tendremos que abrir dicha ventana, desde el menu Windows, si no la tenemos ya abierta). En esta ventana tendremos siempre la clase que habremos seleccionado en el Class Browser.

Observemos que el nombre de la clase derivada es igual que el de la clase base con un sufijo añadido. Nosotros podremos cambiar el nombre de la clase por el que prefiramos, aunque no es necesario. Observemos, también que hay un botón para añadir atributos nuevos. Bien, pulsémoslo.

Editor de atributo

Nos aparece un cuadro de diálogo para definir el nuevo atributo. El primer dato es el nombre del atributo. Es importante elegir un nombre apropiado, porque ello nos ayudará en nuestro trabajo. Escribimos score. El siguiente campo es el tipo de dato del atributo. Elegimos int (número entero). El siguiente campo depende del tipo de dato que hayamos elegido, indica el modo en que se va a editar el dato. Elegiremos Number. Para terminar pulsamos OK.

Crearemos dos atributos similares más con nombres "level" y "record".

Ahora crearemos un objeto, de la clase que acabamos de definir, en la rama Persistence/data. Le llamaremos "player". Y ya tendremos las variables.

El siguiente paso es hacer que sean representadas en los widgets correspondientes. Eso se consigue conectando los atributos del objeto player con el atributo value del objeto correspondiente en los marcadores. El procedimiento es el siguiente.

En el Object Inspector vemos que hay un botón amarillo entre el nombre y el valor de algunos atributos. Eso quiere decir que pueden ser origen de una conexión. Pulsaremos el correspondiente al atributo "score" del objeto "player" recién creado. Veremos que algunos botones de conexión se han vuelto rojos, que quiere decir que son válidos para una conexión de destino, y otros se han vuelto grises. que significa que no lo son.

En la barra de herramientas principal, en el indicador de modo de conexión, ha aparecido la especificación del atributo de origen de una conexión en progreso. Si pulsáramos el botón Kill.png anularíamos dicho proceso, luego no lo pulsemos de momento.

En el Object Browser seleccionemos el objeto "score" (clase NumberWidget) de "marcadores". Pulsaremos el botón rojo que hay en el atributo value. Hecho esto, en el campo valor se mostrará la especificación del atributo de origen de la conexión.

Ahora pulsamos el botón Kill.png del indicador de conexión en progreso, de la barra de herramientas, para terminar. Los botones de conexión volverán a ser amarillos.

Repitamos la operación con los atributos "level" y "record". Para ayudarnos en esta operación podemos usar el sistema de marcadores (Prev.png, Next.png, Bookmark.png) del Object Inspector.

Para ver el efecto de lo que hemos hecho, mostremos la pantalla "menu" en la pestaña Screen en modo prueba (pulsando Start.png). Luego, en el objeto "player" cambiemos el valor del atributo "score", "level" o "record". Observaremos como cambia el valor representado en la pantalla.


Llevando la cuenta

La clave para llevar la cuenta va a ser SetNumberAction. Este es un estado/acción que nos permite manipular atributos numéricos.

Empezamos por lo fácil. Sumaremos 1 a "level" y 150 a "score" cada vez que nos carguemos una oleada de marcianos. El mejor sitio es en el estado "intermedio" de la máquina de estados del juego ("RUN"). Añadiremos un objeto de clase CompositeAction (llamémosle "actualiza_vars") para agrupar en él dos objetos de clase SetNumberAction. Trazaremos una transición desde "intermedio" hasta "actualiza_vars" con una condición de clase StateEnterCondition (recordemos que para usar esta condición basta dejar la transición vacía).

Añadiremos, pues, a "actualiza_vars" dos objetos de clase SetNumberAction. Conectaremos el atributo score del objeto "player" (origen) al atributo attribute del primero (destino) y haremos lo mismo con level y el mismo atributo del segundo.

En el atributo op de ambos elegiremos la operación ADD y en value escribiremos 150 y 1, respectivamente.

Ahora podemos probar el juego. Deberíamos poder observar que el marcador "score" y el "level" crecen cada vez que destruimos una oleada de marcianos. Aunque nos falta un pequeño detalle, las dos puntuaciones deberían empezar en 0 al comienzo del juego.

Pantalla de record

Nos falta inicializar los atributos. Esto podríamos hacerlo en el estado "menu" pero quedaría mal no poder leer la última puntuación conseguida, así que, es mejor, hacerlo en un estado intermedio entre "menu" y "juego". Podríamos llamarlo "game_start". La transición que hay ahora entre "menu" y "juego" (disparada por la pulsación de una tecla) se convierte en la que habrá ente "menu" y "game_start". En este estado podemos poner una pantalla que diga, por ejemplo, "¡Nueva misión!" y se demore sólo un segundo.

Después de esto podemos enlazar a "game_start" una CompositeAction con dos hijos SetNumberAction Conectados de la misma forma, pero, en este caso, el atributo op será SET y value 0. Podemos ahorrarnos algún trabajo haciendo una copia del objeto "actualiza_vars" usando drag'n drop y eligiendo la opción Copy.

Luego veremos cómo puntuar nuestra buena puntería, ahora vamos a contabilizar el record de puntos. Necesitaremos una nueva pantalla para mostrar que hemos batido el record. Como éste es un juego minimalista basta con una parecida a la que vemos en la imagen anterior.

El valor numérico que aparece será un NumberWidget que represente el atributo record del objeto "player".

Crearemos un estado nuevo para esta pantalla que llamaremos "record". Habrá una transición desde el estado "game_over" que se disparará con una condición de clase CompareCondition. Esta condición, como su nombre indica, nos permite comparar dos atributos numéricos. Conectaremos el atributo score de "player" con el atributo value1 de la condición y el atributo record de "player" con el value2 de la condición. Como operación de comparación elegiremos >. Es decir, la condición se cumple cuando score es mayor que record.

La transición desde el estado "record" al estado "menu" se producirá después de un tiempo de uno o dos segundos.

El record ha sido detectado, ahora hay que copiar el valor de score en record. Volvemos a utilizar SetNumberAction enlazada al estado "record" vía StateEnterCondition. En este caso la operación será SET, el atributo manipulado record de "player", y el valor el atributo conectado score, de "player".

El diagrama de la máquina de estados quedaría como la imagen siguiente.

Máquina de estados del juego


Puntuando la puntería

Para puntuar la destrucción de un marciano, una bomba o cualquier otro personaje que se nos ocurra, vamos a crear un objeto reutilizable. Aprovecharemos las conexiones de atributos desde un objeto del contexto.

Crearemos un nuevo personaje que sirva para representar los puntos obtenidos en forma de globito, así como para añadirlos al score. La forma de usarlo es creando un actor de este personaje cada vez que un actor es destruido, además de crear la explosión, por ejemplo.

Llamémosle "bonus" y será de la clase NumberCharacter. Esta clase de personajes permiten visualizar un número y, si se desea, un icono superpuestos a una animación. Lo parametrizamos de la siguiente forma:

Bonus
  • Le añadimos la imagen "nubecilla.png" al atributo movies.
  • En size ponemos un valor apropiado, por ejemplo: [95,65].Siempre podremos ajustarlo después.
  • Activamos el atributo scale para que la imagen se adapte a las dimensiones.
  • Elegimos una tipografía pequeña.
  • Seleccionamos center en align.
  • Como este personaje es efímero asignamos 1000 (1 segundo) a life, que indica el tiempo de vida en milisegundos.
  • Y ahora viene lo bueno. Vamos a realizar la conexión del atributo value del la clase Character, con el atributo value del objeto concreto "bonus". Nótese que no especificamos el objeto de origen concreto, el objeto está determinado por el contexto. Veamos primero el procedimiento y después la explicación:

Debemos tener abierta las ventanas flotantes Class Inspector y Class Browser y seleccionar en ésta última la clase Character.

En el Class Inspector aparecerá desplegada la descripción de la clase. Seleccionamos el registro de contexto OWNER en el desplegable de su barra de herramientas y usamos el botón amarillo del atributo value y continuamos el proceso de conexión, como ya hemos aprendido, pulsando el botón rojo que aparece junto al atribute value del objeto "bonus".

Durante la ejecución de una máquina de estado de clase ActorMachine, el registro OWNER del contexto contiene el Character del actor al que gobierna.

En el caso que nos ocupa, en el momento de la creación del actor de Character "bonus", el contexto es el Character "bomba", si lo creó la bomba o el Character "platillo" si lo creó la máquina de estados del platillo.

Probemos la creación de un "bonus". En la máquina de estados del platillo, a la CompositeAction "muere", agregaremos una CreateActorAction que cree un "bonus". Debemos hacer que esta acción se ejecute después de la creación de la explosión, para que el globo se vea superpuesto a ella y antes de la acción ActorDeadAction, puesto que ésta es el final del actor.

Si ahora probamos a matar un marciano, veremos el globo durante un segundo. Debemos de poner el valor adecuado en el atributo value del Character "platillo", no querremos que sea 0. Hagamos lo propio con la bomba y probemos. La bomba debería valer el doble o más que el platillo, porque es más difícil de acertarle.

Bien. Obviamente aún nos queda sumar los puntos al score.

Crearemos una máquina de estados para "bonus" con los siguientes estados:

  • ActorMoveState. Estado en el que estará el actor todo el tiempo. Nos permite darle algo de dinamismo. Asignamos al atributo velocity [0.05,0.05] para que se mueva lentamente en diagonal.
  • SetNumberAction. Acción conectada con el estado anterior y activada por la condición StateEnterCondition. Incrementará el atributo score del objeto "player" con el atributo value de la clase Character (conexión de contexto).

Si en el mismo ciclo se destruyen una bomba y un platillo con la misma bala (difícil pero posible) el valor obtenido de la clase Character por la máquina de estado es incierto. Afortunadamente tenemos solución para este problema pero lo dejaremos para otro tutorial más avanzado.

Probemos, exportemos, y ¡a matar marcianos!.


La persistencia

Algo falla, ¿no?. El record debería ser recordado de una ejecución a otra del programa, y si se exportó el juego con un record dado no debería de aparecer en el marcador. La solución es activar la persistencia de las variables.

Lo primero que debemos hacer es volver a editar la definición de los atributos "score", "level" y "record" para activar su propiedad de persistencia. (checkbox persistent). Cuando hagamos ésto, su nombre se mostrará en negrita en el ObjectInspector.

Después de hacer todo ésto probemos de nuevo.


Sonidos espaciales

Ya sabemos que en el espacio, por la falta de aire, no hay sonido pero una batalla espacial silenciosa, como nos ha enseñado el cine, pierde mucho. Por ello vamos a acompañar la acción con unos bonitos sonidos de explosiones y disparos.

Lo primero es importar los sonidos. En la carpeta Audio añadimos un par de objetos de la clase AudioClip. Navegaremos por los archivos del proyecto hasta encontrar y seleccionar "explosion.ogg" y "zing2.ogg". De forma semejante a las imágenes podemos importar en bloque todos los audios seleccionados. El nombre de los objetos es inicialmente el del archivo pero podremos cambiarlo si queremos.

El siguiente paso es lanzar el sonido correspondiente en la situación requerida.

En la máquina de estado de la explosión, añadimos un estado de la clase PlayAudioAction. En su atributo audio asignamos "explosion.ogg". Esta acción estará activada por una StateEnterCondition desde el estado "inicio".

Haremos lo equivalente en la máquina de estados de la bala. En este caso el sonido será "zing2.ogg".

Si lo probamos ahora veremos que las explosiones y los disparos suenan, aunque no siempre que debieran. Lo que está pasando es que, por defecto, un sonido no suena más de una vez simultáneamente. Para cambiar eso debemos ir a los objetos AudioClip correspondiente y modificar el atributo max a un valor adecuado, por ejemplo 8.

El número máximo de efectos sonoros simultáneos está especificado el el objeto audio de la rama Configs. Atributo channels.

Añadiendo un fondo estrellado

Fondo estrellado

Mejoremos la ambientación. Pongamos un fondo estrellado y nebuloso. Que se vea que estamos en el espacio sideral.

Volvemos al editor de escenarios y añadimos una capa gráfica (clase ImageLayer) al único escenario que tenemos de momento. Nos aseguraremos de que sea la primera capa para que se pinte antes que las demás, no sea que los marcianos queden detras de las estrellas y nebulosas.

Usaremos la imagen "estrella.png" que es una especie de glóbulo blanco. Para colocarla en la capa gráfica, la arrastramos desde el Image Browser hasta la ventana central en la posición deseada. Podemos cambiar el tamaño, y el color desde el editor de la capa en el Object Inspector. Cuando tengamos una estrella que nos guste la podemos copiar las veces que queramos.

Con esta imagen y las últimas de la explosión, dando rienda suelta al artista que llevamos dentro, podemos conseguir un fondo espacial de lo más chulo.

No queda mal, pero es algo estático. Sería mucho mejor si se desplazara lentamente hacia abajo, daría la sensación de que perseguimos a los marcianos. Para conseguirlo, basta con darle una velocidad a esa capa. En el atributo velocity escribimos (0, 4). También necesitamos hacerla cíclica en la dimensión vertical, si no acabará desapareciendo la capa entera. Para ello asignamos [0, 1200] al atributo cycle.

Comprobemos el resultado.


En el siguiente tutorial hablaremos de:

  • Avances de nivel.
  • Composiciones de actores.
  • Mejores técnicas de control de actores usando nodos y caminos.
  • Ataques de los marcianos.
  • Preparar una versión para pantalla táctil.
  • Crear juego para Android.

Tutorial. Mini Plaga Galáctica 3