Tutorial. Mini Plaga Galáctica 1

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



Objetivos

Mini Plaga Galáctica 1

Nuestro objetivo, en éste primer tutorial, es construir un juego sencillo, aunque completo.

La idea básica es la del clásico juego mata marcianos: una nave, en la parte inferior de la pantalla, que podemos mover a izquierda y derecha con el teclado. Pulsando otra tecla disparamos contra los "marcianos". Éstos están en la parte superior de la pantalla y se mueven lateralmente, en formación, con pasos rítmicos.

Los marcianos dejan caer bombas que pueden matar a la nave protagonista (llamémosle prota). El juego acaba cuando muere el prota. Si acabamos con todos los marcianos comienza una pantalla nueva. En éste primer tutorial no contabilizaremos la puntuación ni habrá ataques de los marcianos o cambios de nivel. Eso lo dejaremos para el siguiente.

El movimiento en formación de los marcianos lo conseguiremos mediante un personaje invisible, el guía, que es el que se mueve con pasos rítmicos, mientras que los marcianos se limitan a seguir unos puntos relativos a él. Eso nos permitirá fácilmente, en el futuro, hacer que los marcianos ataquen y vuelvan a la formación después del ataque.

Los procedimientos usados en este tutorial no son necesariamente la forma mejor o más simple de hacer las cosas, sólo muestran una forma de hacerla que ejemplifican ciertas funcionalidades.


Pasos iniciales

Creación del proyecto

Lo primero es, por supuesto, instalar el editor.

Descargas. Versión 0.41

Una vez descomprimido el archivo en una carpeta de nuestra elección se puede ejecutar sin necesidad de ninguna otra operación.

Para iniciar un proyecto tenemos que crear una carpeta donde se almacenará todo su contenido. Con la opción de menú File/New decidimos el nombre y el lugar del archivo inicial. Podemos llamarle como queramos, por ejemplo: "plaga1.xo" (la extensión ".xo" significa Xitai Objects, aunque puede usarse cualquier otra extensión). Cuando guardemos el proyecto en el disco duro aparecerá un archivo de igual nombre y la extensión ".book" añadida. Ahí se mantienen los marcadores (bookmarks).

En esta carpeta se creará todo el contenido temporal o permanente del proyecto. Por si acaso, es conveniente usar con frecuencia la opción de menú File/Save (o el botón Save Project Save.png) para conservar los cambios que se hagan. Si salimos del programa antes de hacer esta operación se perderá nuestro último trabajo.

Importación de las imágenes

Para ahorrarnos la creación de imágenes y efectos de sonido descarguemos un archivo con algo de material desde el siguiente enlace:


Descomprimamos el contenido en el directorio del proyecto y, desde el editor, importemos todas las imágenes. Estos son los pasos:

  1. En la ventana exploradora de objetos (Object Browser), con el botón Add.png de su barra de herramientas, creamos una carpeta (FOLDER) que renombramos, por ejemplo, como Images. Con el cursor del ratón sobre ella, pulsamos el botón derecho y nos aparecerá el menú contextual.
  2. En él seleccionamos "Add Object" y en el selector de clase que aparece a continuación elegimos BitmapImage (está en el grupo Resources).
  3. Nos aparece el selector de archivos. Buscamos la carpeta donde hemos cargado las imágenes, las seleccionamos todas y pulsamos Open (o como se llame el botón de aceptar en nuestra instalación).

Así obtendremos un conjunto de objetos nuevos en esta rama que representan cada imagen importada. Las imágenes de tipo bitmap pueden ser recortadas a su tamaño justo, para que ocupen menos memoria. La operación se realiza en el editor ajustando el origen de cada imagen para que en las secuencias no se altere la posición relativa entre ellas. Inicialmente, el origen se situa en la esquina superior izquierda, pero la práctica nos indica que es más manejable e intuitivo que el origen esté en el centro.

El editor de propiedades (Object Inspector) de las imágenes y de las carpetas que contienen imágenes disponen de un botón que sirve para centrar el origen (Centre). Y las de tipo bitmap pueden ser recortadas con el botón Optimize. Así pues, haremos estas operaciones en bloque usando, primero, el botón Centre y luego el botón Optimize en el editor de propiedades de la rama Images. (Atención: el orden es importante).

Creación de un escenario

