Aravid

Tutoriales, Gamer Sense, opinión y más

Archivos por Etiqueta: UDK script

UDK: Jugando con la cámara I

Pensando un poco desde el anterior tutorial (hace algo de tiempo), me he dado cuenta de que intentar manejar un juego con el ratón, sin antes explicar algo sobre la cámara sería problemático, porque aunque existen juegos que hacen uso del ratón en una cámara en primera persona (la que nos provee por defecto UDK), digamos que no es lo más habitual, así que aquí comienza una nueva saga en la que comento un poco el uso de la cámara para conseguir una en tercera persona.

En esta entrega, haremos uso de los siguientes ficheros:

  • Fichero del Pawn, DCleanerPawn.uc
  • Fichero del PlayerController, DCleanerPC.uc
  • Fichero del GameInfo, DCleanerGI.uc
  • Fichero de Inputs, DefaultInput.ini

Ahora que ya tenemos la lista de la compra, explicaré el objetivo de esta primera parte. Crearemos una cámara en tercera persona localizada a una distancia fija del personaje, que se moverá con él, y que con la rueda del ratón, podremos elevar o descender la cámara, pero siempre haciendo que nuestro personaje quede centrado, como detalle, haremos una sencilla interpolación en el eje de altura (eje Z) para que los cambios de cámara sean suaves.

Comenzamos con un paso previo en caso de que sea necesario, en el fichero del GameInfo, tenemos que hacer que nuestro juego herede de UDKBase.SimpleGame o tendremos problemas más adelante, como que por ejemplo nada de lo que hagamos funcione, este es el aspecto de mi DCleanerGI.uc después de esta actualización.

class DCleanerGI extends UDKBase.SimpleGame;

defaultproperties
{
	PlayerControllerClass=class'DCleaner.DCleanerPC'
	DefaultPawnClass=class'DCleaner.DCleanerPawn'
	HUDType=class'DCleaner.DCleanerHUD'
}

Ya estamos en condiciones de comenzar, antes de entrar al trapo, voy a comentar un poco cómo funciona el fichero DefaultInput.ini y qué hace.

En este fichero hay dos tipos de línea, las que definen una GBA o Game Bindable Action, y las que asocian una tecla o botón de un mando, a una GBA. Esto nos provee una capa intermedia entre asociar un botón a un trozo de nuestro código, lo que podría crear algún problema o inconveniente a la hora de intercambiar la función de los botones dentro del juego o similar, por lo que creamos una GBA que consiste en un nombre, y un pequeño trozo de código que invocará un método que hayamos creado, y luego asignaremos un botón a una GBA.

No quiero entrar mucho en detalle, pero con esto es suficiente para nuestros propósitos, así que abrimos el fichero DefaultInput.ini, y en cualquier punto del mismo, podemos añadir estas líneas

.Bindings=(Name="GBA_AlejaCamara",Command="alejaCamara")
.Bindings=(Name="GBA_AcercaCamara",Command="acercaCamara")

y tenemos que hacer un cambio más, que es modificar unas líneas (las dos primeras líneas del siguiente bloque ya están en el fichero), yo suelo comentar y volver a escribir, pero sois libres de modificarlas

; .Bindings=(Name="MouseScrollUp",Command="GBA_PrevWeapon")
; .Bindings=(Name="MouseScrollDown",Command="GBA_NextWeapon")
.Bindings=(Name="MouseScrollUp",Command="GBA_AcercaCamara")
.Bindings=(Name="MouseScrollDown",Command="GBA_AlejaCamara")

Las primeras dos líneas definen dos nuevas GBAs, les asignamos el nombre y lo que realizarán, que es ejecutar el comando de consola alejaCamara y acercaCamara, esto ya nos dice que esos métodos deben de definirse de manera que podamos ejecutarlos en consola.

El segundo bloque asigna a la rueda del ratón nuestras GBAs, el problema es que ya está asignada a otra GBA, así que rápidamente comentamos las líneas con el símbolo del punto y coma  y decimos que invoque a nuestras GBAs.

Ahora abrimos el fichero del PlayerController, DCleanerPC.uc en mi caso y añadimos los métodos exec que serán ejecutados cuando utilicemos la rueda del ratón.

exec function alejaCamara()
{
	local DCleanerPawn p;
	p = DCleanerPawn(Pawn);
	if(p.cameraOffset.Z < 570)
		p.cameraOffset.Z += 30;
}

exec function acercaCamara()
{
	local DCleanerPawn p;
	p = DCleanerPawn(Pawn);
	if(p.cameraOffset.Z > 330)
		p.cameraOffset.Z -= 30;
}

Los métodos no son excesivamente complicados, explicaré el primero, que el segundo es igual cambiando un signo y el límite. Primero creamos una variable para almacenar nuestro Pawn personalizado, y hacemos un cast de la variable Pawn que tiene el PlayerController para poder acceder a las variables que hemos declarado en nuestro Pawn personalizado.

A continuación comprobamos que la Z (el eje de las alturas) del vector cameraOffset (hablo de este vector en el siguiente párrafo) que almacena el desplazamiento de la cámara respecto al personaje no haya excedido el valor determinado por nosotros, si no lo ha hecho, lo aumentamos y terminamos. Aclarar que los valores son totalmente arbitrarios y no hay ninguna razón para utilizar estos, se pueden utilizar cualquiera.

