« Junio 2004 | Inicio | Agosto 2004 »

Julio 18, 2004

Componentes FlashSim

Este juego de componentes contiene controles como potenciÛmetros, relojes graduados, ... en fin, los controles tÌpicos de mesas de mezclas y / o control.

Los componentes est·n escritos en AS2, son facilmente skineables, y contienen toda la documentaciÛn necesaria para comenzar a trabajar con ellos sin mayor dificultad.

Si quereis echarlos un vistazo, visitad FMXISComponents ( por cierto, si realizais la compra de los componentes a travÈs de este link, ayudais a mantener esta web :)

Julio 12, 2004

El patrÛn State aplicado al desarrollo de juegos ( III )

Ya ha llegado el momento de empezar a ver cÛdigo.

Puedes bajarte el cÛdigo aquÌ. Nos apoyamos tambiÈn en las implementaciones de lista enlazada y en la de HashMap

Hemos implementado la m·quina de estados utilizando cuatro clases
: Transition ( transiciones )
State ( estados )
SMachine ( mq·uina de estados )
BEngine( motor de comportamientos, es quien maneja las m·quinas de estados )

B·sicamente el procedimiento ser· el siguiente: se crear· la m·quina de estados de la entidad correspondiente, se le aÒadir·n los estados y transiciones necesarias, y m·s tarde se registrar· esa m·quina en el motor global de la aplicaciÛn. Ese motor ser· el encargado de mandar a las m·quinas de estados que tenga registradas que ejecuten el paso siguiente.

Veamos primero la estructura de ese motor. B·sicamente no es m·s que una lista enlazada en la que guardamos todas las m·quinas de estados que registremos, y mÈtodos para registrar m·quinas de estados, eliminarlas del registro, y darlas la orden de proceso.

class BEngine { private var machineListVal: List; public function BEngine( ) { this.machineListVal= new List( ); } public function registerSMachine( machine: SMachine ) { this.machineListVal.push( machine ); } public function unregisterSMachine( machine: SMachine ) { this.machineListVal.deleteElement( machine ); } public function doProcess( ) { var it: IIterator= this.machineListVal.iterator( ); while( it.hasNext( ) ) { SMachine( it.next( ) ).executeCycle( ); } } }

La m·quina de estados no la hemos implementado como una colecciÛn de estados y transiciones, sino que sÛlo guardamos una referencia al primer estado, y al estado actual en que se encuentre. Como ya las transiciones relacionan los estados entre sÌ, no tenemos necesidad de conocer todos los estados y transiciones desde la propia m·quina de estados, sino que con conocer el primero es suficiente. Lo que sÌ que tiene que controlar esta clase es si la m·quina de estados est· arrancada, o parada

class SMachine { . . . . public function resetToInit( initState: State ) { this.currStateVal = initState; this.initStateVal= initState; } . . . . }