Necesitamos, al menos, un escenario donde desarrollar la acción del juego. Para ello creamos la carpeta Stages y en ella creamos un objeto de la clase Stage (Recordemos, con la opción 'Add Object' del menú contextual). Se creará, automáticamente, un archivo que contenga este recurso. Los escenarios, como las imágenes, las animaciones, los sonidos y las tipografías, son recursos que se mantienen como archivos individuales.

Los escenarios se construyen a base de capas. Crearemos una capa de actores (clase ActorLayer) como hija del escenario. En esta capa desplegaremos los personajes iniciales del juego. También la utilizaremos para hacer algunas pruebas.

También necesitaremos una capa de física ShapeLayer. La crearemos también. Cuando hay muchas capas en un escenario es buena idea darles un nombre significativo. Pero cuando hay pocas, o sólo una de cada clase, el icono que indica la clase de la capa suele bastar para indentificarlas claramente.

Creación de personajes

Vamos a crear un primer personaje: la bomba. Para ello añadimos un objeto de la clase BodyCharacter a la carpeta Characters que crearemos previamente. Llamémosle bomba. Una vez creado un objeto, se puede editar su nombre haciendo doble click sobre él. Dejamos el valor de los atributos tal como vienen por defecto.

A este personaje le faltan tres cosas muy importantes: su apariencia visual, su forma física y su comportamiento.

Para conseguir lo primero añadamos una imagen (bomba.png) al atributo movies del personaje.

Para darle la forma física vamos a la pestaña Actor y seleccionamos el personaje bomba. Deberemos tenerlo representado en el centro de la ventana principal. Usamos el botón de zoom (o la rueda del ratón) para ampliar y ver mejor lo que haremos.

Con la herramienta Rectángulo (Rectangle.png) creamos un rectángulo y lo adaptamos para que cubra la imagen de la bomba. Éste rectángulo va a definir los límites físicos del personaje. Podríamos dibujar un polígono que se adapte mejor al contorno, pero en este caso no es necesario, basta un rectángulo para detectar el choque con la nave protagonista. A esta forma así creada hay que darle masa. Esto es muy importante, ya que si no lo hacemos sería inamovible. Para definir la masa se le asigna un valor mayor que 0 al atributo density de la forma. Este atributo aparece en la sección current shape del editor de propiedades del personaje cuando la forma está seleccionada. Nos vale con el valor por defecto, 1.

Ahora, en la pestaña Stage, seleccionamos la capa ActorLayer del escenario mediante la lista desplegable de su barra de herramientas. Arrastraremos el personaje bomba a la parte superior de la capa y probaremos su dinámica pulsando el botón Start.png. La bomba debería caer hasta perderse en la parte de abajo.

Cuando la bomba desaparece de la pantalla no quiere decir que deje de existir en el programa y por ello, sigue usando memoria y recursos, por lo que en un caso como éste, en el que se crearán bombas continuamente, es necesario que se vayan destruyendo a medida que dejen de tener valor. Esa es la primera función que vamos a conseguir con una máquina de estados.

Primero vamos a preparar un método para detectar el momento en que debe de destruirse la bomba: En el editor de escenarios, activamos la capa de física (clase ShapeLayer) y añadimos un rectángulo en la parte de abajo que cubra el escenario de izquierda a derecha. Cuando probemos el escenario veremos que la bomba se queda apoyada en dicho rectágulo. Haremos que la máquina de estados destruya la bomba cuando detecte esa colisión.

Crearemos un objeto de clase ActorMachine en la carpeta Behaviours. Llamémosle, así mismo, bomba. En la pestaña Machine con el comportamiento bomba seleccionado agregamos un estado de clase VoidState (grupo Structural) y una acción de la clase ActorDeadAction (grupo Actor Control). Renombremos estos estados como "inicio" y "final" respectivamente.

Tanto los estados como las acciones se pueden crear usando la opción Add State del menú contextual del editor de máquinas de estados o con el método habitual de crear hijos mediante el menú contextual del explorador de objetos. En este último caso los globos que los representan aparecerán superpuestos en el centro del espacio de trabajo.

Situamos los globos que los representan en las posiciones que nos gusten y los conectamos con una transición a la que añadimos una condición de la clase CollideWorldCondition (grupo Actor Collision).