Ahora a modificar la cámara en el fichero del Pawn, pero en el código de antes hemos hablado de un vector que no está en nuestro DCleanerPawn.uc, así debemos de añadir una variable de tipo Vector que almacene los valores que debemos sumar a la posición del personaje para situar la cámara, con esto conseguiremos que la cámara siempre esté centrada respecto a nuestro personaje.

var Vector cameraOffset;

Ya tenemos nuestra variable, ahora tenemos que sobreescribir el método CalcCamera que se encarga de calcular la cámara, para ello, nos pasa como parámetro la posición de la cámara y la rotación de la misma para que los modifiquemos, así que los calculamos y los almacenamos en los vectores que no pasan como parámetro.

simulated function bool CalcCamera(float dt, out vector CamPos, out rotator CamRot, out float FOV)
{
	// FOV Para consola: 65
	FOV = 85;

	CamPos.X = Location.X + cameraOffset.X;
	CamPos.Y = Location.Y + cameraOffset.Y;
	CamPos.Z = FInterpto(CamPos.Z, Location.Z + CameraOffset.Z, 0.2, 1);
	CamRot = Rotator(Location-CamPos);

	return true;
}

Empieza la explicación del código, que no es muy complejo. Al vector camPos que recibimos como parámetro, sumamos a la componente X y a la componente Y los valores X e Y de cameraOffset directamente. Para la componente Z hacemos uso de un método interesante que nos puede ser más adelante, que es el FInterpTo que lo que hace es interpolar un float de un valor inicial a uno final especificando una velocidad y un tiempo de interpolación, con esto conseguimos que la actualización del eje Z de la cámara se haga de forma suave, más rápido al comienzo de la interpolación y más suave conforme llega al final.

Nota: Existen más funciones del tipo de FInterpTo, dependiendo del tipo de variable que interpole, como IInterpTo que interpola enteros o VInterpTo que interpola vectores.

Para finalizar, debemos de especificar unos valores iniciales para el vector cameraOffset, en mi caso, yo utilizo (0,600,450), que han sido fruto de la experimentación. La componente Z está en función de los números que hay en los métodos de acercaCamera y alejaCamara, así que si los modificáis, acordaros de también cambiar los números de esos métodos.

Aquí añado el bloque de defaultproperties de la clase Pawn, en el que especificamos los valores iniciales del vector cameraOffset y también un modelo para nuestro Pawn de los que nos provee UDK.

defaultproperties
{
	cameraOffset=(X=0,Y=600,Z=450)

	Begin Object Name=SKMesh class=SkeletalMeshComponent
		SkeletalMesh = SkeletalMesh'CH_LIAM_Cathode.Mesh.SK_CH_LIAM_Cathode'
		AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
		bOwnerNoSee = false
	End Object

	Mesh = SKMesh
	Components.Add(SKMesh)
}

Y con esto, ya está completa la cámara. Es una cámara bastante rudimentaria, pero es un comienzo. De hecho, es más avanzada que la cámara de la versión entregada de Dungeon Cleaner, que era fija y no se podía modificar ningún parámetro.

Nota: Si lo habéis probado, o quizá con el código os hayáis dado cuenta, solo modificamos el eje de las alturas, dejando la distancia en los otros dos ejes sin alterar, con esto conseguimos aumentar o reducir la verticalidad de la cámara respecto al personaje y como consecuencia la distancia, pero no podemos alejarla sin elevar la cámara. En el próximo tutorial, hablaré de cómo hacer una cámara que tiene la capacidad de orbitar a nuestro alrededor, cambiando el ángulo desde el que vemos al personaje, y la distancia de la cámara sin modificar la verticalidad de la cámara.

Anuncios

UDK: Ratón en la interfaz I

Ahora que tenemos el GameInfo que especifica cuál será la clase que gestionará el HUD, vamos a crear un HUD con la particularidad de que dispondremos de un cursor que podemos mover con el ratón, y mostraremos en la esquina superior izquierda información relacionada con él, en este caso la posición 2D dentro de la ventana, la posición 3D en la que se encuentra el cursor la mostraremos en la siguiente parte, porque requiere una pequeña explicación, y creo que vale la pena. Más adelante podremos utilizar esta información para interactuar con el juego, pero vayamos por partes.

Así terminará el tutorial, con un ratón en forma de flecha y la información de posición en la esquina superior izquierda en un gris sospechosamente claro

Creo que es justo nombrar el artículo de la UDN que tanto me ayudo durante el proceso de creación de esta parte en Dungeon Cleaner. Incluso recomendaría su lectura antes de proseguir (puedo entender que es algo largo, en momentos un poco técnico y tiene mucho UDK script) y es el siguiente:

http://udn.epicgames.com/Three/DevelopmentKitGemsCreatingAMouseInterface.html

De hecho, la mayoría de lo que yo voy a explicar se puede extraer de ahí, la diferencia principal es que el artículo ayuda para la creación de una interfaz genérica y hay ciertas cosas que no realiza que son importantes si queremos utilizar el ratón como método principal de juego, que es lo que ocurre en nuestro caso.

