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.48


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 Pantallas, que podemos llamar Modelos. Dentro de ella crearemos un objeto de clase Widget que llamaremos, claro está, "marcador". 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 "marcador". 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 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 de widgets intercalados que a "menú".

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


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 sólo 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, en el área lateral, 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 lateral Class Inspector veremos la definición de la clase resultante. 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. 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 construyendo una expresión en el atributo value del objeto correspondiente en los marcadores. El procedimiento es el siguiente.

En el Object Inspector vemos que hay un botón verde entre el nombre y el valor de algunos atributos. Eso quiere decir que son expresiones que se evalúan en diferentes momentos de la ejecución del programa. Vamos a construir que simplemente haga referencia al valor de una de las variables anteriormente definidas.

Expr button.png

Para ello pulsamos la parte izquierda del botón verde que acompaña al atributo value del widget "score". Esto causa el despliegue del selector de funciones, métodos, atributos y literales especiales para expresiones (Véase la figura).

Selector de funciones
Selector de métodos y atributos

En el menú superior del selector pulsamos sobre Attributes. Y, después mediante el botón que aparece inmediatamante debajo, seleccionamos la clase que hemos definido anteriormente (Data_2 o como la hayamos renombrado). Esto nos muestra la lista de atributos y métodos de dicha clase. En este caso aparacerán las tres variables "level", "record" y "score". Seleccionamos "score" y el atributo cambia a la forma siguiente.

Expr Simple.png

Si ahora pulsamos el botón debajo de la etiqueta aparecerá el nombre del único objeto de la clase mencionada. Si hubiera más de un objeto disponible se nos mostaría el selector de objetos.

Repitamos la operación para los atributos "level" y "record" y los widgets respectivos.

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 ProcedureAction. Este es un estado/acción que nos permite ejecutar expresiones arbitrarias que no devuelven ningún valor. 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 ProcedureAction (llamémosle "actualiza vars"). Trazaremos una transición vacía desde "intermedio" hasta "actualiza vars".

Expr simple2.png

En el atributo op construiremos un expresión que añade 1 a "score". Para ello usaremos la función add!, (categoría Arithmetic en el desplegable). El primer parámetro es el atributo que queremos incrementar, editado como en el paso anterior. El segundo es un número, 1 en este caso.

Aparentemente no se nos permite añadir una nueva operación. Para conseguirlo debemos transformar la expresión en una sequencia de expresiones. Con la parte derecha del primer botón verde del conjunto nos muestra un menú. Debemos elegir Sequence. Ahora, con el pequeño botón Add.png podremos añadir una nueve expresión vacía. La hacemos similar a la anteror pero para "record" y con el incremento de 150. Observemos la figura como referencia del resultado.

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.

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á entre "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.

Expr simple3.png

Después de esto podemos enlazar a "game start" una ProcedureAction con una secuencia de expresiones que inicialicen a 0 "level" y "score". Cuando desplegamos el selector de métodos para la clase que contiene las variables de puntuación, se tiene en cuenta el contexto de la expresión. En este caso nos propondrá sendas formas de modificación de las variables mediante el operador set!.


¡Record!

Pantalla de record

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 de la izquierda.

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 ExpressionCondition. Esta condición dispone de una expresión cuyo resultado lógico es el resultado de la condición. Ahora la usaremos para comparar score y record. Como función (Categoría "Comparisons") 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 ProcedureAction enlazada al estado "record".

Expr simple4.png

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.

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. El número que se visualiza es el valor del atributo actor_value del Actor que, a su vez es inicializado con el valor del atributo value del Character que lo creó.

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 conseguir que, en el momento de la creación del actor, el valor del atributo value de su NumberCharacter sea tomado del value del Character del actor que lo crea. Veamos primero el procedimiento y después la explicación:

Comenzamos la expresión con el atributo value de la clase Character. El argumento sería el atributo character de la clase Actor. El argumento de éste sería la función _OWNER. Al elegir esta función en el selector, automáticamente se antepone un operador de adaptación (casting), si es necesario, para asegurarse de que es evaluado según el tipo correcto. En notación compacta, la expresión sería la siguiente:

value (character (#Actor _OWNER))

La función _OWNER no tiene argumentos y devuelve el objeto que está siendo controlado por la máquina de estado vigente. En este caso sería la máquina de estado del actor que crea el "bonus".

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 ActorKillAction, puesto que ésta es el final del actor.

Si ahora probamos a matar un marciano, veremos el globo durante un segundo. Debemos de poner un valor adecuado en el atributo value del Character "platillo", por ejemplo 20. Hagamos lo propio con la bomba y probemos. La bomba debería valer el doble o más que el platillo, por ejemplo 50, 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.
  • ProcedureAction. Acción conectada con el estado anterior, Incrementará el atributo score del objeto "player" con el atributo actor_value del sí mismo (#Actor _OWNER). La expresión es la siguiente:
add! (score (@player),
   actor_value (#Actor _OWNER))

Probemos, exportemos, y ¡a matar marcianos!.


La persistencia

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 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 correspondientes y modificar el atributo max a un valor adecuado, por ejemplo 8.

El número de canales del mezclador de audio determina el número máximo de efectos sonoros simultáneos. Está especificado en atributo channels del objeto audio de la rama Configs.

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