Cada estado tendr· asociado un callback ( que es el que va a ejecutar cuando la m·quina de estados entre en ese estado, y un array con las transiciones que salen de Èl.

class State { . . . . public function addTransition( transition: Transition ) { if( transitionDoNotExist( transition ) ) { this.transitions.push( transition ); } } . . . public function evalState( paramater: Object ): fgPair { var nextState: State= undefined; var retPair: Pair; var nTam: Number= this.transitions.length; for( var nIx: Number= 0; nIx< nTam && nextState== undefined; nIx++ ) { var currentTransition: Transition= this.transitions[ nIx ]; if( currentTransition.evaluate( ) ) { if( this.__traceInfo__ ) { trace( "TRANSIT:"+ this.id+ "->"+ currentTransition.endState.id+ " BY "+ currentTransition.id ); } nextState= currentTransition.endState; retPair = new fgPair( nextState, currentTransition ); } } return retPair; } public function execute( parameter: Object ): Object { if( this.__traceInfo__ ) { trace( "S[A]:"+ this.id+"; "+ this.actionCallbackVal.callbackMethod ); } return actionCallbackVal.fire( parameter ); } . . . . }

Y por fin, las transiciones. Cada transiciÛn tiene una referencia al estado inicial y al final, asÌ como la capacidad de ejecutar el callback que se le pase.

class Transition { . . . public function set initState( state: State ) { state.addTransition( this ); this.initStateVal= state; } public function set endState( state: State ) { this.endStateVal= state; } . . . public function execute( parameter: Object ): Object { if( this.__traceInfo__ ) { trace( "T[A]:"+ this.id+"; "+ this.actionCallbackVal.callbackMethod ); } return this.actionCallbackVal.fire( parameter ); } public function evaluate( ): Boolean { var retVal: Boolean= true; if( this.evaluationCallbackVal!= undefined ) { retVal= Boolean( evaluationCallbackVal.fire( this ) ); } return retVal; } . . . }

Pues ya sÛlo falta implementar un ejemplo de utilizaciÛn. Pero eso ser· otro dÌa.

Julio 07, 2004

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

Julio 05, 2004

El patrÛn State aplicado al desarrollo de juegos ( I )

Vamos a ver un ejemplo de cÛmo podrÌa implementarse el patrÛn State, para realizar juegos con lÛgica compleja y / o reactiva, teniendo en cuenta que la implementaciÛn es bastante relajada, y que el ejemplo que se presenta es tan sÛlo eso, un ejemplo, lo suficientemente sencillo para poder ser explicado en unas pocas lÌneas, pero lo suficientemente complejo para que empiece a merecer la pena el considerar implementar este patrÛn. Obviamente, cuanto m·s complejo sea el comportamiento del juego y / o de sus entidades, esta opciÛn cobrar· m·s cuerpo.

Manos a la obra. Primero veamos quÈ es el patrÛn State.

Del libro "Patrones de diseÒo" ( Gamma et al, Addison Wesley, Madrid, 2003 )

"Permite que un objeto modifique su comportamiento cada vez que cambie su estado interno. Parecer· que cambia la clase del objeto"

Aplicabilidad: ⁄sese el patrÛn State en cualquiera de los siguientes dos casos:

1.- El comportamiento de un objeto depende de su estado, y debe cambiar en tiempo de ejecuciÛn dependiendo de ese estado.
2.- Las operaciones tienen largas sentencias condicionales con m˙ltiples ramas que dependen del estado del objeto.[Ö]. El patrÛn State pone cada rama de la condiciÛn en una clase aparte. Esto nos permite tratar al estado del objeto como un objeto de pleno derecho que puede variar independientemente de otros objetos".


Dicho en otras palabras, lo que vamos a intentar es describir el comportamiento de un objeto como una colecciÛn de estados, de manera que sea el propio objeto el que maneje su comportamiento mediante transiciones de un estado a otro.

Dicho asÌ, no hay quien lo entienda, pero vamos a intentar ilustrarlo con un ejemplo.

Imaginemos que tenemos que hacer un juego: "Explota la pelota". La especificaciÛn del juego es la siguiente: se nos presentar· un n˙mero indeterminado de pelotas en pantalla, que rebotan en el suelo sin pÈrdida de energÌa, y que no rebotan en las paredes ( es decir, si se salen por la derecha de la pantalla, han de volver a aparecer por la izquierda ). Tenemos un tiempo m·ximo para explotar un n˙mero determinado de pelotas ( haciendo clic con el ratÛn sobre ellas ). Al explotar una pelota aparecer· otra, de manera que siempre habr· el mismo n˙mero de pelotas en pantalla. La posiciÛn y velocidad inicial de las pelotas es aleatoria. Si no explotamos un n˙mero mÌnimo, perdemos. Si lo conseguimos, se nos ofrece terminar el juego con la puntuaciÛn actual, o volver a intentarlo, pero con menos tiempo y teniendo que explotar un n˙mero de pelotas mayor. AsÌ hasta que nos plantemos o perdamos.

A bote pronto ( valga la expresiÛn ) tenemos dos entidades claramente diferenciadas: el universo del juego, y la pelota en sÌ, luego parece lÛgico pensar que debamos estructurar el juego en dos clases, una que maneje la lÛgica del juego ( que se encargue de colocar las pelotas en pantalla, compruebe si se han explotado las suficientes, si queda tiempoÖ ), y otra que maneje el comportamiento de la pelota. Vamos a fijarnos en Èsta ˙ltima.

Evidentemente, la pelota se va a representar con un MovieClip. Luego podemos pensar que lo lÛgico serÌa crear una clase Pelota que extienda de MovieClip, y sobrescribir el onEnterFrame, de manera que ese mÈtodo se encargue de chequear la posiciÛn de la pelota en cada frame, haga el chequeo de colisiÛn con el suelo, compruebe si la pelota se ha salido de la pantalla por alguno de los lados, y que coloque la pelota dependiendo de esas circunstancias ( dicho de otra forma, que implemente un mecanismo para manejar la ecuaciÛn de movimiento de la pelota ), que detecte cu·ndo se ha hecho click en la pelota y se lo notifique al universo del juego, que maneje la animaciÛn de explosiÛn de la pelota, etcÖ.

…ste no creo que sea ni el momento ni el lugar para discutir sobre si ese enfoque puede considerarse el m·s apropiado o no. Partamos de la base de que no hay una forma correcta y otras incorrectas de hacer las cosas. Pero ve·moslo con espÌritu crÌtico.

Por un lado, vamos a tener una clase con una abanico de responsabilidades muy amplio ( desde comportamientos fÌsicos a mera presentaciÛn ). Eso es lo que se denomina baja cohesiÛn. Dicho de otra forma: "zapatero, a tus zapatos".

Adem·s, el comportamiento de la pelota se deber· implementar como un enorme switch o una larga serie de sentencias if ( si ha rebotado con el suelo, haz esto; si te has salido por alguno de los lados, haz esto otro;Ö.. ). Bueno, pues, esto, precisamente, es uno de los casos que se ponen como ejemplo de "Èste es el momento para implementar un state". Sobre todo teniendo en cuenta, que aunque Èste no sea el caso, el comportamiento de la pelota podrÌa ser reactivo, es decir podrÌa tener que cambiar a lo largo del juego ( cambia el rozamiento con el aire, las propiedades din·micas del rebote, etc ).

Por hoy es suficiente. En breve veremos lo que es una m·quina de estados, y cÛmo implementarla.