Si lo habéis leído (espero que sí) habla de dos maneras para obtener la posición del ratón y trabajar a partir de ahí, mediante Scaleform y mediante UDK script. Voy a hablar un poco de mi experiencia sobre este tema. No puedo decir que haya trabajado Scaleform de forma exhaustiva en el juego, pero sí que ha tenido algunos problemas y eso que no ha sido utilizado mucho, por eso, me decanto por UDK script.

Además me parece mucho más directo que Scaleform, por eso será el que voy a explicar, si queréis atreveros con el otro, allá vosotros. No digo que no sea igual de bueno, pero siempre he leído que hay que tener algo de cuidado con la cantidad de elementos de Scaleform que se introducen en la escena, porque podrían afectar al rendimiento.

Una aclaración más antes de empezar el trabajo duro, en el artículo divide el código en tres clases distintas, cuando todo se podría realizar en dos, personalmente, me gusta ser ordenado en mi código, y si puedo separarlo en distintas clases, lo prefiero así, por ello voy a mantener la misma organización que utiliza el artículo mencionado anteriormente.

Primero tenemos que crear las clases de HUD y de PlayerController especificadas en el GameInfo, haciendo una consulta rápida a dicho artículo, y siempre manteniendo la coherencia de añadir el DC al comienzo de todos los archivos (cosa que realmente es bastante chorra si uno se para a pensar, al final tendrás una veintena de archivos que todos comienzan por DC y no servirá para nada, incluso un punto en la jerga informática habla sobre esto) son DCleanerHUD.uc y DCleanerPlayerController.uc

Ya se ha comentado antes, pero para hacer un repaso rápido, en DCleanerHUD escribiremos todo el código relacionado con el HUD (era obvio, pero no me cuesta nada recordarlo) y en DCleanerPlayerController se especifica qué acciones realizan los controles y cómo se maneja el Pawn. Aprovecho para decir que desde el HUD podemos acceder al PlayerController y viceversa con un simple cast y  que ambas clases son únicas en una instancia del juego, ya sea monojugador o multijugador (en este caso, se refiere al jugador local).

Comenzamos con el DCleanerPC.uc

class DCleanerPC extends PlayerController;

defaultproperties
{   
  InputClass=class'DCleanerMouseInterfacePI'
}

Bien, no es mucho, nuestro PlayerController no tiene que hacer nada por el momento, lo único, especificar que la clase que gestionará la entrada del juego es DCleanerMouseInterfacePI, que es la clase que trabajaremos a continuación.

class DCleanerMouseInterfacePI extends PlayerInput;

var IntPoint MousePosition; 

event PlayerInput(float DeltaTime)
{

  if (myHUD != None) 
  {
    MousePosition.X = Clamp(MousePosition.X + aMouseX, 0, myHUD.SizeX); 
    MousePosition.Y = Clamp(MousePosition.Y - aMouseY, 0, myHUD.SizeY); 
  }

  Super.PlayerInput(DeltaTime);
}

defaultproperties
{
}

En la primera línea, creamos una variable de clase de tipo IntPoint llamada MousePosition (un punto que almacena valores enteros, no os voy a descubrir nada nuevo) que es donde vamos a almacenar la posición en la que se encuentra nuestro cursor en todo momento, y que será accesible desde las clases, esto es importante porque la clase del HUD tiene que conocer el valor de la posición del ratón para calcular la posición 3D (comentado en futuras entregas).

Después definimos nuestro propio PlayerInput que es un método de clase para que realice lo que queremos, en nuestro caso, lo que queremos es actualizar la variable que contiene la posición del ratón. La primera acción del método es asegurarse que existe myHUD, que es el nombre de la variable de la clase PlayerInput que apunta a nuestro HUD.

Es una buena práctica asegurarse de que las variables que no están contenidas en la clase existan, porque si no existen, el juego no va a detenerse e informarte que has accedido a una variable que no está disponible, lo general es un aviso en el log

El problema (para mi no, porque tengo dos monitores) es que no puedas tener el log y la ventana de juego visible a la vez, que solo utilices el UDK editor que no tiene ventana de log o directamente ignores el log (mala práctica, esto nunca, o el día que te de por utilizarlo, veas que salen mil warnings por segundo. Es gracioso porque con una actualización del repositorio me salían tantos warnings por segundo que ralentizaba mi PC).

Aquí hay algo de miga, pero tampoco mucha, primero explicar el método Clamp, que su labor es asegurarse que la primera variable (la coordenada X o la Y) se encuentre entre los otros valores de entrada del método (esto es entre 0 y el tamaño del HUD), esto es importante porque queremos que la posición del ratón no se salga de la ventana, y en caso de que se exceda, lo ajusta al valor mínimo o máximo.

Un detalle sobre la implementación del artículo y que yo también emplée es que utiliza el incremento de la posición del ratón, por eso se observa que el primer parámetro del método es la suma de la posición actual del ratón y aMouseX o aMouseY , que es el incremento en cada uno de los ejes.

