jueves, 21 de noviembre de 2013

7) WorldInfo y GameInfo. Spawneando entre la vida y la muerte (y las reglas de juego)

¡Muy buenas!

Ya estamos aquí otra vez para explicar un poquito otra de las partes que tenemos en UDK y que es muy importante: WorldInfo y GameInfo.

Como bien nos dice Mougli (http://www.moug-portfolio.info/game-info/), GameInfo es el corazón denuestro juego, y debe poseer las reglas de "juego". Entre ellas, se encontrará la manera de spawnear (crear una instancia de nuestro Pawn y PlayerController) y dónde debe realizarlo. Así mismo, determinará cuando termina un juego y cuando no. El término juego en este caso, se refiere a todo lo que ocurre entre la carga de un mapa y otro. ¿Porqué? Porque toda información en GameInfo desaparece con la carga. Deberemos por tanto, tener esto en cuenta para la realización de nuestro juego.

Como curiosidad y dato importante, existe la clase WorldInfo que es la encargada de guardar todo lo relacionado con un mapa: Secuencias de Kismet, Actores, NavigationPoints... Contiene el árbol de escena del mapa así como una instancia de GameInfo. Este planteamiento de programación encaja perfectamente con la estructura utilizada principalmente por este motor: Shooter en primera persona multiplayer. De este modo, tendremos un GameInfo con información de DeathMatch, con información de Capturar la bandera, etc... Y podremos cambiar entre ellos de mapa en mapa y podremos identificar que tipo de GameInfo cargar con el sistema de prefijos en los mapas que tiene UDK (en los ficheros de configuración podemos asignar que prefijos en los nombres de los mapas cargan qué tipos de GameInfo).

Métodos y acceso a GameInfo desde cualquier lugar

Como os he indicado más arriba, desde WorldInfo podéis acceder a cualquier actor de la escena. Pero por supuesto, con un coste. No es recomendable abusar de estas llamadas, pero tampoco escatimar en su llamada si es realmente necesario. El rendimiento deberíais estudiarlo tras hacer que las cosas funcionen, así que, de momento mi recomendación es que no os volváis demasiado locos con esto.

Con la llamada GetWorldInfo() os devolverá la instancia de WorldInfo actual. Desde allí o bien, si tenéis visibilidad directa de WorldInfo, podéis obtener Game (que es el GameInfo actual, quedando del siguiente modo: WorldInfo.Game). Está claro, que si queréis acceder a métodos de vuestro GameInfo particular, por ejemplo NULLGameInfo, deberéis realizar un casting: NULLGameInfo(WorldInfo.Game).

También podéis recorrer todos los controladores y actores desde WorldInfo usando un Foreach y las estructuras de datos WorldInfo.AllControllers y WorldInfo.AllActors. Os recomiendo echarle un vistazo a la clase WorldInfo en caso de que necesitéis acceder desde una clase a algo de la que no tenéis referencia (Por ejemplo, desde GameInfo no tenéis referencia del jugador).

Más información en: http://wiki.beyondunreal.com/UE3:WorldInfo_%28UDK%29


El flow de ejecución

Antes de ponernos manos a la obra, es interesante leer el artículo de la UDN que nos indica el flow de ejecución de UDK.  (http://udn.epicgames.com/Three/UnrealScriptGameFlow.html)

Voy a seguir los pasos de Mougli para explicaros un poco el funcionamiento de esta clase y su utilización. ¡Gracias Mougli!

Propiedades notables en GameInfo.uc

Mougli nos hace mención de todas estas, ya que algunas de ellas las podemos utilizar dependiendo del tipo de juego que queramos realizar. Basta con echar un vistazo a la clase base GameInfo para buscar referencias sobre las mismas y su utilización.

bPauseable
bTeamGame
bDelayedStart
GameDifficulty
MaxSpectatorsAllowed
MaxPlayersAllowed
GoalScore
MaxLives
TimeLimit

SetGameType() de GameInfo.uc e InitGame()


Pues como su nombre indica, le sirve al motor internamente para elegir que tipo de clase se debe utilizar. Por defecto, si le echamos un vistazo, llama al GameInfo utilizado por defecto en los ficheros de configuración (DefaultGame.ini). Igualmente, si utilizamos el sistema de prefijos, un poco más abajo en ese mismo fichero, se seleccionará el juego por defecto según el tipo de mapa.

Por otro lado, tenemos el evento InitGame que es llamado antes que cualquier PreBeginPlay de cualquier actor dentro del motor. Aquí se configura lo necesario con respecto al tipo de juego y podemos utilizarlo para spawnear clases de ayuda. En mi caso, utilizo dicha función para comprobar si estoy utilizando una partida guardada (Siguiendo las directrices del UDKGem de guardado de partidas con JSON aquí: http://udn.epicgames.com/Three/DevelopmentKitGemsSaveGameStates.html). Si se desean realizar diferentes tipos de juego, es ahí donde se debe modificar la función. Podéis echar un vistazo al código del método en la clase base GameInfo del motor.


Estados en la clase GameInfo.uc


Si observamos el código de la clase GameInfo, veremos que contiene diferentes estados. Por defecto (Auto state) se llama al estado PendingMatch. Dicho estado tiene su propia función StartMatch que es la encargada de avisar a todos los actores que la partida comienza. Podéis modificar cuanto queráis a la hora de crear nuevos estados o simplemente, por defecto, utilizar este si no queréis complicaros la vida. Básicamente, este estado termina llamando a Global.StartMatch() cuando se llama a su método StartMatch(). Vemos lo que nos indica la UDN:

Match Start

The actual game, as in the gameplay that happens from the time the player is spawned until the game ends, is often referred to as a match. This is just a term and has no bearing on the type of games that can be created with Unreal Engine 3.

The match is begun when the StartMatch() function of the gametype is called from the PostLogin() event (also called from StartMatch() andStartArbitratedMatch() in the PendingMatchstate). This function is responsible for spawning the player’sPawns and notifying all of the players that the match has begun. 
function StartMatch()
The actual spawning of the players’ Pawns occurs in the RestartPlayer() function. This is called from the StartHumans() and StartBots() functions, which are in turn called from the StartMatch() function. In addition to spawning the Pawns, this function locates a starting spot, e.g. a PlayerStart Actor, to start the player from. 
function RestartPlayer(Controller NewPlayer)

Básicamente, nos dice que la partida comienza con la llamada StartMatch, que se llama desde PostLogin(). En ese momento, StartMatch se encarga de "spawnear" el Pawn del jugador y notificar a todos los jugadores que ha comenzado la partida. 

En la función RestartPlayer() se realiza dicho "spawneado". Aquí es dónde se analiza, de todos los PlayerStart (En categoría Navigation en ActorClasses del ContentBrowser) que existen, y mediante un algoritmo de puntuaciones, dónde debe spawnear el Pawn y su controlador. Aquí deberemos modificar la funcionalidad para spawnear arquetipos si los vamos a utilizar o bien, como es mi caso, spawnear varios Pawns diferentes que queramos controlar si es que tenemos más de uno. No queda otra que analizar paso a paso qué realiza el método y estudiarlo para ver qué podemos y queremos realizar nosotros. Es por ello que esta entrada, para que no se haga eterna, requiere de un esfuerzo mayor por vuestra parte.
Es importante también destacar el método function Pawn SpawnDefaultPawnFor(Controller NewPlayer, NavigationPoint StartSpot) dónde como podréis comprobar en RestartPlayer, se indica que tipo de Pawn debe spawnearse según que controlador y punto de inicio. Exactamente es esta función en la que se debe cambiar la llamada de Spawn clásica por un Spawn(DefaultPawnArchetype.Class,,,StartSpot.Location,StartRotation,DefaultPawnArchetype); para poder utilizar arquetipos como pawns.

Finalmente, la función function Killed( Controller Killer, Controller KilledPlayer, Pawn KilledPawn, class<DamageType> damageType ) es llamada cuando muere un Pawn. Deberemos filtrar que Pawn es el que ha muerto y de ahí, realizar las acciones pertinentes. Por ejemplo, podemos hacer un Fade Out con la cámara programado y activar un timer a una función (SetTimer(3,false,’EndingGame’);) . De manera sencilla, en ese método EndingGame utilizar el comando de consola PC.ConsoleCommand(“RestartLevel”); Por supuesto, deberéis realizar tanta funcionalidad extra y comprobaciones como vuestro proyecto necesite.

Como véis, este apartado, siendo importante, requiere de un esfuerzo por vuestra parte de investigar en el código y entenderlo conociendo las directrices que sigue UDK y las llamadas realizadas en el flow de código.

Detectando el final de la partida

Cuando hablamos de detectar el final de una partida, ya estamos haciendo un acercamiento a lo que son las reglas de juego de nuestro proyecto. En este apartado intervienen algunos métodos que debemos comentar:

  • EndGame, llama a su vez a CheckEndGame y por ende, nos da la pista de tener que llamar a esta función cada vez que nuestro juego pueda terminar. En el ejemplo que Mougli nos indica, cada vez que recolectamos una moneda en un juego en el que hay que recolectarlas todas o por ejemplo, cuando el tiempo necesario para poder recolectarlas todas se ha terminado. Recibe un PlayerReplicationInfo con la información del jugador ganador si lo hay y un string con el mensaje por la causa de haber finalizado la partida.
  • CheckModifiedEndGame, llamado por CheckEndGame y se encarga de detectar si por los mutadores del mapa se termina la partida. Si no se ha terminado se devuelve un false, en caso afirmativo, se notifica a todos los Controllers que ha terminado el juego (eso en CheckEndGame):  
foreach WorldInfo.AllControllers(class'Controller', P)
P.GameHasEnded();
    • RestartGame, una vez terminada una partida, se encarga de reiniciar el mapa y la partida, o de cambiar de mapa.
    • GetNextMap, es el encargado de devolver el siguiente mapa que se cargará. Por defecto devuelve "" y por tanto, se carga el mismo mapa (ver RestartGame). Recordemos que cargar un nuevo mapa, equivale a recargar un nuevo GameInfo y perder la información almacenada ahí. Una manera de evitar esto, es realizar carga por streaming, que como nos indica Mougli, es lo que sospecha se hace en Gears of War, aunque no lo sepa con certeza absoluta.
    El juego de monedas de Mougli, ¡un ejercicio perfecto!

    Pues así es, es un ejercicio perfecto para poder practicar un poco tanta teoría que hemos visto arriba. Además, que va a ser la parte básica para el ejercicio práctico de mis alumnos. No lo copio aquí, ya que el mérito lo tiene nuestro querido Mougli y por tanto, os redirijo allí para ponernos manos a la obra: 





    No hay comentarios:

    Publicar un comentario