| Tabla de contenidos |

Tutorial 2: Completando los personajes del juego

Es el momento de decidir la geometría de la visualización. 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 de la ventana del juego la indicamos en los atributos width y height del objeto de clase Package. Usaremos 500 y 750, respectivamente. La resolución virtual la declaramos en el atributo virtual height del Package. Lo hacemos coincidir con la altura del escenario (1200).

La resolución visible depende de la escala con que se visualice. Para que esta escala se elija automáticamente de forma que no queden huecos en la pantalla al representar el escenario, debemos asignar al atributo auto_zoom del WorldWidget el valor Fill.

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

De paso, quitemos el personaje bomba de la capa de actores (Seleccionar y usar el botón , o tecla X ).

Los marcianos

El guía de los marcianos

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 la carpeta Personajes 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, aunque sea visible como imagen o animación, no influye en los demás actores.

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.

Para conseguir un comportamiento similar vamos a crear una ruta de cuatro nodos. En cada nodo se hará una pausa.

Vamos a crear la máquina de estados del guía. Para seguir con la nomenclatura que estamos empleando podemos llamarla guia-machine y crearla en la carpeta Personajes.

La construimos con dos estados:

Las unidades de velocidad y distancias 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 mundo (clase derivada de World) que estemos usando. El valor por defecto es apropiado generalmente.

Conectamos los estados mutuamente. Para que no coincidan las líneas de conexión podemos arrastrar los extremos hasta que queden a nuestro gusto.

Las transiciones se producirán cuando se cumplan sendas condiciones. A saber:

Asignemos la máquina recién creada al Character guia. Coloquemos dicho personaje en la capa de actores del escenario cerca de algún nodo y probemos. Deberíamos de ver una bomba que sigue una ruta cíclica en forma de rombo con pausas en los vértices.

El marciano

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

Empecemos creando una animación para ellos. Creamos la rama Animaciones y en ella añadimos un objeto de clase ImageSequence (categoría Image) al que añadimos las ocho imágenes "platillo1.png" a "platillo8.png" (seleccionamos las ocho imágenes arrastrando el cursor desde la primera hasta la última). Asegurémonos de que han entrado en el orden de su numeración. En otro caso usemos los botones y para ordenar la secuencia. El atributo loop activado. Llamémosla "platillo".

En la rama Personajes creamos un objeto de clase BodyCharacter, llamémosle "platillo" al que le asignamos la animación del mismo nombre.