Y no hay mucho más que contar, con eso disponemos de la posición del ratón en nuestra ventana de UDK, ahora tenemos que informar al HUD que queremos dibujar un material en la pantalla en la posición que está el ratón para que el usuario pueda conocer su posición, y también escribiremos un pequeño mensaje en la parte superior izquierda con la posición. No es extremadamente útil en este momento, pero es una buena manera de disponer de información adicional durante la ejecución.

Nota: Si os habéis dado cuenta, la posición del ratón se calcula por incrementos, y una variable no inicializada, tiene sus valores a 0, por lo que al iniciar nuestro juego, el ratón siempre estará en la posición (0,0). Se puede inicializar y poner cualquier valor, como por ejemplo, la mitad de la ventana (myHUD.SizeX/2,myHUD.SizeY/2)

Ahora es cuando comienza lo más interesante, DCleanerHUD.uc

class DCleanerHUD extends HUD;

var const Texture2D CursorTexture; 

event PostRender()
{
  local DCleanerMouseInterfacePI MouseInterfacePlayerInput;

  if (PlayerOwner != None && CursorTexture != None) 
  {
    MouseInterfacePlayerInput = DCleanerMouseInterfacePI(PlayerOwner.PlayerInput); 

    if (MouseInterfacePlayerInput != None)
    {
      Canvas.SetPos(MouseInterfacePlayerInput.MousePosition.X, MouseInterfacePlayerInput.MousePosition.Y); 
      Canvas.DrawTile(CursorTexture, 64, 64, 0.f, 0.f, CursorTexture.SizeX, CursorTexture.SizeY,, true);
      Canvas.SetPos(50, 50);
      Canvas.DrawText("Posición Ratón ("$MouseInterfacePlayerInput.MousePosition.X$","$MouseInterfacePlayerInput.MousePosition.Y$")");
    }
  }

  Super.PostRender();
}

defaultproperties
{
  CursorTexture=Texture2D'DCleanerAssets.flecha'
}

Bien, empezamos por las variables de clase, que se encargan de almacenar la textura que representará el cursor, en nuestro caso, utilizamos una bonita flecha que amablemente descargamos de opengameart.org, al que le borramos el fondo para que fuera transparente y estuviera colocado en la esquina superior izquierda de la imagen, es importante porque cuando decimos que queremos dibujar una textura en pantalla en una coordenada, la esquina superior izquierda quedará en dichas coordenadas.

Nuestro cursor

Ahora viene lo bueno, el evento PostRender() que se ejecuta cuando promete, aquí es donde escribimos nuestro código relacionado con el HUD y que será ejecutado después del proceso de renderizado. Primero nos declaramos la variable que nos servirá para acceder al PlayerInput y comprobamos que tanto el PlayerController como la textura estén disponibles, si no hay problemas, seguimos.

El HUD tiene una variable llamada PlayerOwner que es de tipo PlayerController, por lo que podemos acceder directamente y pedirle el PlayerInput, pero por cosas de la programación orientada a objetos (en la que no voy a entrar, asumo que si estáis leyendo esto, sabéis cómo funciona), es, exactamente, un PlayerInput y no un DCleanerMouseInterfacePI que es lo que queremos, así que tenemos que hacer un cast y guardamos el resultado del cast. Ahora podemos acceder a las variables y métodos de nuestra clase propia.

Nota: La manera de comprobar si el cast es correcto es comprobar si la variable es distinta de None, en caso de que lo sea, el cast se ha realizado correctamente, y en caso contrario, es que el cast no ha podido realizarse.

Una vez sabemos que se ha podido realizar, colocamos el pincel del Canvas en la posición del ratón, que obtenemos mediante nuestra variable, y ahí, dibujamos el cursor con el método DrawTile al que le hemos especificado el tamaño del cursor a 64×64.

Para terminar, también escribiremos en pantalla la posición del ratón, primero moveremos el pincel a la posición donde vamos a escribir el mensaje, a efectos de posición, la esquina superior izquierda es el origen de coordenadas, el eje X es positivo hacia la derecha y el eje Y es positivo hacia abajo, sabiendo eso, colocamos nuestro pincel y escribimos el mensaje en pantalla.

Nota: Si lo probáis, os daréis cuenta de que al mover el ratón, la cámara también se mueve, eso es porque al heredar de PlayerController, éste tiene una funcionalidad por defecto que es el movimiento de la cámara con el ratón y del Pawn con WASD. Por eso se mueve tanto el cursor como la cámara, más adelante nos encargaremos de anular este comportamiento para que al mover el cursor, no se mueva la cámara.

Ha sido un poco largo, pero ya se ha terminado esta primera parte, en la siguiente entrega calcularemos la posición 3D del cursor, modificaremos la cámara a una tercera persona y haremos que no se mueva al mover el ratón.

UDK: Game Info

El Game Info es una clase cuyo objetivo es definir el juego: las reglas del mismo, la puntuación, los actores que pueden existir y quién puede entrar en el juego. O al menos, eso es lo que dice la wiki sobre el fichero. En mi experiencia creando un juego monojugador, el trabajo que he tenido que realizar con el Game Info ha sido mínimo, ya que la mayoría de las opciones son para servidores multijugador.

Nota: Cuando UDK crea un nivel a partir de un mapa y del Game Info que define las características del juego, crea un servidor al que nosotros nos conectamos. Es importante conocer este detalle porque aunque estemos jugando solo, la creación del servidor se realiza.