La transición la creamos conectando los bordes de los respectivos globos con una línea, usando el ratón en el espacio de trabajo. La condición la creamos como hija de la transición en el explorador de objetos.

La máquina de estados así definida la asignamos al personaje bomba (atributo machine) y probamos el escenario. La bomba debería desaparecer al chocar con el rectángulo anteriormente añadido a la capa de física del escenario.

Exportación del paquete

Aún no podemos usar la pestaña Game ni exportar el juego para ejecutarlo individualmente porque no es completo. Para que el proyecto pueda considerarse un juego completo, aunque mínimo, se necesitan algunos objetos esenciales: tenemos que definir un tipo de empaquetado del juego. Una máquina de estados que controla el juego globalmente, y una o más pantallas que representan los diferentes estados globales del juego.

  • Crearemos un objeto de clase StageWidget en la carpeta Screens y le asignaremos al atributo stage una referencia al escenario que hemos creado.
  • Crearemos un objeto GameMachine en la carpeta Games. Le añadiremos un estado de la clase ScreenState. En su atributo screen le asignaremos la StageWidget que hemos creado.
  • En la rama Packages, crearemos un objeto de clase Package. En el atributo machine seleccionamos la GameMachine creada y en atributo dest debemos de escribir el nombre de la carpeta del sistema de archivos (relativa al proyecto) donde se va a generar el paquete.

Ahora podemos probar el juego en la pestaña Game y exportarlo con el menú Make Pakage o el botón correspondiente (Package.png) de la barra de herramientas general.

Al exportar el contenido tenemos la opción de incluir todos los archivos necesarios para distribuir el juego. En el caso de la versión de ordenador podremos ejecutarlo como un programa independiente después de la exportación.

Cuando se efectúa la exportación se crea una carpeta adicional (de nombre xi). Si estamos usando algún sistema de control de versiones tendremos que ignorarla. También deberíamos ignorar la carpeta de exportación del juego.


Completando los personajes del juego

Es el momento de decidir las dimensiones de la ventana del juego. Empezamos por determinar las dimensiones del escenario. Vamos a usar un formato vertical, como el original de las máquinas de salón. En el escenario establecemos los límites (atributo size) en 800,1200.

Las dimensiones del display la indicamos en los atributos width y height del objeto de clase Package. Usaremos 500 y 750, respectivamente.

La resolución visible depende de la escala con que se visualice. Esta escala se elige automáticamente de forma que no queden huecos en la pantalla al representar el escenario. El límite en esta escala viene indicado por el atributo zoom_auto en el StageWidget que representa el escenario. No necesitamos preocuparnos de esto, de momento, ya que estableceremos la resolución virtual igual a la física por lo que el zoom automático será 1.0. La resolución virtual la declaramos en el atributo virtual height del Package. Lo hacemos coincidir con la altura del escenario (1200).

Movamos ahora la posición del rectángulo que usamos para detectar el fin de la vida de la bomba de forma que quede justo por debajo de los límites del escenario. Ajustemos también sus dimensiones para que cubra todo el ancho del mismo.

El guia de los platillos

Necesitamos crear un pequeño enjambre de marcianos. Los marcianos deberán moverse en formación pero conviene que puedan salir y volver al enjambre de vez en cuando. Esto es sencillo de conseguir con un personaje especial, el guía del enjambre, que podrá ser invisible si es conveniente. Los marcianos, cuando estén en el enjambre, se moverán siguiendo un punto relativo a la posición del guía.

En el juego original, en el que nos inspiramos, el enjambre de marcianos se mueve un trecho, se para un momento y se mueve otro trecho, así recorre la pantalla de un lado a otro y luego en el sentido inverso, cíclicamente.

nodos de referencia

Para conseguir un comportamiento similar vamos a utilizar unos puntos de referencia que representan las pausas. Crearemos en el escenario una capa nueva de la clase PathLayer. Insertaremos en ella tres nodos situados cerca de la parte superior del escenario, separados entre sí unos 150 pixels, aproximadamente. (Ver las instrucciones de la capa de rutas).