En el editor de Actor creamos un cuerpo que rodee la imagen. Un polígono de 5 vértices bastará, (iniciado con el botón y usando 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. Aquí, con el botón revisamos la animación del personaje.

En la sección Dynamics del Object Inspector el atributo body_type será kinematic. Este tipo de dinámica 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 irrelevante.

La creación de los platillos se la vamos a encargar al guía. Cuando un actor crea a otro, se convierte en su dueño (master). Esta relación tiene unas interesantes propiedades. Por ejemplo, la posición relativa al master cuando se creó el actor se memoriza. La llamamos hook. Esta posición se puede cambiar después pero siempre es relativa al master. Si el máster muere deja de tener sentido.

Editemos la máquina de estados del guía añadiendo un nuevo estado de clase State. Este será el nuevo estado inicial, por lo que tenemos que moverlo a la primera posición en el árbol representado en el Object Browser. (Botones y de su barra de herramientas). El estado inicial se representa en el gráfico con un borde más grueso.

Se añadirá una acción de clase CreateActorAction (categoría Actor Control) a la que se conectará el estado inicial. El estado inicial se conectará al estado ruta. Véase la figura.

Una conexión sin condiciones es como si contuviera una condición de clase AtFrameCondition con atributo skip_frames = 0. Es cierta sólo una vez después de entrar en el estado y saltar un determinado número de frames. En este caso, es una condición cierta al comienzo del estado. Es probablemente el tipo de conexión más frecuente. Se utiliza siempre que haya que ejecutar una o varias acciones al comienzo de un estado.

Es importante el orden de las conexiones, porque determinan el orden en que se evalúan. No queremos que la máquina pase al estado ruta antes de ejecutar la acción que crea los marcianos. El orden está indicado por los números en el arranque de la linea de conexión. Podemos cambiar el orden de las conexiones también con los botones y .

En la acción CreateActorAction asignamos platillo al atributo character. Si probamos veremos que se crea un marciano en la posición inicial del guía. Pero queremos crear un enjambre, no uno sólo. Para hacerlo de forma fácil y flexible vamos a indicar los puntos iniciales de creación con un grupo de nodos como en la figura. Estos nodos deberán estar etiquetados de alguna forma para que puedan ser reconocido. Vamos, pues, a crear una etiqueta primero.

En el Symbol Browser localizamos el tipo de símbolo llamado Tags. Creamos un nuevo símbolo de este tipo (menú contextual) y le llamamos, por ejemplo, P1.

Ahora, en el editor de escenarios ocultamos (botón o atributo HIDDEN) la capa ShapeLayer que tenemos para que no nos despiste y creamos una capa nueva de la misma clase.

Creamos un nodo y marcamos P1 en el atributo tags de la sección current node. Movemos el cursor y vamos duplicamos ese nodo con la tecla V hasta conseguir una distribución, alrededor de la posición del guía, similar a a la de la figura anterior. Con el truco de copiar el nodo nos hemos ahorrado tener que etiquetar cada uno individualmente.

Ahora, en la acción CreateActorAction seleccionamos P1 en el atributo node_flag y escribimos 8 en el atributo count. Cuando probemos observaremos que se crea un marciano sobre cada nodo. Aunque, de momento, se quedan donde están.

Vamos con la máquina de estados del marciano. Para seguir la costumbre crearemos una Machine llamada platillo-machine. Le añadimos un estado de clase ActorGoToState (llamémosle sigue guía) donde elegimos el valor Master_hook en el atributo target. Que no se nos olvide asignar esta máquina al personaje. Si probamos veremos que los marcianos acompañan el movimiento del guìa, aunque con un cierto retraso. Aumentamos la velocidad para evitar el retraso (velocity = 10).

Ya podemos hacer invisible al guía. Para ello, simplemente hay que quitarle la imagen de la bomba que contiene. Y, tal vez, querramos retocar algunas posiciones de los nodos del camino del guía para evitar que el enjambre sobrepase el borde de la pantalla en algunas ocasiones.

La bomba marciana

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 del tipo 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 los grupos 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 (CreateActorAction), hacer layer_tag = -inferior- y position = (0,0.3,0). Esto hace que la bomba aparezca por detrás y en la parte de abajo del platillo.

El prota

La nave

Crearemos un personaje BodyCharacter para el protagonista, con su polígono de forma física y su máquina de estados. No será sensible a fuerzas (body_type = kinematic), el grupo de choque será group = PROTA y chocará con los otros grupos (mask = WORLD, ENEMIGO). Todo esto ya es rutinario para nosotros.

Ahora vamos a darle la capacidad de movimiento controlado por el teclado. Pero antes necesitamos definir las teclas que vamos a usar. Esto lo haremos en dos fases. Primero debemos definir los símbolos que corresponden a las teclas de control y luego asignárselos a las teclas correspondientes.

Los símbolos los creamos en la clase de símbolos predefinida Input buttons. Llámense, por ejemplo IZQUIERDA y DERECHA. En la rama Configs/gamepads/Buttons crearemos sendos objetos de la clase GamePadButton, left y right. En cuyos atributos button_symbol asignamos el símbolo que corresponda, dejamos input_source como Keyboard y en key indicamos la tecla que nos convenga en cada caso. Por ejemplo la tecla LEFT (<-) para el movimiento a la izquierda y RIGHT (->) para el movimiento a la derecha.

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

Luego añadimos tres acciones de la clase ProcedureAction (categoría Structural Actions). Este tipo de acción evalúa una expresión. En este caso nos servirá para modificar el atributo linear_velocity de la clase Body2DActor, que es a la que pertenece el prota.

El atributo do de ProcedureAction contiene un botón verde que es el acceso a la edición de expresiones. Pulsando en su parte derecha nos muestra un menú de operaciones dependientes del contexto. Pulsando la parte izquierda nos presenta el selector de términos (Term Selector) para insertar en la expresión que estemos editando. Vamos a ello, pulsemos en la parte izquierda.

En el menú superior del selector de términos pulsamos sobre Attributes. Y, después mediante el botón que aparece inmediatamante debajo, seleccionamos la clase del prota (Body2DActor). Esto nos muestra la lista de atributos y métodos de dicha clase. Buscamos la línea que comienza por "set! linear_velocity" y la seleccionamos.

Obtenemos la expresión que asigna un valor al atributo linear_velocity de un objeto, aún por determinar, de la clase Body2DActor. Si pulsamos el botón que aparece bajo el nombre del atributo se despliega el selector de objetos, pero eso no nos sirve ahora. Queremos que el objeto sea el que está siendo controlado por la máquina de estados. Para ello pulsamos el lado derecho del botón verde que hay junto a dicho botón.

Seleccionamos la opción _OWNER. Éste es el nombre de una función de contexto, sin parámetros, que devuelve siempre el objeto que está siendo controlado por la máquina de estado en curso. En caso de que no se invoque la expresión desde una máquina contendrá el objeto nulo.

La variable contiene un objeto de cualquier clase por lo que debemos indicarle a la expresión de qué clase es el objeto que estamos tratando, la que tiene el atributo que nos interesa. Esta operación, llamada casting en la jerga de los programadores se efectúa anteponiendo el operador #Body2DActor, que se hace automáticamente.

Asignemos ahora las velocidades y las transiciones de la siguiente forma:

Arrastremos el personaje a la capa de actores del escenario, cerca del borde inferior, y probemos.

Está bien pero no conviene que el prota se salga er la pantalla. Para evitarlo añadimos otra acción de clase WorldConstrainAction (también de la categoría Actor Dynamics). Asignamos los límites (1,0,0) tanto a margin0 como a margin1. Y, para que tenga efecto continuo, la conexión desde inicio tendrá una condición de clase AnyCondition (categoría Structural Conditions) que siempre se cumple.

Matar o morir

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 (que, como su nombre indica, sirve para agrupar acciones) y a éste le ponemos un ActorDeadAction (Más tarde le añadiremos la creación de una bonita explosión y otros efectos).

Conectamos inicio a muere con una transición dependiente de una condición CollideActorCondition y probamos.

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 otra CollideActorCondition y ya está.

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 (para poder impulsarla), gravity_scale = 0 (importante, para que no sea atraída por la gravedad y nos vuelva a caer encima). Esta vez, la expresión invocará el método linearImpulse de Body2DActor, que aplica un empujón a un punto relativo a su centro de gravedad.

Que choque sólo contra los enemigos (group = PROTA y mask = WORLD y ENEMIGO). Le daremos la imagen bala.png y un cuerpo masivo (rectángulo simple, rol = Solid, density = 1).

La máquina de estado de la bala estará compuesta por:

Necesitamos definir una tecla de disparo. Recordemos que hay que asignar la tecla que queramos (por ejemplo Q, con símbolo DISPARA) en un objeto de clase GamePadButton hijo de Configs/gampepads/Buttons.

Para que el prota lance la bala necesitamos añadir a su máquina un estado (nombre dispara, por ejemplo) de clase CreateActorAction que tenga el atributo character = bala y conectado desde inicio con la condición ButtonsCondition cuyo atributo buttons tenga DISPARA activado, Para que sólo se active al pulsar la tecla pero no mientras se mantenga pulsada, ponemos on = ON, changed = ON, a menos que queramos que parezca una ametralladora.

Probemos el diparo. Vemos que la bala desaparece al chocar con un platillo.

Tendremos que hacer lo mismo que con el prota para que el platillo muera. O sea: En la máquina de estados del platillo, creamos un estado de clase CompositeAction (llamado muere) conectado desde sigue guía con una CollideActorCondition. A este objeto le añadimos una ActorDeadAction.

Recordemos el método de hacer que la bomba desaparezca 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. (Recuerda la utilidad de la tecla V para copiar el elemento del escenario actualmente seleccionado).

La explosión

Montemos una ImageSequence 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, (llamémosle explo) al que añadimos dicha animación y su máquina de estados correspondiente, (llamada explo-machine, cómo no).

Empezaremos la máquina con tres estados:

Habrá otros estados que mencionaremos más adelante.

Para que el prota explote, en su máquina de estados, agregaremos a la ación compuesta muere una acción de la clase CreateActorAction 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.

Hagamos lo equivalente en el muere del marciano, para que este también explote.

Y para que podamos hacer que las bombas exploten cuando les toca una bala añadimos a su máquina otra CompositeAction similar. Pero la conexión desde inicio debe ser la primera y la condición de choque debe ser más restrictiva, en la CollideActorCondition el atributo character debe de contener bala.

Como es difícil probar esto último podemos desactivar temporalmente las transiciones a muere del prota y del marciano. Esto se hace en el atributo disabled de Trigger.

 
Tutorial 2: Introducción   Tutorial 2: Los estados del juego


(C) Paco Suárez (pacosu@xitai,es). 2020.
XITAI