Habitualmente especificaremos las clases que se encargarán de controlar nuestro juego y trabajaremos con alguno de los métodos que nos permite, como ejemplo, la función ScoreKill es utilizada en Dungeon Cleaner para el sistema de experiencia.

Para poder crear nuestro Game Info, debemos heredar de GameInfo o de cualquiera de las clases que heredan de ella.

class DCleanerGI extends GameInfo;

El siguiente paso es especificar, como mínimo, el peón del jugador, la clase que lo controla y el HUD.

 defaultproperties
{
	PlayerControllerClass=class'DCleaner.DCleanerPC'
	DefaultPawnClass=class'DCleaner.DCleanerPawn'
	HUDType=class'DCleaner.DCleanerHUD'
}

Con esto en el Game Info, debería de ser suficiente para poder comenzar a trabajar. Ahora si creamos un mapa de UDK y le especificamos que su tipo de juego va a ser este Game Info, ya sabrá cómo jugar el mapa.

Más adelante, cuando se hable del sistema de experiencia que se ha implementado en el juego, volveremos a este fichero para ampliarlo.

Scaleform: Integración en UDK

Después de los dos tutoriales anteriores, ya tenemos una película de Flash preparada para que pueda ser importada en UDK. Para ello, debemos de hacer una clase que se encargue de la película en sí y de implementar los métodos que llamamos desde Actionscript.

El primer paso es importar a nuestro paquete de assets la película siguiendo los pasos a continuación:

  1. Lanzamos el Unreal Editor y nos vamos al Content Browser.
  2. Seleccionamos nuestro paquete de assets (en mi caso, DCleanerAssets) y pulsamos el botón Import.
  3. Seleccionamos la película que se encuentra en UDK\UDKGame\Flash\DCleanerAssets (Recordar que no podemos especificar a qué paquete de assets se añadirá la película. Lo hará en uno con el nombre de la carpeta, por eso mi carpeta se llama igual que el paquete de assets).
Ahora en nuestro paquete de assets tendremos un SwfMovie que es la película que hemos importado y una Texture2D que es el puntero del ratón.
Nota: Lo que importamos no es el fichero .fla, lo que importamos en el fichero .fla una vez publicado, que su extensión es .swf

A continuación, nos vamos a nuestra carpeta donde se encuentran todos los ficheros de UnrealScript, en mi caso y siguiendo las convenciones habituales, se encuentra en Development\Src\DCleaner\Classes siendo DCleaner vuestra carpeta para código propio. Como recordatorio, esta carpeta se definió en el primer artículo de UDK: Estructura de carpetas, y en dicha carpeta creamos un nuevo fichero que yo llamaré DCleanerPauseMenu.uc

Recomendación: Ya dije en el artículo de UDK: Herramientas que lo mejor para trabajar con UnrealScript es tener nFringe instalado, y para crear un fichero desde nFringe, nos vamos a la carpeta donde debería de estar el fichero, y con botón derecho -> Add -> New Item y seleccionando un fichero de UnrealScript, nFringe ya se encarga de crearlo y además el autocompletado de código funcionará mejor que si importamos el fichero.

Ya tenemos el fichero, y debemos especificar el nombre de la clase y de qué clase vamos a heredar. Yo llamaré a la clase igual que al fichero, es decir DCleanerPauseMenu y toda película de Flash debe de heredar de GFxMoviePlayer, así que sabiendo esto, podemos poner la primera línea:

class DCleanerPauseMenu extends GFxMoviePlayer;

A continuación, rellenamos el bloque de defaultProperties con el nombre de la película que vamos a reproducir, y activamos el boolean bPauseGameWhileActive que se encargará de pausar el juego mientras mostramos la película, que es exactamente lo que queremos para nuestro menú de pausa. También necesitamos capturar la entrada del juego, así que también activamos el boolean bCaptureInput. El bloque quedará así:

defaultProperties
{
	MovieInfo = SwfMovie'DCleanerAssets.PauseMenu'
	bCaptureInput=true
	bPauseGameWhileActive=true
}

Nota: Para obtener el nombre completo de la película, nos vamos al Content Browser y con botón derecho sobre el item del que queremos obtener el nombre completo, seleccionamos la opción de Copy fullname to clipboard.

Ahora tenemos que implementar el método Init(), que lo llamaremos para inicializar la película.

function Init(optional LocalPlayer LocPlay)
{
	super.Init(LocPlay); // Llamamos al método de la clase padre
	Start(); // Inicializamos la película

	/* Los siguientes métodos especifican el escalado de la película, al igual 
	que su alineación en la pantalla, los posibles valores de cada uno están 
	definidos en GFxMoviePlayer.uc pero como no me cuesta nada, os los copio 
	aquí para que conozcáis las posibilidades de cada método

	enum GFxScaleMode
	{
		SM_NoScale,
		SM_ShowAll,
		SM_ExactFit,
		SM_NoBorder
	};

	enum GFxAlign
	{
		Align_Center,
		Align_TopCenter,
		Align_BottomCenter,
		Align_CenterLeft,
		Align_CenterRight,
		Align_TopLeft,
		Align_TopRight,
		Align_BottomLeft,
		Align_BottomRight
	}; */

	SetViewScaleMode(SM_NoScale);
	SetAlignment(Align_Center);
	Advance(0.f); // Especificamos que vaya al frame 0 de la película
}