Estos nodos los vamos a etiquetar para referirnos a ellos posteriormente. Todas las etiquetas que usemos tendremos que crearlas previamente. Para ello basta con crear los objetos que queramos en la rama Symbols/Labels y nombrarlos a nuestro gusto. La etiqueta, ya existente, "-None-" simbolizará ninguna etiqueta. Crearemos, pues, tres etiquetas que llamaremos "P1", "P2" y "P3" y las asignaremos a cada uno de los nodos anteriores (atributo label) en la sección current node del editor de atributos del layer.

Después de esta preparación vamos a por el personaje propiamente dicho: a la carpeta Characters añadimos un objeto de la clase SpriteCharacter. Llamémosle "guía". Para que sea visible mientras lo depuramos le podemos añadir una imagen (la de la bomba, por ejemplo). Luego se la quitaremos, ya que nos interesa que sea invisible.

La diferencia entre un SpriteCharacter y un BodyCharacter estriba, esencialmente, en que el segundo tiene corporeidad física, tiene inercia, está sujeto a fuerzas, choca con el escenario y otros actores, etc, mientras que el primero es una imagen o animación que no influye en los demás actores.

Máquina del guía

Ahora definimos su comportamiento. Crearemos un objeto de clase ActorMachine, en la carpeta Behaviours, que también podemos llamar "guía". Esta sería su estructura:

  • Estado inicial ("inicio"), de la clase VoidState.
  • Cuatro estados de la clase ActorGoToState (grupo Actor Dynamics), que llamaremos "go_P1", "go_P2(a)", "go_P3" y "go_P2(b)". Esta clase de estado hace que el actor que controla se mueva progresivamente hacia un destino dado. Los atributos relevantes son:
    • target. El tipo de destino en el movimiento. Elegimos Path_node.
    • label. La etiqueta del nodo destino. Elegimos "P1", "P2" o "P3", según corresponda.
    • distance. La distancia al destino suficiente para considerar que el actor ha llegado a él. Escribimos 0.01
    • velocity. La velocidad de movimiento. Escribimos 0.05

Las unidades de los dos últimos valores son las de la física, o mundo simulado. La relación con los píxeles de las imágenes está configurada en el atributo world_scale del objeto Configs/physics. El valor por defecto es apropiado generalmente.

Conectaremos los estados con transiciones como se ven en la imagen. El estado que falta ("prepara") servirá de inicialización y lo explicaremos más adelante. La transición desde "inicio" a "go_P1" tendrá la condición StateEnterCondition, que no es necesario añadir ya que se usará por defecto, y las que establecen el ciclo entre las otras, la condición TimerCondition (grupo Events) con un valor en delay de 2000 (milisegundos). Esta pausa mide el tiempo entre loa momentos en que inicia el movimiento de un nodo a otro.

Asignemos la máquina de estados así definida al personaje "guía". Añadamos el personaje a la capa ActorLayer del escenario, cerca del nodo "P2", y probemos su comportamiento. Si el tiempo de pausa en cada nodo no es de nuestro gusto, cambiemos el delay de cada timer. Más adelante veremos un procedimiento para que los valores puedan reutilizarse de forma que sólo haya que escribirlos una vez.

Puede ser que nuestra pantalla de ordenador sea pequeña y no quepa la acción completa. Para ello está el botón Package Defined Window (Defwin.png) de la barra de herramientas. Usándolo se ajusta la imágen a las proporciones definidas para el paquete actual, dando una visión más real del juego definitivo. Por defecto está activado.


El platillo

Forma del platillo

El equivalente a los marcianos en este juego son los platillos.

Empecemos creando una animación para ellos. En la rama Movies creamos un objeto de clase Animation al que añadimos las ocho imágenes "platillo1.png" a "platillo8.png" en el orden de su numeración. El atributo loop activado. Llamémosla "platillo".

En la rama Characters creamos un objeto de clase BodyCharacter, llamémosle "platillo" al que le asignamos la animación del mismo nombre. En la pestaña Actor creamos un cuerpo que rodee la imagen. Un polígono de 4 vértices bastará, (iniciado con el botón Polygon.png y usando Add point.png para añadir vértices). Hay que tener en cuenta que los polígonos de choque han de ser convexos, es decir, no pueden tener entrantes. El número de vértices por polígono está limitado a 8. Si se necesita crear una forma compleja de pueden usar más de un polígono. El atributo body_type será kinematic. Este tipo nos permite el control de la velocidad y las colisiones pero no le afectan las fuerzas (y por consiguiente no le afecta la gravedad). En este caso el valor del atributo density es irelevante.

