Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





Novato: Sobre "game Loop"

Iniciado por Ruben, 25 de Noviembre de 2005, 12:47:27 PM

« anterior - próximo »

Ruben

 Hi,
he estado trasteando con DirectX 9.0c durante algun tiempo. He desarrollado un par de aplicaciones sencillitas en Visual C++, entre ellas un visualizador de animaciones 3D de una red neuronal de tipo I&F.

Creo que tengo los conceptos básicos pillados y voy a intentar meterme con algo más complicado. Asi que mi "chuper-diseño-para-ir-tirando-con-mil-variables-globales" se me ha quedado muy pequeño. Vamos, que ni loco me meto en algo un pelin mas complicado con el diseño de aplicacion que tengo ahora mismo. Es un suicidio....  (nooo)

Tengo pensado hacerme un marco de trabajo sencillito que me sirva como base para cualquier proyecto relacionado con juegos/simulaciones/etc....
En este marco habría una clase llamada cAplicacion que tendría como objetivo correr todo el sistema. El winmain estaría separado en un archivo y en el, se instanciaría cAplicacion para ejecutar el programa: inicializacion de graficos, sonido, etc.... Además tendría otras clases como cGraficos, cSonido, cInput para manejar el resto del sistema. Estas clases las usuaría cAplicacion.

Con esto como punto de partida tengo dos ideas:
1. Tener en cAplicacion metodos con nombre runX(), donde X es Menu, Intro, Juego... Cada metodo con su propio "game loop", de tal forma que en cada "game loop" solo habrá que manejar información del estado(Intro, Menu, Juego,Creditos,...) en el que te encuentras. Además tener un metodo run(), que se llamaría desde el winMain, despues de inicializar la instanciación de cAplicacion, claro esta.

2. Tener en cAplicacion un metodo con nombre run(). Este metodo tendra un "game loop" en el que se implementará la logica para pasar de un estado a otro de la aplicación. Asi que, en cada iteración del programa habrá que comprobar en que estado se esta y hacer lo correspondiente.

Ahi estan las dos opciones que se me han ocurrido. Personalmente, me parece mejor la primera. Aunque como no estoy seguro, prefiero que me aconsejeis vosotros.

Si teneis alguna otra opción mas interesante que las que he propuesto yo postearlas. (ole)

Un saludo y gracias,
Rubén

CoLSoN2

 El tema de estados de un juego en concreto y arquitectura de un juego en general es algo que da mucho juego, pues es un tema complejo que se puede solucionar de muchas formas distintas, y que a mí personalmente me atrae mucho porque me encanta el diseño de software a un nivel de abstracción alto. Quizá me plantee una serie de artículos sobre ello.

Centrándonos en tu caso particular, yo optaría por tener un único game loop (método run()) y trataría los distintos estados de forma independiente a esa clase. Hay gente que lo hace con una máquina de estados pero a mí personalmente me gusta añadirle una pila a dicha máquina, para poder volver a estados anteriores. Algo así:


class Application
{
std::stack<GameState*> mStates;

void PushState(GameState* theState)
{
 mStates.top()->OnLoseFocus();
 mStates.push(theState);
 mStates.top()->OnEnter();
}
void PopState()
{
 mStates.top()->OnLeave();
 mStates.pop();
 mStates.top()->OnFocus();
}
void Run()
{
 UpdateInput();
 mStates.top()->Update();
 DrawScene();
}
};


Yo este concepto de estados lo uso para las distintas pantallas de GUI, porque mis juegos suelen ser muy sencillos y el gameplay no requiere de varios estados, normalmente.

Por ejemplo, sólo el estado de GUI activo recibe eventos de los widgets, por lo que si estoy en el menú principal y suelto un cuadro de diálogo o la ventana de opciones, si hago PushState(new OptionsScreen()); sólo los widgets de la pantalla de opciones estarán activos, aunque los otros sean visibles. Luego hago PopState(); y vuelvo al menú principal. Ese mismo código para manejar las opciones me sirve tanto si muestro esa pantalla desde el menú principal como desde el juego, pues al volver el juego recuerda el estado anterior.

