« El patrón State aplicado al desarrollo de juegos ( I ) | Inicio | El patrón State aplicado al desarrollo de juegos ( III ) »

El patrón State aplicado al desarrollo de juegos ( II )

Como ya hemos explicado antes, lo que vamos a intentar hacer es manejar el comportamiento del objeto como una serie de estados relacionados entre sí, de manera que la entidad que implementa esa colección de estados ( conocida como “máquina de estados” ) sea capaz de transitar de un estado a otro. Es decir, tendremos una clase Ball que tiene agregados una serie de objetos ( los estados ) y un gráfico para representarse a sí misma ( el movieclip ). Las máquinas de estados que vamos a implementar son no jerárquicas y no concurrentes. Si queréis leer algo más completo sobre máquinas de estados, pasad por FlashSim orquantum-leaps ( gracias a Jonathan Kaye for the links ). Dado que estamos desarrollando un juego, y que por tanto, la performance debe ser nuestra mayor preocupación, vamos a intentar simplificar la máquina de estados lo más posible.

Vamos a intentar ilustrarlo con un diagrama UML ( statechart diagram ):

Máquina de estados de la pelota

El universo del juego

Veamos este último diagrama con un poco más de detalle, para entender cómo funciona la máquina de estados. Definimos los estados en los que vamos a dividir el comportamiento del juego:

1.- initGame: Inicialización general del juego.
2.- initRound: Inicialización de cada ronda de juego. Se pondrán a cero los contadores de pelotas explotadas y de tiempo,…..
3.- gamePlay: Se está ejecutando el juego. Las pelotas están botando, y el jugador está intentando explotarlas….
4.- endOfTime: Se ha terminado el tiempo. Chequearemos si se han explotado suficientes pelotas…
5.- defeat: No se han explotado suficientes. Hemos perdido.
6.- newChallenge: Hemos explotado suficientes, entonces se nos pregunta si queremos volver a empezar, o nos retiramos. En caso de que queramos volver a empezar, volveremos al estado initRound, y en caso de retirarnos, vamos al estado endOfGame.
7.- endOfGame: El juego ha terminado. Se puede enviar a servidor la información sobre la puntuación, etc.

Una vez comprendida la separación conceptual en estados, veamos cómo funciona la máquina de estados.
En primer lugar, en cada estado se ejecuta una acción ( initGameAction, initRoundAction,… ). Normalmente, una máquina de estados tiene asignadas tres acciones por estado: una a la entrada en el estado, otra en la ejecución, y otra a la salida del estado. En nuestro caso, vamos a realizar una implementación sencilla, en la que sólo se ejecutará una acción por estado. La máquina de estados no es jerárquica ni concurrente, por lo que la evaluación de las transiciones se simplifica mucho.

Por ejemplo, en el estado initRound, se ejecutará el callback initRoundAction, que se encargará de la inicialización de la ronda actual.

Cada una de las flechas representa una transición. Cada estado tiene asignados un número de transiciones indeterminado. Esas transiciones son callbacks que devuelven un booleano, de manera que la máquina de estados se moverá por la transición que devuelva true. Normalmente, esas transiciones evalúan flags que son propiedades de la clase. Voy a intentar explicarlo mejor.

La máquina de estados está vinculada a un temporizador ( un enterframe de un clip vacío, por ejemplo ), de manera que a cada tick de ese temporizador, la máquina de estados evalúa todas las transiciones que salen del estado en el que se encuentra, y transitaré por la primera que devuelve un true. Por tanto, pasará a otro estado, ejecutando la acción asignada a dicho estado, y en el siguiente tick evaluará las transiciones de ese nuevo estado, transitando a su vez por la primera que encuentre que devuelva true.

Esto se entenderá mucho mejor con el ejemplo de la pelota. Aunque todavía es pronto para ver código, recordemos que habíamos decidido que la pelota no fuera una subclase de MovieClip, sino una entidad más compleja, que agregara la máquina de estados y el clip para su presentación gráfica.

Bien, pues cuando instanciamos esa clase y creamos y arrancamos su máquina de estados, nos encontramos en el primer estado de la misma: initBall. Se ejecutará la acción correspondiente ( initBallAction ), que se encarga de hacer un attachMovie y de colocar en el stage el correspondiente MovieClip. Una vez hecho esto, ponemos a true un flag de la clase ( por ejemplo isInitFlag ), y a false otro llamado isOutOfBoundsFlag. En cada tick del temporizador se ejecutan las transiciones asignadas al estado initBall ( en este caso sólo hay una, que parte de initBall y acaba en moveBall ),. Por tanto, si esa transición devuelve el valor del flag isInitFlag, devolverá true cuando el clip se haya inicializado y colocado en pantalla, por lo que la máquina de estados transitará al estado moveBall.

Ese estado tiene asignadas tres transiciones: una sobre sí mismo, otra a outOfBounds, y otra a destroyBall. Respectivamente, esas transiciones evaluarán tres flags: isOutOfBoundsFlag== false, isOutOfBoundsFlag== true ( sí, es el mismo ), y isClickedFlag== true.

Mientras tanto, en la acción de ese estado, primero se moverá la pelota, y luego se chequeará si la pelota está fuera del stage ( sea por que tiene que rebotar con el suelo, o porque se ha salido por los laterales ). En caso de que así ocurra, el flag isOutOfBoundsFlag se pondrá a true. Por tanto, al evaluarse las transiciones, si la bola no se ha salido del stage, el flag isOutOfBounds será igual a false, por lo que la transición que devolverá true será la que se realiza sobre sí mismo ( isOutOfBounds== false ), por lo que volverá a entrar en ese estado, ejecutando otra vez la acción moveBallAction, por lo que se volverá a mover la bola, se volverá a chequear si se ha salido…..

En caso de que la bola se hubiera salido, la transición que devolvería true sería la que termina en outOfBounds. En ese estado se cambiaría el movimiento de la bola ( rebote ) o la posición ( si se ha salido por un lateral ), y se volvería al estado de movimiento.

En caso de que se haya hecho clic en el clip se transitaría al estado destroyBall.

En el próximo post veremos esto implementado en código, pero por ahora es más importante entender el concepto subyacente, que es que la propia entidad es la que, chequeando una serie de flags propios, va cambiando su comportamiento. Y ese comportamiento está encapsulado en una serie de estados, independientes unos de otros.

Hasta pronto...

Comentarios

Os lo digo en serio, me molaría muchísimo poder mirarme esto, pero no hay tiempo material.

Pero... puedo prometer y prometo, que en un futuro no muy lejano me lo miraré XD.


Jejejeje...

César me sigue pareciendo muy interesante (no te vayas a desanimar ahora!)
un saludo y gracias