Solo nos falta implementar los métodos que son llamados desde ActionScript, los nombres que le dimos a estos métodos eran ExitMenu() y ExitGame(), así que los declaramos y los rellenamos.

function ExitMenu()
{
	ConsoleCommand("TogglePauseMenu");
}

function ExitGame()
{
	ConsoleCommand("exit");
}

Nota: El método ConsoleCommand lo que invoca es el método que se le pasa por parámetro como si lo hubieramos introducido en la consola.

El comando de consola exit ya existe, por lo que no tenemos que hacer nada con él, pero TogglePauseMenu no existe, y debemos de declararlo de forma que pueda ser invocado por consola para que todo funcione correctamente. El lugar más adecuado para este método es la clase personalizada para mostrar el HUD que ya debería de existir. Yo abro mi fichero DCleanerHUD.uc y al final, declaramos el método TogglePauseMenu que abrirá la película si está abierta, y en el caso de estar abierta, la cerrará.

/* Al comienzo del fichero, declaramos una variable de tipo DCleanerPauseMenu */
var DCleanerPauseMenu PauseMenu;

exec function TogglePauseMenu()
{
	/* Si el fichero no es igual a none y el flag de que la película está 
	abierta es true, lo que hacemos es cerrar la película. */
	if(PauseMenu != None && PauseMenu.bMovieIsOpen)
	{
		/* Cerramos la película. El parámetro de la función es si deseamos 
		eliminar de la memoria la película. En nuestro caso, como es un
		menú de pausa que será invocado en más de una ocasión, mejor no 
		eliminarlo porque la siguiente vez que fuera invocado, tendría que
		copiarlo a memoria de nuevo. */
		PauseMenu.Close(false);
	}
	/* Si por otro lado, la película no es igual a None o no se encuentra 
	abierta */
	else
	{
		/* Comprobamos si la película es igual a none, para inicializar la 
		variable. Lo realizamos así, porque al no descargar la película, 
		podría ocurrir que la película estuviera cerrada, pero que PauseMenu 
		no fuera none */
		if(PauseMenu == None)
		{
			/* Sabiendo que la película no está inicializada, la 
			inicializamos haciendo un new en nuestra variable */
			PauseMenu = new class'DCleanerPauseMenu';
		}
		/* Llegados a este punto, estando ya la película creada, o recién 
		creada, debemos llamar a este método para que muestre la película. 
		Recuerdo que este método lo hemos escrito en nuestra clase*/
		PauseMenu.Init();
	}
}

Nota: la palabra reservada exec se utiliza para especificar que un método puede ser invocado por consola. Lo que nos permite invocarlo con el método ConsoleCommand.

Ya hemos terminado con la integración de un fichero de Flash en UDK. Como habéis podido ver, es realmente fácil y una vez realizado uno, todos son iguales. Un pequeño resumen de los pasos es:

  1. Realizamos la película con los widgets específicos de Scaleform.
  2. Importamos la película a nuestro paquete de assets.
  3. Declaramos el fichero que hereda de GFxMoviePlayer.
  4. En el bloque de defaultProperties, especificamos la película y todos los parámetros por defecto, como bCaptureInput o bPauseGameWhileActive.
  5. Reescribimos el método Init() y modificamos la alineación o escalado al gusto.
  6. Y Voilá. Tenemos que buscar un sitio en nuestro código donde llamaremos a la película para mostrarla, y acordarnos de disponer de una manera de cerrar la película, o no podremos volver al juego.

UDK: Estructura de carpetas

El siguiente paso, una vez tenemos las herramientas instaladas y correctamente configuradas es entender cómo gestiona UDK un proyecto y dónde debemos colocar las cosas para que sean visibles desde UDK. También es importante conocer ciertos archivos que se esconden en la estructura de carpetas de la instalación, así que la comentaré, haciendo hincapié en los ficheros que utilizaremos, y las carpetas que debemos crear para nuestro proyecto.

En la instalación limpia, disponemos de las siguientes carpetas:

  • Binaries
  • Development
  • Engine
  • UDKGame

Ahora comentaré una a una las carpetas, y la importancia detrás de cada una de ellas.

Binaries

UnrealFrontend.exe será nuestro centro de operaciones, desde él tenemos acceso a todo lo que nos permite UDK.

Captura de pantalla del UnrealFrontend, con el proyecto que estoy realizando, DCleaner

No voy a entrar en muchos detalles, porque el UnrealFrontend merece un artículo para él solo. Sí que os digo que en el menú de la izquierda salen los distintos proyectos que están disponibles para trabajar, pero ya comenté que no es recomendable trabajar con más de un proyecto por instalación de UDK, por eso sólo tengo uno.

UnrealFrontend.Profiles

Es una carpeta donde, en formato xml, se almacenan los datos de los proyectos, que son leídos por el UnrealFrontend. Aquí no es necesario hacer nada, porque los cambios realizados se guardan automáticamente, pero es importante conocer la existencia de la carpeta, por si utilizáis algún sistema de control de versiones para añadir el fichero de información del proyecto.

