Estaba pensando en cómo implementar estados de juego en mi juego. Las cosas principales que quiero para ello son:
Estados superiores semitransparentes: poder ver a través de un menú de pausa el juego detrás
Algo OO-Me parece más fácil de usar y entender la teoría detrás, así como mantenerlo organizado y agregarle más.
Estaba planeando usar una lista vinculada y tratarla como una pila. Esto significa que podría acceder al siguiente estado para la semi-transparencia.
Plan: haga que la pila de estados sea una lista vinculada de punteros a IGameStates. El estado superior maneja sus propios comandos de actualización y entrada, y luego tiene un miembro isTransparent para decidir si se debe dibujar el estado debajo.
Entonces podría hacer:
states.push_back(new MainMenuState());
states.push_back(new OptionsMenuState());
states.pop_front();
Para representar la carga del jugador, luego vaya a las opciones y luego al menú principal.
¿Es una buena idea, o ...? ¿Debo mirar algo más?
Gracias.
fuente
new
de la manera que se muestra en el código de muestra, solo está pidiendo pérdidas de memoria u otros errores más graves.Respuestas:
Trabajé en el mismo motor que coderanger. Tengo un punto de vista diferente. :)
Primero, no teníamos una pila de FSM, teníamos una pila de estados. Una pila de estados forma un solo FSM. No sé cómo se vería una pila de FSM. Probablemente demasiado complicado para hacer algo práctico.
Mi mayor problema con nuestra máquina de estado global era que era un conjunto de estados, y no un conjunto de estados. Esto significa, por ejemplo, ... / MainMenu / Loading era diferente de ... / Loading / MainMenu, dependiendo de si aparecía el menú principal antes o después de la pantalla de carga (el juego es asíncrono y la carga es principalmente impulsada por el servidor )
Como dos ejemplos de cosas esto hizo feo:
A pesar del nombre, no era muy "global". La mayoría de los sistemas de juego internos no lo usaban para rastrear sus estados internos, porque no querían que sus estados se burlaran de otros sistemas. Otros, por ejemplo, el sistema UI, podrían usarlo pero solo para copiar el estado en sus propios sistemas estatales locales. (Especialmente advertiría contra el sistema para los estados de la interfaz de usuario. El estado de la interfaz de usuario no es una pila, es realmente un DAG, y tratar de forzar cualquier otra estructura en él solo hará que las UI sean frustrantes de usar).
Lo que fue bueno fue aislar las tareas para integrar el código de los programadores de infraestructura que no sabían cómo se estructuraba realmente el flujo del juego, para que pudieras decirle al tipo que escribe el parche "pon tu código en Client_Patch_Update", y al tipo que escribe los gráficos cargando "ponga su código en Client_MapTransfer_OnEnter", y podríamos intercambiar ciertos flujos lógicos sin muchos problemas.
En un proyecto paralelo, he tenido mejor suerte con un conjunto de estados en lugar de una pila , sin tener miedo de hacer varias máquinas para sistemas no relacionados, y me niego a caer en la trampa de tener un "estado global", que es realmente solo una forma complicada de sincronizar las cosas a través de variables globales: seguro, terminarás haciéndolo cerca de una fecha límite, pero no diseñes con eso como tu objetivo . Básicamente, el estado en un juego no es una pila, y los estados en un juego no están todos relacionados.
El GSM también, como tienden a hacer los punteros de función y el comportamiento no local, hizo que la depuración sea más difícil, aunque depurar ese tipo de grandes transiciones de estado tampoco fue muy divertido antes de que lo tuviéramos. Los conjuntos de estados en lugar de las pilas de estados realmente no ayudan a esto, pero debes ser consciente de ello. Las funciones virtuales en lugar de los punteros de función pueden aliviar eso de alguna manera.
fuente
Aquí hay un ejemplo de implementación de una pila de gamestate que encontré muy útil: http://creators.xna.com/en-US/samples/gamestatemanagement
Está escrito en C # y para compilarlo necesita el marco XNA, sin embargo, puede consultar el código, la documentación y el video para obtener la idea.
Puede admitir transiciones de estado, estados transparentes (como cuadros de mensajes modales) y estados de carga (que gestionan la descarga de estados existentes y la carga del estado siguiente).
Ahora uso los mismos conceptos en mis proyectos de pasatiempos (no C #) (concedido, puede que no sea adecuado para proyectos más grandes) y para proyectos pequeños / pasatiempos definitivamente puedo recomendar el enfoque.
fuente
Esto es similar a lo que usamos, una pila de FSM. Básicamente, solo asigne a cada estado una función de entrada, salida y marca y llámelos en orden. Funciona muy bien para manejar cosas como cargar también.
fuente
Uno de los volúmenes de "Gemas de programación de juegos" tenía una implementación de máquina de estados destinada a estados de juegos; http://emergent.net/Global/Documents/textbook/Chapter1_GameAppFramework.pdf tiene un ejemplo de cómo usarlo para un juego pequeño, y no debe ser demasiado específico de Gamebryo para ser legible.
fuente
Solo para agregar un poco de estandarización a la discusión, el término clásico de CS para este tipo de estructuras de datos es un autómata pushdown .
fuente
No estoy seguro de que una pila sea completamente necesaria, además de limitar la funcionalidad del sistema de estado. Con una pila, no puede 'salir' de un estado a una de varias posibilidades. Digamos que comienza en "Menú principal" y luego va a "Cargar juego", puede que desee pasar al estado "Pausa" después de cargar con éxito el juego guardado y volver al "Menú principal" si el usuario cancela la carga.
Solo me gustaría que el estado especifique el estado a seguir cuando salga.
Para aquellos casos en los que desea volver al estado anterior al estado actual, por ejemplo "Menú principal-> Opciones-> Menú principal" y "Pausa-> Opciones-> Pausa", simplemente pase como parámetro de inicio al estado el Estado para volver a.
fuente
Otra solución para las transiciones y otras cosas similares es proporcionar el destino y el estado de origen, junto con la máquina de estado, que podría estar vinculada al "motor", sea lo que sea. La verdad es que la mayoría de las máquinas de estado probablemente tendrán que adaptarse al proyecto en cuestión. Una solución puede beneficiar a este o aquel juego, otras soluciones pueden obstaculizarlo.
Los estados se envían con el estado actual y la máquina como parámetros.
Los estados aparecen de la misma manera. Si llama
Enter()
al inferiorState
es una pregunta de implementación.Al ingresar, actualizar o salir,
State
obtiene toda la información que necesita.fuente
Utilicé un sistema muy similar en varios juegos y descubrí que, con un par de excepciones, sirve como un excelente modelo de interfaz de usuario.
Los únicos problemas que encontramos fueron casos en los que en ciertos casos se desea volver a mostrar múltiples estados antes de presionar un nuevo estado (volvimos a fluir la interfaz de usuario para eliminar el requisito, ya que generalmente era un signo de una interfaz de usuario incorrecta) y crear un estilo de asistente flujos lineales (se resuelven fácilmente pasando los datos al siguiente estado).
La implementación que utilizamos en realidad envolvió la pila y manejó la lógica para actualizar y renderizar, así como las operaciones en la pila. Cada operación en la pila desencadenó eventos en los estados para notificarles sobre la operación que está ocurriendo.
También se agregaron algunas funciones auxiliares para simplificar tareas comunes, como Swap (Pop & Push, para flujos lineales) y Reset (para volver al menú principal o finalizar un flujo).
fuente
Este es el enfoque que adopto para casi todos mis proyectos, porque funciona increíblemente bien y es extremadamente simple.
Mi proyecto más reciente, Sharplike , maneja el flujo de control de esta manera exacta. Todos nuestros estados están conectados con un conjunto de funciones de eventos que se llaman cuando los estados cambian, y presenta un concepto de "pila con nombre" en el que puede tener múltiples pilas de estados dentro de la misma máquina de estados y ramificar entre ellos, un concepto herramienta, y no necesaria, pero útil para tener.
Advierto contra el paradigma "dígale al controlador qué estado debe seguir este cuando termine" sugerido por Skizz: no es estructuralmente sólido, y hace cosas como cuadros de diálogo (que en el paradigma estándar de estado de pila solo implica crear un nuevo subclase de estado con nuevos miembros, luego leerlo cuando regrese al estado de invocación) mucho más difícil de lo que tiene que ser.
fuente
Utilicé básicamente este sistema exacto en varios sistemas ortogonalmente; los estados frontend y del menú del juego (también conocido como "pausa"), por ejemplo, tenían sus propias pilas de estados. La interfaz de usuario del juego también usó algo como esto, aunque tenía aspectos "globales" (como la barra de salud y el mapa / radar) que el cambio de estado podría teñir pero que se actualizaba de manera común en todos los estados.
El menú del juego puede estar "mejor" representado por un DAG, pero con una máquina de estado implícita (cada opción de menú que va a otra pantalla sabe cómo ir allí, y al presionar el botón Atrás siempre aparece el estado superior) el efecto fue exactamente lo mismo.
Algunos de estos otros sistemas también tenían la funcionalidad de "reemplazar el estado superior", pero eso se implementaba normalmente de la
StatePop()
siguiente maneraStatePush(x);
.El manejo de la tarjeta de memoria fue similar ya que en realidad empujé un montón de "operaciones" en la cola de operaciones (que funcionalmente hacía lo mismo que la pila, igual que FIFO en lugar de LIFO); una vez que comienza a usar este tipo de estructura ("hay una cosa que está sucediendo ahora, y cuando se hace, aparece") comienza a infectar cada área del código. Incluso la IA comenzó a usar algo como esto; la IA fue "despistada" y luego cambió a "cautelosa" cuando el jugador hizo ruidos pero no fue visto, y finalmente se elevó a "activa" cuando vio al jugador (y a diferencia de los juegos menores de la época, no se podía ocultar en una caja de cartón y haz que el enemigo se olvide de ti! No es que esté amargado ...).
GameState.h:
GameState.cpp:
fuente