Para darle dificultad a los platillos en el siguiente nivel vamos a hacer que ataquen al prota de vez en cuando. Para no complicarnos mucho vamos a definir una ruta en zig-zag que vaya desde la parte superior de la pantalla hasta la parte inferior. La diseñaremos en una nueva capa de clase PathLayer que será opcional. Que se parezca a la imagen adjunta.
Es conveniente etiquetar la ruta a seguir. No queremos que se confunda con la ruta del guía, ya que si no le indicamos qué ruta seguir utilizará la que tenga más cerca. Para ello creamos un símbolo del tipo Labels en la ventana de edición de símbolos. Luego, en el Object Inspector, con el primer nodo de la ruta seleccionado asignamos dicha etiqueta al atributo label de la sección current node de la PathLayer.
Vamos a por la máquina de estados del platillo. Para que el marciano siga la ruta añadimos a su máquina de estados un estado de clase ActorFollowPathState, llamémosle ataca, conectado con el estado inicial por una condición de temporizador TimerCondition. En la parte fija, delay, 0 ms. y en la parte aleatoria (rnd_delay) 2000 milisegundos. Asignamos la etiqueta de la ruta en su corespondiente atributo label.
Para que parezca un ataque y no un paseo le daremos una gran velocidad: velocity = 6.
El final del estado, y vuelta al estado inicial lo determinará una condición StateFinishedCondition. La condición se cumplirá cuando se llegue al último nodo del camino. Hay que asegurarse de que el atributo pingpong del ActorFollowPathState está desactivado, en caso contrario el marciano recorrería el camino indefinidamente adelante y atras.
Probemos el escenario con el botón del editor de escenarios. Tendríamos que ver un caos de ataques continuos de los marcianos. Si no es así, repasemos los pasos, que algo hemos hecho mal.
El movimiento es un poco imperfecto y carente de elegancia, ya lo mejoraremos. Ahora lo que importa es que siga tirando bombas y detectando las balas durante el ataque. Podríamos conectar el estado que sigue el camino con las mismas acciones que el estado inicial pero podemos hacer algo mejor. Con la tecla SHIFT pulsada seleccionemos con el ratón el estado inicial (sigue_guía) y el del ataque. Ambos deben de aparecer seleccionados. Con el cursor del ratón en la parte libre del editor de máquinas desplegamos el menú contextual (botón derecho) y elegimos Convert Selection to Embedded Machine. El efecto será que aparecerá una nueva máquina con sólo esos estados.
Esta nueva máquina recién creada (llamémosle movimiento) es, a la vez, un estado de la máquina anterior, a la cual podemos volver con el botón del editor de máquinas de estados. Podemos hacer dobleclick en el globo que representa la sub máquina para editarla, (o usar el Object Browser). Ahora somos libres de refinar la submáquina, incluso añadiendo estados y acciones, sin preocuparnos de las conexiones generales a las acciones de la bomba y la muerte.
Parece excesivo que todos los marcianos ataquen casi a la vez. Sería mejor que lo hicieran por turnos. Para conseguirlo utilizaremos uno de los métodos de sincronización de máquinas que tenemos disponibles. En este caso será un contador. Crearemos un objeto de la clase Counter, en la rama Personajes, por ejemplo. Lo parametrizamos con mode = Manual.
En el atributo stat_counter del estado ataca de la submáquina movimiento insertamos una referencia al contador recién creado. Con esto se consigue que el contador se incremente cada vez que alguna máquina entra en este estado y se decremente cuando salga. Nos vale para saber cuantos platillos están atacando en cada momento.
Ahora tenemos que evitar la entrada en el estado ataca si el valor del contador es mayor o igual a 2, para que sólo haya dos marcianos atacando en cada momento. Eso lo hacemos combinando una ExpressionCondition < (value(@contador), 2) con la condición del temporizador dentro de una condición compuesta And.
No queremos que los marcianos ataquen siempre, sólo cuando cambiemos de nivel y el juego se vaya complicando. Podemos conseguirlo de varias maneras, una es que la capa de la ruta de ataque se pueda activar o desactivar durante el juego. Si el marciano no encuentra la ruta a seguir la condición StateFinishedCondition que pasa de ataque a sigue_guía será siempre verdadera y es como si el ataque no existiera. Para demostrarlo hagamos lo siguiente:
Si ahora hacemos la prueba del escenario veremos que los platillos no atacan. Para activar la capa durante la prueba abrimos la ventana de configuración de la prueba de escenario con el botón de la barra de herramientas del editor de escenarios.
Activemos ATAQUE y probemos de nuevo. Ahora sí.
Quizá hemos observado que el mundo (objeto de clase WorldBox2D) además del escenario permite seleccionar las capas activas. También vemos que puede ejecutar una máquina de estados. Lo primero que podemos pensar para gestionar los cambios de nivel es usar estas opciones de alguna forma. Es posible, pero hay previsto un mecanismo perfeccionado para ello, ya que es algo que necesitarán la mayoría de los juegos. Se basa en el objeto de clase Scene.
Los objetos de clase Scene residen siempre en la rama Persistence/scenes. En todo momento hay una o ninguna escena activa que es accesible en las expresiones como _SCENE. Se cambia de escena activa con la acción SetCurrentSceneAction. Los mundos que no tengan asignado un escenario adquieren el escenario y sus capas activas de la escena actual.
Crearemos dos escenas en la rama Persistence/scenes. Les asignaremos el escenario que hemos creado y a la segunda le activaremos el layer_tag ATAQUE. Tenemos que desasignar el escenario al objeto mundo (WorldBox2D) para que surta efecto el cambio de escena.
Para conseguir el cambio de escena podemos que añadir a la máquna de estados global RUN una acción de clase SetCurrentSceneAction conectada al estado intermedio con el atributo select en NextOrFirst para que seleccione la escena siguiente o la primera si no hay siguiente.
Para asegurarnos de que siempre se empieza por la primera habría que añadir otra SetCurrentSceneAction conectada, en primera posición, al estado nueva misión con select en First.
Veamos el resultado.
Como es una clase extensible se pueden añadir los atributos que se deseen para parametrizar el juego según la escena actual. Vamos a crear una clase derivada con un atributo adicional: el número de platillos que pueden atacar simultáneamente. Ya sabemos. En el Class Browser localizamos Scene y creamos una derivada suya. Llámese MiEscena. A ésta le añadimos el atributo numérico ataques.
La expresión, mencionada anteriormente, que limita los ataques simultáneos posibles la cambiamos por:
< (value (@contador), ataques (#MiEscena _SCENE))
Nos falta usar la nueva clase en lugar de la escena simple. En el Object Browser, con el cursor sobre la segunda escena desplegamos el menú contextual y seleccionamos Promote to Extended Class. Nos da a elegir la única posible alternativa. La escogemos. Ahora hacemos ataques = 2. Creamos otra escena de clase MiEscena con el escenario y el tags ATAQUE y hacemos ataques = 10.
¿Hace falta explicar el resultado?