También, los métodos OnEnter/OnLeave/OnFocus/OnLoseFocus sirven para tener más control, aunque normalmente sólo uso los dos primeros.

Si mi tipo de juego fuera más complejo, tendría una estructura como esa para dividir el gameplay en estados, además de la que utilizo para pantallas de GUI.
Manuel F. Lara
Descargar juegos indie  - blog sobre juegos indie y casual
El Desarrollo Personal.com  - blog sobre productividad, motivación y espíritu emprendedor

chechocossa

 Algo parecido a ColSon2... Esto es C# con Irrlicht


public void Run()
{
 // Inicialización del Dispositivo Gráfico, cámara, scene manager, etc.
 // Cargo en memoria recursos como Meshes, Textures, etc.
 // Inicializo los primeros estados
 // Inicializo la parte GUI del HUD
 Loop();
} // End of Run()


El bucle del juego... de lo más simple


/// <summary>
/// Bucle principal del Juego
/// </summary>
public void Loop()
{
 while ( oGraphDevice.device.Run() )
 {
   if ( oGraphDevice.device.WindowActive )
   {
     oEntityMgr.UpdateEntities( oGraphDevice, oViewMgr );
     oEventMgr.ManageEvents( oGraphDevice );
     oViewMgr.Render( oGraphDevice, oHud );
   }
 } // End of Drawing-loop while()
} // End of Loop()


En una clase EntityManager tengo algo así:


/// <summary>
/// Actualiza todas las entidades entre cada bucle del loop principal
/// </summary>
/// <param name="oGraph">Dispositivo gráfico</param>
/// <param name="oViewMgr">Manager de Gráficos</param>
public void UpdateEntities(GraphicDevice oGraph, ViewManager oViewMgr)
{
 // Controlo el estado del Juego
 switch ( GameStates.AlGameState )
 {
   case "StarSys" :
     oStarSysMgr.UpdateEntities( oGraph, oViewMgr, this );
     break;
   case "Planet" :
     oPlanetMgr.UpdateEntities(oGraph, oViewMgr );
     break;
   case "Ship" :
     oShipMgr.UpdateEntities( oGraph, oViewMgr );
     break;
   } // End del Switch de GameStateNow()
} // End of UpdateEntities()


GameStates.AlGameState es un ArrayList que funciona como una pila, insertando o removiendo estados de acuerdo al juego.

Después, dentro de la clase Entidad StarSysManager, algo así:


/// <summary>
/// Actualiza todas las entidades de este Systema Solar
/// </summary>
/// <param name="ograph">Dispositivo gráfico</param>
/// <param name="oViewMgr">Manager de Gráficos</param>
/// <param name="oEntityMgr">Manager de Entidades</param>
public void UpdateEntities( GraphicDevice oGraph, ViewManager oViewMgr, EntityManager oEntityMgr )
{
 // Controlo el SUB estado del Juego
 switch ( GameStates.GameSubStateNow )
 {
   case GameStates.GameSubState.Load:
     // Antes de un Load, tengo que resetear la cámara
     oGraph.oCamera.ResetCamera( oGraph.camBlack );
     LoadStarSystem( oGraph, oViewMgr, oEntityMgr );
     GameStates.GameSubStateNow = GameStates.GameSubState.Update;
     break;
   case GameStates.GameSubState.Update:
     UpdateStarSystem( oGraph, oViewMgr, oEntityMgr );
     break;
   }
} // End of UpdateEntities()


Los subestados principales de cada estado, son Load y Update.

Saludos!
ergio Cossa

http://www.fatherjoe.com.ar - Father Joe Mobile
http://www.fantasticzone.blogspot.com - Fantastic Zone Blog
http://www.fantasticzone.com.ar - Fantastic Zone Page
Argentina

Ruben

 Hi,
gracias por las respuestas.  (ole)

La verdad es que la idea de la pila esta muy chula, no se como no se me ha ocurrido. De esa manera el codigo de mi , desde ahora bautizado como, "chuper-framework"  :D , es mucho mas general y seguramente me valga no solo para juegos si no para futuras aplicaciones.