Recomendación: Para conseguir un proyecto propio, lo mejor es clonar uno ya existente, renombrarlo y eliminar todos los mapas que están añadidos. Una vez hecho esto, ya podemos comenzar a utilizarlo.

Development

Dentro de Development, tenemos la carpeta de Src, y dentro de ella, debemos de crear una carpeta para nuestro trabajo. Si nuestro proyecto se llama DCleaner, lo recomendable sería mantener ese nombre para las carpetas.

  1. Development
    1. Flash
    2. Src
      1. DCleaner
        1. Classes

Como se ve en el esquema de arriba, la carpeta DCleaner tiene que ser creada en Src, y dentro de la carpeta que hemos creado, tenemos que crear una llamada Classes. En esta carpeta, se guardarán todos nuestros ficheros en UDKScript, con extensión .uc, pero no es suficiente para que UDK lo encuentre. Tenemos que especificarlo en un fichero de configuración. En el siguiente apartado, se comentará la ubicación del fichero.

Engine

Nada importante para nosotros.

UDKGame

En este fichero es donde estará casi todo nuestro juego, a excepción de los ficheros de UDKScript. Vamos poco a poco:

Config

Lo que voy a decir suena raro, pero la lógica utilizada aquí a veces lo es. Si observáis la carpeta, veréis que la mayoría de los ficheros están duplicados con un nombre ligeramente distinto, por ejemplo, DefaultInput.ini y UDKInput.ini. El sentido común podría dictarnos que el fichero DefaultInput.ini no debería de ser tocado, ya que son los valores por defecto, y tendremos que trabajar con el otro. Pues no.

El fichero con el que debemos trabajar es con el Default, porque los otros ficheros, cada ejecución de Unreal, son borrados y creados de cero, para asegurar que los valores se mantienen. Una vez comentada esta curiosidad, hablaré de los ficheros que debemos cambiar, y lo importante dentro de cada uno de ellos:

DefaultEngine.ini Aquí es donde especificamos la carpeta creada arriba, donde están nuestros ficheros de UDKScript, para que sea tenida en cuenta por el motor. Tenemos que buscar el bloque [UnrealEd.EditorEngine] y al finalizar el mismo, añadir la línea +ModEditPackages=DCleaner (siendo DCleaner el nombre de vuestra carpeta). Debería de quedar así:

[UnrealEd.EditorEngine]
+EditPackages=UTGame
+EditPackages=UTGameContent
+ModEditPackages=DCleaner

Hay más bloques que podrían modificarse, pero tienen menos importancia, como el bloque de [FullScreenMovie] donde se especifican las películas de carga. El necesario es el de arriba para que nuestros ficheros de UDKScript sean compilados.

DefaultGame.ini Aquí hay que especificar algunos ficheros de UDKScript, pero que aún no hemos creado. Os diré qué serán esos ficheros, y la convención de nombres utilizada. Primero, os copio el bloque concreto que hay que modificar, y las primeras seis líneas:

[Engine.GameInfo]
DefaultGame=DCleaner.DCleanerGI
DefaultServerGame=DCleaner.DCleanerGI
PlayerControllerClassName=DCleaner.DCleanerPC
GameDifficulty=+1.0
MaxPlayers=32
DefaultGameType=”DCleaner.DCleanerGI”;

De todo el bloque, tenemos que modificar las primeras tres líneas, y la sexta. El primer DCleaner, que antecede al punto, es la carpeta de nuestros ficheros, que se llama DCleaner, y después es el nombre del fichero de UDKScript.

El fichero DCleanerGI es el Dungeon Cleaner Game Info, en él se especifican las clases que definen aspectos del juego, como la clase que controlará al jugador, la clase que definirá al jugador o la clase que especificará el HUD utilizado.

El fichero DCleanerPC es el Dungeon Cleaner Player Controller, en este fichero está programado el comportamiento del personaje.

Aviso: Estos ficheros los crearemos más adelante, pero lo importante ahora es saber dónde hay que colocarlos, y qué hace cada uno.

DefaultInput.ini Las asignaciones de teclas a las acciones se realizan aquí. Decir que UDK utiliza un sistema que hace de capa intermedia entre el código y la pulsación de una tecla, que es la GBA o Game Bindable Action. El funcionamiento es que a un método que realiza algo, se le asigna una GBA, y luego, a un botón concreto, se le asigna esta GBA en vez del método. La utilidad es que el reasignar teclas dentro del juego, y los botones de las distintas plataformas es más cómodo.

Content

En esta carpeta va todo nuestro contenido del juego, pero para tenerlo todo organizado, hay que crear una carpeta dentro de Content con el nombre de siempre, DCleaner.

  1. UDKGame
    1. Content
      1. DCleaner

Dentro de esta carpeta, guardaremos nuestros ficheros de mapa que crearemos con el UnrealEditor, y los paquetes de Assets que son unos ficheros que encapsulan todos nuestros contenidos, como mallas estáticas, materiales, animaciones, etc.

Flash