Con el botón Start.png revisamos la animación del personaje. Si nos falta alguna imagen o no las hemos insertado en en el orden correcto debemos de corregirlo en la pestaña Movie.

Máquina del platillo

Finalmente, crearemos una máquina de estados (ActorMachine en la carpeta Behaviours) que llamaremos también "platillo". Añadamos a esta máquina un estado de la clase ActorGoToState ("sigue guía"). Su atributo target será Master_hook. El resto de atributos no es necesario modificar.

Asignaremos la máquina de estados "platillo" al personaje "platillo".

Es tiempo de completar la máquina de estados del guía. Haremos que sea el guía el que cree los platillos. Cuando un actor crea a otro, se convierte en su dueño (master). Esta relación tiene unas interesantes propiedades. Cuando hemos configurado el estado de clase ActorGoToState del platillo le asignamos el valor Master_hook al atributo target. Ésto se refiere a un punto relativo a la posición del dueño, concretamente a la posición relativa en que el dueño creó al platillo.

En la máquina de estados "guía" crearemos un estado nuevo de clase CompositeAction (llamémole "prepara"). Estableceremos una transición entre "inicio" y "prepara" a la que añadimos la condición StateEnterCondition. Necesitamos que esta transición se evalue antes que la que conecta "inicio" con "go_P1" por lo que cambiaremos el orden seleccionándola en el Object Browser y usando el botón "Up" (Up.png).

Una CompositeAction es, como su nombre indica, un conjunto de acciones. En el Object Browser, añadiremos a este objeto cinco hijos de la clase CreateActorAction con el atributo character asignado a "platillo". Esto hará que se creen cinco platillos en el momento en que se inicie la vida del "guía". en el atributo position indicaremos la relativa de creación. Que sean los valores siguientes: (-2,0,0), (-1,1,0), (0,0,0), (1,1,0), (2,0,0). Hay modos de hacer esta operación en un solo paso pero, de momento, nos vale así.

Ahora podemos quitar la imagen del "guía", puesto que ya no la necesitamos. Probemos el resultado.

Si aún no hemos quitado la "bomba" del escenario, es momento de hacerlo. Veamos ahora cómo hacer que el platillo suelte una bomba de vez en cuando: Añadamos a su máquina de estado un estado de la clase CreateActorAction que cree un actor de tipo "bomba" y conectémoslo al estado inicial con una transición. Añadiremos una condición de clase TimerCondition con los valores delay: 2000 y rnd_delay: 2000 (parte aleatoria del delay).

Probemos. Veremos que algo no va bien a causa de que la bomba choca con el platillo. Esto tiene fácil solución. Debemos de decirle a cada personaje con quién choca y con quién no. Para ello primero tenemos que crear los símbolos "PROTA" y "ENEMIGO" en la rama Symbols/Collision groups. El primer símbolo, existente previamente, "WORLD", de valor 0, representa los choques con el mundo.

Volvemos a editar el personaje "bomba". Seleccionamos "ENEMIGO" en el atributo group y en mask dejamos marcado sólo WORLD y PROTA. Esto significa que la bomba es del grupo de choque "ENEMIGO" y choca con "WORLD" y "PROTA". Lo mismo haremos con el personaje "platillo".

Probemos de nuevo. Mucho mejor. Sólo nos queda ajustar la posición de salida de la bomba o los temporizadores a nuestro gusto. Una sugerencia, en la acción que crea la bomba la posición puede ser (0,0.4,-0.1), esto hace que la bomba aparezca por detrás y en la parte de abajo del platillo.

Dejaremos para más adelante la destrucción del platillo.


El protagonista

Forma del "prota"

Crearemos un objeto (llamado "prota") de la clase BodyCharacter de body_type kinematic y group "PROTA", que sólo choque con los personajes del grupo "ENEMIGO". Le añadiremos una animación de un sólo frame con la imagen "cannon.png". Y no olvidemos darle una forma física. Todo esto ya sabemos hacerlo.

La máquina de estados la empezamos con un estado de la clase VoidState (llamado "inicio").