De momento voy a quedarme con la idea de la pila y a ver como lo desarrollo. No creo que necesite nada mas complejo que eso. Por cierto, ¿sabeis de algun libro que comente sobre diseño de software relacionado con juegos? He encontrado un libro y algun capitulo en el libro de Daniel Sanchez Crespo. Me gustaría tener otros puntos de vista distintos.

Pues nada, ya solo queda ponerse manos al "teclado"...  :P

Un saludo y gracias,
Rubén

CoLSoN2

 Está el Software Engineering for Game Developers y el Software Engineering and Computer Games. Si mal no recuerdo en la biblioteca de la UPF tienen ambos, por si vives cerca. Yo los he estado ojeando y son más bien normalitos, quizá el primero está mejor (también es más nuevo).
Manuel F. Lara
Descargar juegos indie  - blog sobre juegos indie y casual
El Desarrollo Personal.com  - blog sobre productividad, motivación y espíritu emprendedor

Warchief

 Lo de la pila me parece bien, pero en general no veo qué utilidad puede tener volver tantos estados hacia atrás, y sin embargo, veo crecer la pila. Yo haría, dependiendo de la magnitud que se espera, un patrón strategy (con singletons) o una máquina de estados.

Strategy:
http://exciton.cs.rice.edu/javaresources/D...tegyPattern.htm

FSM:
http://www.ai-junkie.com/architecture/stat...tut_state1.html


Ambos permiten a 1 estado anterior o N dependiendo de implementación. Aunque la de Colson parece simple y eficaz.

tamat

 Yo en mi framework tengo un sistema de tareas, estas tareas se agregan a un contendor de tareas que las ejecuta secuencialmente. Cosas como el render y pillar el input tienen una tarea especifica, estas tareas tienen una frecuencia de actualización por si es una tarea que debe ejecutarse muy rapido o cada cierto tiempo.

Lo bueno es que mis tareas a su vez pueden contener otras tareas (patron composite), entonces para el tema de estados yo lo que tengo son tareas que representan cada estado, todas dentro de una tarea "logica de juego", y conforme se cambia de estado pues congelo o descongelo la tarea pertinente (si está congelada el sistema no la ejecuta durante el loop). Así hasta podría tener varios estados a la vez en funcionamiento.
Por un stratos menos tenso

Ruben

 Hi,
más que nada, la duda me entra al pensar que la lógica que cambia de un estado a otro de la aplicación ( no me estoy refiriendo a los de la fsm) la poneis siempre dentro del loop. ¿No debería estar fuera del bucle de juego? Son comprobaciones sencillas que al fin y al cabo no consumen apenas, pero ¿no sería mejor sacar la lógica de estados fuera del loop?

Basicamente yo lo que había pensado (Nota: ir a pirmer post apartado 1) antes es que cada estado tuviese su propio game loop y que de alguna forma la lógica de cambio de estado solo se ejecutase cuando se haya terminado un estado y hay que decidir a que estado ir.

Por cierto, estoy viendo que con este tema hay mil y una soluciones posibles. Intentaré decidirme por la que mas se ajuste a lo que necesito, aunque se que voy a tirar por la solución que mas chula este....... (genial)  (genial)

Un saludo,
-Rubén-

Warchief

 
Cita de: "Ruben"Hi,
más que nada, la duda me entra al pensar que la lógica que cambia de un estado a otro de la aplicación ( no me estoy refiriendo a los de la fsm) la poneis siempre dentro del loop. ¿No debería estar fuera del bucle de juego? Son comprobaciones sencillas que al fin y al cabo no consumen apenas, pero ¿no sería mejor sacar la lógica de estados fuera del loop?
A mi no me gusta esa idea porque al final tendrás un módulo enorme con un montón de ifs.

Yo prefiero que cada estado sepa a dónde debe ir al recibir un evento. Eso crea dependencias entre estados útiles cuando desaparece un estado o cambia.


por ejemplo:


EstadoA::Control()
{

 if(eventoX) {
    // Hacer blabla
 }
 if(eventoY) {
    // Hacer blabla
 }

 if(eventoZ) {
      fsm.cambiarEstado(EstadoB); // Esto llama a EstadoA.exit() y EstadoB.enter()
 }

}



Si el estadoB cambia, estadoA no se ve afectado.
Sin embargo, si estadoB desaparece estadoA no compila, obligando a cambiar su comportamiento.

La combinación de la FSM de Buckland con los templates de Singleton es muy buena.
(una de las posibles templates del Singleton: http://www.codeproject.com/cpp/singleton_template.asp)

Pogacha

 La verdad es que yo no me decido, ... el patrón que propone "Clasificar" los estados me parece que engorda mucho al codigo y ensucia el compartir comportamientos en mismos estados ... pero por otro lado transforma los problemas de sintaxis a problemas de diseño reduciendo la posibilidad de errores ... es un tema para pensar mucho ...
Me parece que tendria que experimentar en un juego y ver cuales son las verdaderas ventajas ...

@Warchief: Muy buenos los links

Saludos.

ethernet

 Yo he usado un sistema muy similar al que Colson propone, pero no lo uso de la misa forma que él lo hace.
Tengo una clase ScreenManager que hace las veces de Aplication en la explicación de Colson y una clase Screen (GamState) de forma que al comienzo de la aplicación pongo todas en el manager.


screenManager.Add(new MenuScreen,"menu");
screenManager.Add(new Game1PScreen,"game1P");
screenManager.Add(new CreditsScreen,"creditos");
//etc


Para variar de unas a otras basta con hacer Jump("nombre_pantalla"); o un Pop() para volver a la pantalla desde la que fue llamado, muy útil para pantallas compartidas. Por ejemplo en MakeFight se puede llamar al editor de coches desde el menú ppal y desde el menú de usuarios, pues al terminar de crear el coche BulidCarScreen hace un Pop (que en el fondo hará  screenManager.Pop() ) y retorna a la pantalla desde la que fue llamado.

El paso de variables entre estados en mi caso es algo lamentable, tengo un map con el nombre de variale y su valor y también se puede hacer:

screenManager["menu"].variable_de_la_clase;


En C++ habría que hacer un upcast, python es muchísimo más flexible en eso y permite hacer cosas más interesantes :).


Ya que estamos, en cuanto al GUI tengo un sistema que almacena todas las Capas (guiManager) y en cada OnFocus de cada Screen activas las correspondientes:

guiManager.Activate([1 5 7 8]);

Lógicamente conviene tener siempre cargadas las de uso común y cargar solo las necesarias cuando no se usen demasiado.

un saludo

Warchief

Cita de: "ethernet"En C++ habría que hacer un upcast, python es muchísimo más flexible en eso y permite hacer cosas más interesantes :).
En c++, parece un patrón strategy con functores. Eso sí que es interesante.




enum Estados{
 A,
 B,
 ....
 NUM_ESTADOS
}

CFunctor* [NUMESTADOS];



Un enlace bueno sobre Functores:
http://www.newty.de/fpt/functor.html

ethernet

 Muchos de los patrones de diseño usados en C++ en python no tienen ningún sentido sobretodo porque python no es tan fuertemente tipado y puedes hacer lo que quieras. Por ejemplo, en makefight las mallas puedes aplicarlas las propiedades que quieras y para ello lo que hago es crear un atributo en el objeto que cargo para después usarlo en un script, es algo así como definir una variable en tiempo de ejecución.

Otro ejemplo que viene más al caso, el Functor. En python puedes hacer lo que quieras, esto es:



class A:
  def __init__(): pass;
  def  foo(): print "holas";

a  = A();
f = lambda : a.foo();
f();



Con lo cual el patrón functor pierde todo el sentido, así como la herencia pierde algo de sentido, etc, etc. La ventaja de todo esto es que permite hacer de todo sin preocuparte de tener miles de tipos. La desventaja es que si no lo controlas se vuelve un infierno.






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.