La última carpeta, es la de Flash. En ella se guardan las películas creadas en flash para ser introducidas en el juego. El motor implementa ScaleForms, que es un middleware que nos permite utilizar una película en flash dentro del juego, ejecutar código de ActionScript y comunicarnos en los dos sentidos con dicha película, es decir, desde UDKScript con ActionScript, y viceversa.

Para que los ficheros se añadan correctamente, deben de estar dentro de una carpeta, dentro de Flash. Por alguna razón, en la versión en la que trabajo, no puedo especificar el paquete al que quiero importarlo, lo importa directamente en un paquete con el nombre de la carpeta, por ello, crearemos una carpeta con el nombre del paquete de Assets con el que trabajaremos, para que al importar un fichero, lo añada donde queremos. Sabiendo que nuestro paquete de assets se llamará DCleanerAssets, crearemos la siguiente estructura de carpetas:

  1. UDKGame
    1. Flash
      1. DCleanerAssets

Aviso: Los ficheros de Flash deben de tener ciertas características para que funcionen correctamente, al igual que una versión concreta de ActionScript. Estos detalles se comentarán en el tutorial de integración de elementos de Flash con UDK Script.

UDK: Herramientas

En un principio, comenté que quería hacer un blog de desarrollo, y durante las semanas que lo he ido pensando, la cosa ha cambiado un poco. Más que un blog de desarrollo, en el sentido estricto de la palabra, me parece más interesante compartir lo que estoy aprendiendo y realizando en forma de tutoriales, e ir hablando de lo necesario para hacer un juego usando UDK.

Así que la manera de unir tutoriales y el blog de desarrollo, es informar, paso por paso, de lo necesario para hacer el juego que estamos desarrollando, al menos, desde mi campo, que es la programación con UDK script. Para poder comenzar con el trabajo, necesitaremos una serie de herramientas que nos faciliten el trabajo, porque UDK script no es precisamente pequeño ni fácil, y probablemente sea un lenguaje nuevo para la mayoría, así que cualquier ayuda es poca.

Comenzamos nombrando lo que nos hace falta:

  • UDK
  • UnCodex
  • nFringe

UDK:

Lo primero es tener una versión de UDK para poder realizar un juego. UDK es gratuito para nuestros propósitos de aprendizaje (otro día hablaré de los distintos modos de licencia) y se puede descargar desde su página web http://www.udk.com/download

Voy a hacer una recomendación sobre UDK, y es que al ser actualizado con bastante frecuencia, lo mejor es que os descarguéis una versión y trabajéis con ella, porque puede ocurrir que si estáis a medio proyecto y cambiáis a una nueva versión, aparezcan problemas de compatibilidad.

Es evidente que si sacan una nueva versión, es porque han añadido o mejorado alguna cosa, así que de vez en cuando visitad la página de UDK para ver qué han hecho en la última versión, por si hay algo que os interesa.

Yo, por comentaros mi caso, comencé a trabajar con la versión de septiembre de 2011, y la malla de navegación tiene algún fallo al generar las rutas, pero que haciendo una prueba rápida con la versión de Febrero, el problema se solucionaba en parte, porque no eran tan exagerado esos rodeos que os muestro en una captura del juego con una cámara más elevada.

La ruta calculada, mostrando los puntos de la ruta con esferas, unidas por líneas


UnCodeX:

Este programa puede desempeñar varias tareas distintas, pero la más importante es la búsqueda rápida de clases y palabras dentro de las clases programadas en UDK script dentro de UDK. Las clases existentes son muy grandes y, a menudo, la herencia se convierte en un caos cuando estamos mirando alguna clase.

Para solucionar la papeleta está UnCodeX que dispone de un árbol para ver visualmente la herencia (cuadro 1), una zona para ver las clases estructuradas en paquetes (cuadro 2) y su búsqueda es realmente rápida en comparación con la siguiente herramienta, por eso siempre es interesante tener ambos programas abiertos para complementar unos con otros.

Os remito a la página de Epic que habla de UnCodeX, con el link de descarga, y una pequeña explicación de uso que me fue realmente útil. http://udn.epicgames.com/Three/UnCodeX.html

Captura de la interfaz del UnCodeX, cedida amablemente por el enlace anterior


nFringe:

No es un programa en sí, pero es uno de los mejores plugins para Visual Studio que uno puede encontrar para trabajar con UDK script. No es gratuito, pero para proyectos no comerciales sí lo es, si activamos la copia enviando unos cuantos datos, como el nombre del proyecto, nombre del equipo de desarrollo y una dirección de correo.

Hay otras alternativas para colorear y dar ayuda durante el trabajo de escritura de código, pero después de probar otras, he aceptado lo inevitable y utilizado nFringe con Visual Studio 2008 Shell (que es también es gratuito).

Entre sus bondades, nos permite compilar el UDK script para encontrar errores desde el entorno de Visual Studio, sin tener que irnos al Front End de UDK, el autocompletado de código (solo para ficheros de UDK script creados desde Visual Studio con nFringe, ojo), y navegación de las clases de UDK, aunque a una velocidad mucho menor que UnCodeX, por eso mi recomendación de tener ambos.

La página con las instrucciones de instalación y links de descarga están disponibles en su Wiki http://wiki.pixelminegames.com/index.php?title=Tools:nFringe