Ahora vamos a darle la capacidad de movimiento controlado por el teclado. Pero antes necesitamos definir las teclas que vamos a usar. Para lo cual tenemos que crear objetos de clase DiscreteInput en la rama Configs/inputs. Creemos un par de ellos con los nombres MOVE_LEFT y MOVE_RIGHT. Estos serán los comandos de movimiento. Debemos definir qué teclas o botones del mando de juego generan estos comandos. Para ello les añadimos sendos hijos de clase DiscreteSource que configuramos con las teclas LEFT y RIGHT respectivamente (por ejemplo).

Luego añadimos tres estados de la clase LinearVelocityAction a la máquina del prota:

Máquina del prota
  • "izquierda". Con velocity de (-3,0) y una transición desde "inicio" con una condición de clase KeyOnCondition (del grupo Input) en la que marcamos MOVE_LEFT en el atributo keys. La condición se cumple cuando el botón mencionado está pulsado.
  • "derecha". Con velocity de (3,0) y una transición desde "inicio" con una condición de clase KeyOnCondition en la que marcamos MOVE_RIGHT en el atributo keys. Aquí, también, la condición se cumple cuando el botón mencionado está pulsado.
  • "para". Con velocity de (0,0) y una transición desde "inicio" con una condición de clase KeyOffCondition en la que marcamos MOVE_LEFT y MOVE_RIGHT en el atributo keys. La condición se cumple cuando ninguno de los botones mencionado está pulsado.

Asignemos la máquina creada al personaje "prota", arrastremos el personaje a la capa de actores del escenario, cerca del borde inferior, y probemos.

Aún nos queda un detalle para terminar con el movimiento del prota: limitar su movimiento para que no salga de la pantalla por los lados. Añadamos un estado (llamado "límites") de clase WorldConstrainAction a su máquina de estados, con los atributos margin0 y margin1 igual a (1,0,0). Conectado a "inicio" con una condición AnyCondition. Probemos.

Ya que estamos, hagamos que el prota muera cuando le toca una bomba. Nada más fácil. Añadimos un estado (llamado "muere") de clase CompositeAction y a éste le añadimos un ActorDeadAction (Más tarde le añadiremos la creación de una bonita explosión y otros efectos).

Conectamos "inicio" y "muere" con una transición dependiente de una condición CollideActorCondition y probemos.

Ooops. El prota muere pero la bomba rebota. Pues editamos la máquina de estados de la bomba y en la transición de "inicio" a "final" añadimos una CollideActorCondition y ya está.


La explosión

Máquina de la explosión

Montemos una animación llamada "explo" con las imágenes "humo02.png" a "humo30.png" en el orden de los números. El atributo loop debe de estar desactivado. Crearemos un personaje de clase SpriteCharacter, (llamado "explo") al que añadimos dicha animación y su máquina de estados correspondiente en Behaviours, (llamada "explo", cómo no).

Empezaremos la máquina de estados con tres estados:

  • "final". Clase VoidState. Una transición desde el anterior disparada por una condición de clase MovieCondition. Este tipo de condiciones vigila el estado de la animación actual del actor. En el atributo test_mode elegiremos finished. Que detecta que la animación ha terminado y se ha parado. (Gracias a que el atributo loop de la animación está desactivado).

Habrá otros estados que mencionaremos más adelante.

Para que el prota explote agregaremos un estado hijo de la clase CreateActorAction al estado "muere" de la máquina del prota con el atributo character apuntando a "explo". Como un actor muerto no puede crear actores, el estado de clase ActorDeadAction que ya estaba en "muere" debemos de desplazarlo a la última posición de la lista. Probemos.

Si todo se ha hecho correctamente, cuando al prota le toca una bomba, soltada por algún platillo, se esfumará en una bonita explosión. De momento, silenciosa. Del ruido ya hablaremos.


La bala

Máquina de la bala

Un juego de marcianos con un prota indefenso es un poco raro, así que vamos a darle la capacidad de disparar.

Crearemos un personaje ("bala") de la clase BodyCharacter. body_type: dynamic, gravity_scale: 0 (para que no sea atraída por la gravedad), group: PROTA y mask: marcado sólo WORLD y ENEMIGO. Le daremos una animación de un sólo frame y un cuerpo con densidad 1.

La máquina estará compuesta por:

Límites de seguridad

Recordemos el método de hacer que la bomba desapareciera al llegar al fondo del escenario. Rodearemos el escenario de rectángulos de choque, establecerán unos límites de seguridad para que no se escapen ni bomba ni bala en algún rebote.

Para que el prota lance la bala necesitamos añadir a su máquina un estado ("dispara") de clase CreateActorAction que tenga el atributo character:"bala" y conectado con una transición desde "inicio" con la condición KeyOnCondition cuyo atributo keys tenga FIRE_UP activado y changed activado, para que sólo se active al pulsar la tecla pero no mientras se mantenga pulsada, a menos que queramos que parezca una ametralladora.

Recordemos que hay que asignarle la tecla que queramos (por ejemplo Q) al botón "FIRE_UP" en un objeto de clase DiscreteSource, hijo de un DiscreteInput, en la rama Configs/inputs.

Probemos el diparo. Vemos que la bala desaparece al chocar con el platillo. Tendremos que hacer lo mismo que con el prota para que el platillo explote. O sea:

En la máquina de estados del platillo, creamos un estado de clase CompositeAction (llamado "muere") conectado con "sigue guía" mediante una transición disparada por una CollideActorCondition. Al este estado le añadimos una CreateActorAction, (con character igual a "explo") y una ActorDeadAction. En este orden.


La bomba

Máquina de la bomba

No hay mucho que decir de la bomba. Para que explote al tocarla una bala, le añadimos la consabida CompositeAction ("muere") a su máquina de estados, disparada por una CollideActorCondition. etc.

Dos cosas más. A la transición de "inicio" a "final" añadiremos una CollideActorCondition a la que debemos asignarle al atributo character, "prota"; y a la CollideActorCondition de la transición de "inicio" a "muere" debemos asignarle al atributo character, "bala". Para que discriminen con qué tipo de actor chocan.

Ahora probemos la puntería contra las bombas.


Los estados del juego

¿Qué hay de las pantallas de menú, "game over" y esas cosas?. Bien. Allá vamos.

Tipografías

Lo próximo será preparar una tipografía para mostrar textos. En el menú Tools seleccionamos Compose Font Files. Esto nos abrirá una caja de diálogo para ayudarnos a crear un tipo de letra utilizable en Xitai Engine.

Constructor de tipografías

Las tipografías están compuestas de dos archivos. Uno es un archivo de imagen normal y corriente, que contiene la imagen de todos los caracteres usados, y otro, con extensión ".fm5", que define la métrica de los caracteres.

El archivo de la imágen tiene una restricción en sus dimensiones: deben ser potencias de 2, por ejemplo 64, 128, 256, 512.

Para crear una tipografía debemos seleccionar antes alguna de las que estén instaladas en el sistema con el botón [...] que hay en la parte superior derecha de la caja de diálogo. Debemos también elegir un tamaño adecuado. Si la imagen es de 512x512 (recomendado) el tamaño del tipo de letra puede ser de 40 puntos, aproximadamente. Eso depende de la tipografía. Cada vez que cambiemos los parámetros se recargará la imagen, a escala, en el visualizador.

Cuando usemos el botón Create, se crearán el archivo de métricas y uno o dos archivos de imagen. Desmarquemos la opción "TGA" ya que no la necesitamos por ahora. Así sólo tendremos un archivo de imagen de tipo PNG.

Pulsemos Create. Si todo fue bien se crearon dos archivos "the_font.fm5" y "the_font.png" con los caracteres escritos en el texto editable del centro de la caja de diálogo. El nombre de base de los archivos se puede editar en File.

Para un óptimo aprovechamiento del espacio, elegiremos un tamaño de tipografía que cubra la máxima superficie posible. Podemos repetir la operación cuantas veces queramos hasta que estemos satisfechos con el resultado.

A continuación importaremos la tipografía resultante. Para ello creamos un objeto de clase PM5Font en la carpeta Fonts. Se nos pedirá un archivo de tipo .fm5 que indicaremos. Si necesitamos usar la misma tipografía con diferentes tamaños, sólamente debemos crear otros PM5Font con el mismo archivo y elegir el tamaño en el atributo scale.

Por último, debemos definir un estilo de texto. Para ello creamos en la carpeta Styles un objeto de clase TextStyle que es el que van a utilizar los elementos gráficos en último término. Asignamos al atributo font el objeto PM5Font creado anteriormente.

Las pantallas

El juego visualiza siempre alguna pantalla, que es un objeto de clase Widget o derivada. Los cambios de pantalla se controlan mediante una máquina de estados. Aquella referida en el atributo machine del objeto Package actual.

En este primer tutorial no seremos muy ambiciosos. Vamos a tener cuatro pantallas. Menos la del juego, las demás van a contener un simple texto.

Ya tenemos una, que renombraremos como "juego". Es la StageWidget donde sucede la acción.

Ahora, en la carpeta Screens, crearemos.

  • "menú". Clase Widget. Por aquí empieza el juego. Cuando se pulse la tecla FIRE_UP entrará la pantalla de juego.
  • "game_over". Clase Widget. Aparecerá cuando muere el protagonista y se mantendrá unos segundos. Luego vuelve el menú.
  • "intermedio". Clase Widget. Aparecerá después de que hayan muerto todos los marcianos. Se mantendrá unos segundos y volverá a la pantalla de juego.

En cada una de estas tres pantallas añadiremos un objeto de clase TextWidget cuyo atributo text contendrá el texto a visualizar.

Ahora podemos asignar los textos. Por ejemplo: "Pulsa Q para jugar", "GAME OVER" y "¡Bravo!", respectivamente. Y el estilo del texto en el atributo text_style. Esta operación es importante, porque aunque en el editor veamos el texto con la única tipografía disponible, el juego exportado necesita que esté explícitamente referido, ya que no se exportan los objetos que no tengan referencias usadas.

En la pestaña Screens veremos representada la pantalla que seleccionemos.

Puede que queramos que el texto aparezca centrado en la pantalla, entonces en el atributo text_align seleccionamos center, en expand, horizontal y en el atributo size una altura de 80.

Repasemos todas las pantallas hasta estar satisfechos.

La máquina del juego

RUN

Volvamos a la máquina de estado global. Podemos llamarla "RUN". Ya contiene un estado, el estado del juego. renombrémosle a "juego". Ahora crearemos tres estados más, de la clase ScreenState, que llamaremos "menu", "intermedio" y "game_over". Les asignaremos las pantallas de su mismo nombre.

Como "start" es el primero que tiene que ejecutarse lo pondremos en primer lugar en la lista. (Usando el botón "Up" Up.png). Cada estado debe de tener su atributo screen configurado con la pantalla correspondiente.

Para disparar las transiciones "éxito" o "fracaso" desde el juego utilizaremos señales. Estos son objetos de la clase Signal que representan mensajes emitidos de forma indiscriminada. Cualquier otro objeto puede recibirlos si los esperan. Crearemos, en la carpeta Signals, dos señales llamadas "SUCCESS" y "FAIL".

Establezcamos las transiciones como las representadas en el gráfico. Cada transición se dispara por una condición de la siguiente manera:

  • Desde "juego" a "menu". Una KeyOnCondition con keys:EXIT. Deberemos asociar EXIT con una tecla, por ejemplo ESCAPE.
  • Desde "game_over" a "menu". Una TimerCondition con delay de, por ejemplo, un segundo (1000).
  • Desde "intermedio" a "juego". Una TimerCondition con delay de, por ejemplo, un segundo (1000).

Ya sólo nos queda emitir las señales que avisan del éxito o el fracaso en el juego. La idea es comprobar, de vez en cuando, si quedan platillos (si no quedan es un éxito) o si todavía vive el prota, si no vive es un fracaso.

Un momento perfecto es cuando termina la explosión. Crearemos dos estados de clase EmitSignalAction en la máquina de estados de la explosión. Le llamaremos "éxito" y "fracaso" y asignaremos su atributo signal(SEND) la señal que le corresponda a cada una.

Definimos sendas transiciones desde el estado "final" cada una disparada por una ActorCountCondition (grupo World). Esta condición tendrá su atributo character configurado con "platillo" (la que va a "éxito") o con "prota" (la que va a "fracaso").


Pruébese y si todo va bien se puede hacer una exportación final del minijuego.

En el siguiente tutorial hablaremos de:

  • Puntuación, records.
  • Persistencia de las variables.
  • Fondo de estrellas.
  • Efectos sonoros.

Que nos servirá para mostrar el uso de algunas características más avanzadas.

Tutorial. Mini Plaga Galáctica 2