sábado, 14 de diciembre de 2013

9) Powered by Kismet!

¡Muy buenas!
A continuación vamos a ver las bases para poder crear vuestras propias cajitas de Kismet. Para el que no sepa qué es Kismet, es un sistema de nodos que utiliza UDK para realizar "fragmentos" de código que actúen sobre actores en el mapa. Es un envoltorio de código para que se puedan crear ciertos comportamientos por medio de un entorno visual. Se utiliza fundamentalmente para "allanar" terreno a los diseñadores y poder trabajar de manera más independiente sin depender tanto del programador. 
En Kismet, existen 3 tipos de nodos (cajitas):
  1. Actions
  2. Events
  3. Conditionals
Vamos a centrarnos en las primeras ya que la estructura general es la misma en todas.

Action

La estructura básica de las acciones es la siguiente:
// extend UIAction if this action should be UI Kismet Action instead of a Level Kismet Action
class NULLSeqAction_Example extends SequenceAction;
var Object NameOfTheVariableInTheCodeSeeAtTheTopOfThisClass;
//Esta clase recibe este evento cuando recibe una señal positiva de otro nodo en //algún input
event Activated()
{
if(InputLinks[0].bHasImpulse)
{ //Si recibimos un impulso positivo de el primer elemento dentro de nuestro arrat de entradas, ejecutamos este bloque de código.
OutputLinks[0].bHasImpulse = true;
}
else
{ //If we don’t have a positive pulse, then our output is false
OutputLinks[0].bHasImpulse = false;
}
}
defaultproperties
{
ObjName=”NULLExample Action” //Nombre del nodo tal como se ve en el editor //de Kismet
ObjCategory=”NULLGame Actions” //Nombre de la categoría a la que pertenece //para poder ordenar mejor nuestros nodos de Kismet
InputLinks(0)=(LinkDesc=”Name of the input (connection) from the left side of the node”) //Démonos cuenta de que cuando hablemos de Eventos no necesitamos //InputLinks
//They are just input to select different behaviour in the node. InputLinks(n), //n=0,1,2,3,4,5…
VariableLinks.Empty //Clean up inherited variables
//Variables are connected to the node from the bottom
VariableLinks(0)=(
ExpectedType=class’SeqVar_Object’, //This is the most generic class, but you can see in Engine/Classes/SeqVar_* other like SeqVar_Int
LinkDesc=”Name of this variable shown in editor”,
PropertyName=NameOfTheVariableInTheCodeSeeAtTheTopOfThisClass,
bWriteable=true) //If you can modify this variable or it is just read only
OutputLinks(0)=(LinkDesc=”Output”) //Different output from the right side of the node
}
Tened cuidado si copiáis y pegáis el código (no deberíais hacerlo, recordad las sabias palabras de José Luis), pero como se que lo vais hacer, el que avisa no es traidor. Los comentarios entre los parámetros en InputLinks no funcionarán. Borradlos cuando los hayáis leído y entendido o para hacer pruebas.
Recordad, que podemos acceder al grafo de escena desde la clase WorldInfo y si bien, tenemos la función GetWorldInfo() que nos devuelve una referencia del mismo. Si trabajamos desde ciertas clases como Actor, podemos consultarlo directamente como WorldInfo. Recordad también que WorldInfo posee una referencia al GameInfo que estáis utilizando actualmente y que podéis realizar comparativas haciendo castings y comparando con none. 
También podéis acceder al PlayerController local con la función GetALocalPlayerController().
Como imagináis, podéis modificar variables declaradas en VariableLink que conecten con vuestros nodos de Kismet si tienen el flag Writeable (Si no utilizan una copia de sus valores). Podéis hacer un casting del tipo de objeto que queréis con simplemente declarar una variable en la clase con el nombre que designéis en PropertyName. Por supuesto, si no puede hacerlo, su valor será none.
(var MyClass myPropertyVariableName)
Como ejercicio, tratad de crear un nodo Action que reciba un pulso positivo en su input y éste haga que escriba mediante `log() algo. Conectamos esta Action con un Evento Trigger touch (añadimos al mapa, un trigger con botón derecho, AddActor->Add Trigger) y teniéndolo seleccionado en el mapa, en la ventana de Kismet, seleccionamos usar un Evento con este trigger. Colocamos en sus propiedades en la ventana de Kismet la variable MaxTriggerCount=0, para recibir todos los eventos de Touch y no sólo el número >0 que le indiquemos (es un problema bastante común, ya que por defecto esta variable está a 1 y sólo detectará Touch una vez si no lo modificamos).

Events

La estructura de la clase de eventos es similar a las acciones con algún cambio en sus defaultproperties:
// extend UIEvent if this event should be UI Kismet Event instead of a Level Kismet Event
class NULLSeqEvent_Example extends SequenceEvent;
defaultproperties
{
ObjName=”NULLSeqEvent_Example”
ObjCategory=”NULLGame Events”
VariableLinks.Empty //Clean up inherited variables
//Variables are connected to the node from the bottom
VariableLinks(0)=(
ExpectedType=class’SeqVar_Object’, //This is the most generic class, but you can see in Engine/Classes/SeqVar_* other like SeqVar_Int
LinkDesc=”Name of this variable shown in editor”,
PropertyName=NameOfTheVariableInTheCodeSeeAtTheTopOfThisClass,
bWriteable=true) //If you can modify this variable or it is just read only
OutputLinks(0)=(LinkDesc=”Output”) //Different output from the right side of the node
//Exclusive!
MaxTriggerCount=0 //Max number of times this event could be triggered
bPlayerOnly=false //Not only triggeable by PlayerController
}
Para poder lanzar estos eventos, necesitamos indicar qué clases van a utilizarlos:
En defaultProperties de las clases que queremos que los utilicen, añadimos:
SupportedEvents.Add(class’SeqEvent_*’) 
o si queremos controlar el orden de prioridad si utilizan varios eventos...
SupportedEvents(n)=class’SeqEvent_*’ donde n es la prioridad cuanto menor su número, mayor su prioridad (al estar en la estructura de datos SupportedEvents antes).
Hay que tener cuidado al heredar de otra clase si utiliza eventos, ya que podríamos "chafar" algunos de los eventos que utiliza el padre. Utilizad Add en estos casos para concatenar eventos utilizados o bien, reescribir con el nuevo orden todos los eventos que soporta vuestra clase.
Una vez hemos preparado una clase para que reciba los eventos, si es placeable, podremos en Kismet, al tener una instancia de dicha clase en la escena, aplicarle un evento de Kismet propio de ese tipo. Para activarlo, tenemos dos opciones diferentes:
Global, activa todos los eventos de un tipo TriggerGlobalEventClass(class’SeqEvent_*’,self)
Local, activa el evento * del objeto actual TriggerEventClas(class’SeqEvent_*’,self)
Si quieres activar más de un evento, consultad ActivateEventClass (link)
Este es un primer acercamiento a la programación de nodos de Kismet. Los Conditionals tienen un funcionamiento similar, aunque de base son menos utilizados.

viernes, 13 de diciembre de 2013

U0) Bases de programación en Unity

¡Muy buenas!

Ya estamos por aquí a puntito de comenzar con Unity. No entraré en detalles de cómo lo podéis instalar o del propio interfaz. Me centraré en algunas clases prácticas de cómo realizar un pequeño juego de plataformas con Unity 3D. Desde luego, va a ser un viaje sencillo y con clases bastante básicas. Muy posiblemente entre en algunos detalles de dilemas con los que os podéis encontrar a la hora de tomar decisiones de cómo realizar ciertas cosas con código. Tomadlo como lo que es, una de las opciones que tenéis disponibles y recordando, que procuraré ser lo más claro y sencillo posible (tampoco quiero profundizar en estructuras que puedan resultar complicadas si no es necesario).

Por supuesto, recordad una cosa: Hay mil formas de hacer las cosas. Lo importante es que os encontréis cómodos a la hora de trabajar con el código que estáis haciendo y que, vosotros mismos, os deis cuenta de aquellas estructuras o formas de realizar vuestro código que se adecuen a aquello que queréis crear. Y recordad una gran máxima: "Si parece que funciona, funciona". No os volváis locos con la perfección, ni intentéis crear el código perfecto. Simplemente porque creo que es como buscar el Santo Grial. Simplemente, cread aquello que necesitéis y si más adelante necesitáis más modularidad en el código o cosas más complejas, vosotros mismos os daréis cuenta de ello. Tampoco os obsesionéis con el rendimiento: Primero que las cosas funcionen y después le dais al coco de cómo se puede hacer más eficiente, SÓLO si os hace falta. No tiene sentido si vuestro juego va con la fluidez que deseáis poneros con quebraderos de cabeza cuando os podéis poner a trabajar en otras partes del mismo.

Organizando las cosas. Jerarquía de carpetas

Es muy importante tener las cosas en un sitio donde, más adelante, podáis encontrarlas fácilmente. Igual que en UDK colocábamos un prefijo en todos los scripts para saber que son nuestros, aquí tendremos que trabajar con muchas otras cosas que no son código, y conviene que os creéis una jerarquía de carpetas donde situar cada cosa, del mismo modo que crear una nomenclatura clara para la denominación de cada cosa (normalmente indicada en un documento técnico de diseño).
Mi recomendación:

En la jerarquía de carpetas (La pestaña Project en Unity), podéis crear bajo la carpeta Assets todas las carpetas que deseéis. Allí crearía algunas básicas...

- Scenes, para guardar las escenas de juego en su interior.
- Code, para colocar vuestros scripts de código y clases. Podéis subdividir tanto como queráis (Navigation, CameraControl, Abstract, Shaders...)
- Materials, para colocar materiales. Se recomienda jerarquizar subcarpetas en su interior también.
- Models, para colocar mallas importadas en FBX o en el formato que deseéis.
- Textures, para texturas.
- Prefabs, para colocar, cuando veamos lo que son más adelante, los prefabs que deseemos. 

GameObject, la base de Unity


Muy bien, pues como podéis ver, Unity funciona con escenas que debéis guardar en disco (como si fueran los mapas de UDK). Arriba, en la barra de opciones tenemos GameObject. Si pinchamos en él, nos aparecerá Create Empty o Create Other. Básicamente, la primera opción nos generará un GameObject desnudo. ¿Y qué es un GameObject? Pues es una entidad en Unity que tiene un Componente llamado Transform que es el encargado de darle una posición en el espacio y una orientación.
¿Interesante verdad? Se parece mucho a los ActorComponents de UDK. Pero no es igual. Como vemos, podemos pinchar en crear un GameObject y nos aparecerá un gizmo con las coordenadas del espacio en la pestaña de Scene y también en la pestaña de Hierarchy. Si lo pinchamos o seleccionamos tanto en la pestaña de Scene o de Hierarchy, en la pestaña inspector normalmente a la derecha, nos encontraremos con que aparece el componente transform el cual podemos toquetear al gusto.
Bien, visto esto, ya tenemos claro dónde podemos encontrar las cosas en Unity:
En la pestaña Hierarchy aparece el árbol de escena, con todos los objetos e hijos de estos (Que aparecerán tabulados bajo sus padres y que podremos desplegar pinchando en la flecha que tendrán los mismos a su izquierda).
En la propia escena, en la pestaña Scene, podemos seleccionar elementos. 
En la pestaña Inspector, veremos los componentes que componen cada GameObject y sus variables "visibles" para poder modificarlas.

Dicho esto, vemos la primera gran utilidad de Unity. Los Componentes no son más que clases de código que heredan de una clase especial llamada MonoBehaviour que les proporciona los eventos y llamadas a métodos necesarios por el motor. Así mismo, las variables globales de dichas clases, son mostradas por el Inspector y lo mejor: PUEDEN MODIFICARSE EN TIEMPO REAL mientras se ejecuta el juego.

¿Cómo se hace esto? Muy sencillo: Veis que arriba existen botones parecidos a los de un reproductor de música. Efectivamente, el botón con flecha es para comenzar a ejecutar todos los scripts MonobeHaviour de los GameObjects en la escena actual. En la pestaña Scene se mostrará exactamente lo que la cámara principal renderice en ese momento.

Para hacer una pequeña prueba de lo que os comento, basta con crear en la pestaña GameObject->Create Other->Cube. Lo colocamos en la pestaña de Scene en una posición en la que podamos verlo por la cámara. Si no tenemos cámara, es el momento de crear una en la misma pestaña GameObject->Create Other->Camera.
También necesitaremos una luz para poder iluminar nuestra escena: GameObject->Create Other-> Point Light.

Muy bien, ya podemos ejecutar nuestra escena y seleccionando nuestro Cube en Hierarchy, podremos modificar su posición numéricamente, yendo a la pestaña Inspector y modificando su Position dentro de su Componente Transform.

Componentes

¿Cómo añado un componente? Existen algunos ya "creados" en Unity para, por ejemplo, utilizar el motor de físicas con sus colisiones y rigidbody. Así mismo, para poder tener ciertas mallas de figuras geométricas simples.
En la barra superior, tenemos la pestaña Component con un listado de dichos componentes.

Es importante señalar la importancia de los componentes RigidBody, en el apartado Physics así como Box Collider, o Capsule Collider.

¿Cómo creamos nuestros scripts de comportamientos? Podemos hacerlos de muchas maneras. Desde la pestaña de Project, podemos con el botón derecho crear un script de programación (Create X Script). En este curso, utilizaremos C# como lenguaje para nuestros scripts, al tener riqueza de librerías externas para utilizar y al ser un lenguaje que podemos utilizar fuera de Unity al ser utilizado para programación en general. Javascript puede resultar más sencillo, pero para mi gusto está más relacionado con la programación web y es allí donde debe permanecer :)

Nuestro primer contacto con la API de Unity (Script reference) Clase MonoBehaviour

Una vez creéis un script (acordaos de colocarlo en un lugar ordenado dentro de vuestra jerarquía de carpetas para no sembrar el caos), podéis hacer doble click en él y acceder desde el IDE que instala por defecto Unity: Monodevelop. Como veréis es muy sencillo de utilizar y posee herramientas más que de sobra para comenzar con Unity. Se puede utilizar otros IDEs, y se deben especificar en la barra de arriba, Edit->Preferences->External Tools. Monodevelop está perfectamente configurado e integrado para trabajar con Unity de modo que a vuestra elección queda utilizar otro (aunque nosotros utilicemos Monodevelop para este curso, que además es gratuito :D ).

Bien, la misma jerarquía de assets que tenemos en el proyecto de Unity debería apareceros en Monodevelop, de modo que podéis buscar y crear scripts desde el propio IDE. Además, se puede debuggear "attachando" al hilo de ejecución de Unity para que lo inspeccione (la tecla que tiene una flecha grande, que al pulsarla os indicará con el hilo que deseáis trabajar).

Ahora si llega el momento de conocer dónde se encuentra la documentación de referencia de Scripts para Unity: http://docs.unity3d.com/Documentation/ScriptReference/

Lo primero que haremos será echarle un vistazo a la clase MonoBehaviour:

En el apartado Messages podemos ver todos los eventos a los que llama el motor y el momento en el que lo hace documentado. Los más básicos que nos interesan ahora mismo son:

- Awake, Como nos indica la documentación, se llama después de que se hayan cargado todos los objetos en escena, de modo que es un sitio seguro para buscar otros objetos en la escena y guardarnos una referencia de ellos. Además, sólo se llama una vez en todo el tiempo de vida del objeto. Se llama aunque el componente no esté activo por defecto en la escena (todos los componentes tienen un tick en el inspector para activarlos o desactivarlos). Ideal para utilizar como "Constructor" o método que configura referencias y valores de variables y atributos.
- Start, Llamado antes de que se realice la primera llamada al evento Update. Del mismo modo que Awake, se llama sólo una vez en el tiempo de vida del objeto. Pero no se llamará si el script no está activo en el momento de inicialización. Ideal para utilizar como "Constructor" o método que configura referencias y valores de variables y atributos.
- Update, Llamado cada "tick" del motor o frame. Mediante la clase estática Time.deltaTime, no deja de ser la clase principal para colocar en el "loop" de ejecución del programa. Aquí se realizará la mayor parte del comportamiento del script.
- LateUpdate, Como su nombre indica, se ejecuta después de las llamadas de Update. Es interesante su uso, por ejemplo, para recolocar cámaras que siguen ciertos objetos, ya que éstos se habrán movido con anterioridad en la llamada a Update.
-FixedUpdate, Recomendado su uso como un Update cuando tenemos que interactuar o utilizar temas de físicas.
-OnEnable, Cuando el componente se activa, se hace una llamada a este método.
-OnLevelWasLoaded, Como su nombre indica, al cargar una escena, se realiza una llamada a este método. Muy útil para recorrer todos los objetos de la escena y cargar valores que se deseasen haber guardado (SaveGameManager).

Además de todos los eventos que se llaman, también tenemos variables importantes para el comportamiento de los Componentes:

- enabled, booleano que indica si está activo o no dicho componente.
- renderer, referencia al primer componente de tipo Renderer que tenga el GameObject si tiene alguno, si no será null
- rigidbody, referencia al componente RigidBody del GameObject si tiene alguno, si no será null
- transform, referencia al componente Transform del GameObject (con el que le damos la posición, posición local y rotación del GameObject)
- name, nombre del GameObject

Mi primer script de navegación, ¿Qué componentes voy a necesitar?

Para realizar un primer ejemplo de navegación, necesitaremos:

- Components->Physics-> RigidBody
- Components->Physics-> Capsule Collider 

Si al añadir el RigidBody se os coloca ya un Collider, podéis con el botón derecho en el Inspector, eliminar dicho componente con Remove Component.

Al igual que hemos hecho antes, es interesante ver el funcionamiento de RigidBody y de los Collider. 

En Monobehaviour debemos consultar para las colisiones:

- OnCollision*
- OnTrigger*

Y en RigidBody es importante leer la documentación completa de cómo funciona.

Un poco de mates. Métodos y funciones interesantes antes de empezar.

Para empezar, tenemos que tener claros algunos métodos y funciones utilizadas en las clases Vector3 y Quaternion. Estas clases son las utilizadas para colocar puntos y vectores en el espacio y rotaciones respectivamente.
Para ello, debemos volver de nuevo al Script reference:

Funciones matemáticas con float.

No hay mucho que explicar. Todo son funciones matemáticas para trabajar con float. Entre las más utilizadas están Sqrt para hacer raíces cuadradas (recordad no abusar ya que es costoso aunque la implementaciónd e Unity espero sea eficiente y aproximada, también tened cuidado con eso), así como el clásico Lerp para realizar interpolación lineal entre dos valores. Encontraremos Lerp también en la clase Vector más adelante y en Quaternion.

Funciones matemáticas con Vectores de 3 dimensiones:

El método Normalize() hace que el Vector3 desde el que lo llamemos, se quede normalizado. Si es demasiado pequeño, se convertirá en un Vector3 vacío (0,0,0). También podemos devolver una copia del mismo utilizando su variable normalized.
Es interesante destacar la interpolación lineal entre vectores con Lerp http://docs.unity3d.com/Documentation/ScriptReference/Vector3.Lerp.html

Mediante RotateTowards, utilizaremos un Vector como una dirección que como un punto en el espacio. Claramente su uso nos vendrá bien para poder orientar nuestro personaje mediante vectores (luego veremos con Quaternion cómo conseguir un vector o cómo convertir un vector dirección en un Quaternion).

El código que aparece es exactamente de lo que os hablo a la hora de calcular y modificar rotaciones de nuestro personaje.

Funciones y métodos con Quaterniones

Para representar rotación, Unity utiliza cuaterniones. Las funciones Euler, Lerp y LookRotation (así como el método SetLookRotation) son las más comúnmente utilizadas. La primera sirve para rotar con grados euler, la segunda para realizar interpolación lineal entre dos Quaterniones (que también puede hacerse esféricamente mediante Slerp) y LookRotation nos va a devolver un Quaternion, a partir de un Vector dirección y un vector que apunte hacia arriba.

Al contrario que UDK, debemos darnos cuenta de que las coordenadas de mundo, están colocadas de diferente forma. El eje Y es el correspondiente a up o altura, mientras que el eje Z es el correspondiente a forward (o mirar hacia delante). 

Recoger el Input de teclado o periféricos.

Unity tiene una clase estática llamada Input (http://docs.unity3d.com/Documentation/ScriptReference/Input.html) donde almacena cada frame los estímulos recibidos por periféricos. Se pueden configurar "Buttons" y "Axis" (hacer que esos estímulos hagan referencia a un nombre específico). Buttons es, como su nombre indica, para pulsaciones (tiene eventos propios para comprobar si el botón se ha pulsado, si se ha dejado de pulsar, para consultar su valor... GetButtonDown, GetButtonUp), Axis para simular ejes como crucetas de mandos. Un Button o un Axis no deja de ser un valor entre dos float (normalmente -1 y 1 para los Axis y 0 y 1 para los button) que dependen del periférico en cuestión relacionado. Podemos acceder a teclas directamente con GetKey* conociendo los códigos de las letras por defecto que podemos utilizar (http://docs.unity3d.com/Documentation/ScriptReference/KeyCode.html). Si trabajamos en otras plataformas, debemos consultar la documentación para conocer qué KeyCode corresponde a dichos inputs.
Para configurar cómo se tratarán los valores de los inputs, debemos buscar en la barra Edit->Project Settings->Input. Ahí nos aparecerá en la ventana de Inspector los valores de los input por defecto que tenemos configurados. 
Podemos obtener el valor sin modificar por Unity de los Input utilizando funciones que contienen la palabra Raw como GetAxisRaw. Esto es interesante si queremos realizar nuestra propia modificación de los valores de Input (los mandos de consola por ejemplo tienen una DeadZone, que es un área en la que los valores que nos devuelven pueden no ser fiables ya que por el paso del tiempo, la propia fabricación del mando, pueden tener desviados su centro). Es muy interesante el siguiente artículo en caso de que queráis aventuraros a realizar vuestro propio "wrapper" clase estática con vuestro tratamiento de input (cosa harto interesante si vais a trabajar vuestro proyecto en diferentes plataformas). (http://gamasutra.com/blogs/JoshSutphin/20130415/190541/Doing_Thumbstick_Dead_Zones_Right.php)

Manos a la obra. Scriptando que es gerundio.






viernes, 29 de noviembre de 2013

8) Vamos con el HUD. Sistema de debug por consola (si no tenéis debugger o para consultar estados de clases de un sólo golpe)

¡Muy buenas!


Hoy nos centraremos en crear un HUD sencillito para el ejercicio que vamos arrastrando de recolección de monedas y ya de paso, crearemos un pequeño sistema de debug por consola para mostrar en el HUD valores que creamos importantes en ciertas clases. Por supuesto, es opcional y una herramienta más para aquellos que no tengan un debugger en condiciones configurado para trabajar con UDK (cosa que parece ser más frecuente de lo que debería).

En primer lugar analizamos la función más importante de la clase HUD (development/Src/Engine/Classes/HUD.uc):

HUD.uc

/**
 * The Main Draw loop for the hud.  Gets called before any messaging.  Should be subclassed
 */
function DrawHUD()
{
local vector ViewPoint;
local rotator ViewRotation;

if ( bShowOverlays && (PlayerOwner != None) )
{
Canvas.Font = GetFontSizeIndex(0);
PlayerOwner.GetPlayerViewPoint(ViewPoint, ViewRotation);
DrawActorOverlays(Viewpoint, ViewRotation);
}
PlayerOwner.DrawHud( Self );
}

Como véis, debemos hacer un override de dicho método para pintar lo que sea necesario en nuestro HUD. También vemos que podemos delegar parte del HUD a PlayerController (llamada PlayerOwner.DrawHud(self) al final). Éste a su vez, delegará en DrawHUD de Pawn y de PlayerInput (que veremos más adelante su uso). También es importante fijarnos que pinta el HUD dependiendo del lugar en el que está mirando la cámara y demás, y por tanto, si queréis experimentar haciendo HUDs raros, tenéis que meter mano por aquí también.

Aquí tengo un ejemplo de utilización de pintado de HUD de un antiguo ejercicio. En él, se pinta un reloj (una textura y un rectángulo de colores según su estado, para indicar el tiempo que queda (¿Os suena de algo verdad?).

ESATHUD.uc

class ESATHUD extends HUD;


var CanvasIcon clockIcon;
var Texture2D clockTex;
var int clock;
var int FullClock;
var int MediumClock;
var int LowClock;



function ResetTimer(int newValue, int current)
{

FullClock=newValue;
MediumClock=FullClock * 0.66f; //2/3 Full
LowClock=FullClock * 0.33f; //1/3 de Full
SetTimer( 1, true, 'ClockTimer');
clock = current;
}

function ClearMyTimers()
{
ClearTimer('ClockTimer');
}

function ClockTimer()
{
//`log("Clock: "@clock@" ; FULL: "@FullClock);
if(clock > 0)
{
clock--;
}
}

function DrawHUD()
{
super.DrawHUD();

//Canvas.DrawIcon(clockIcon, 0, 0);
//Colocas "El cursor" de dibujo dónde deseas dibujar en la pantalla

Canvas.SetPos(100,15);
Canvas.SetDrawColor(255, 255, 255); // White
Canvas.DrawTile(clockTex,32.0f,32.0f,0.0f,0.0f,256.0f,256.0f,,,);

Canvas.Font = class'Engine'.static.GetLargeFont();
Canvas.SetDrawColor(255, 255, 255); // White
Canvas.SetPos(70, 15);

Canvas.DrawText(clock);

if(clock < LowClock)
{
Canvas.SetDrawColor(255, 0, 0); // Red
}
else if (clock < MediumClock)
{
Canvas.SetDrawColor(255, 255, 0); // Yellow
}
else
{
Canvas.SetDrawColor(0, 255, 0); // Green
}

Canvas.SetPos(200, 15);
Canvas.DrawRect(20 * clock, 30); 

}


/**
* Puedes llamar a la llamada desde la consola ShowDebug y la etiqueta que quieras. En este caso
* Como puedes ver más abajo, existe la etiqueta GameInfo, que llama a una función de display debug
* que tiene tu GameInfo
* Para desactivar el mostrado de debug basta con colocar en consola el comando ShowDebug
*
*/
function ShowDebugInfo(out float out_YL, out float out_YPos)
{

if( ShouldDisplayDebug( 'GameInfo' ) )
{
ESATGameInfo(WorldInfo.Game).DisplayDebug(self, out_YL, out_YPos);
}
else
{
if( ShouldDisplayDebug( 'Pawn' ) )
{
(ESATGameInfo(WorldInfo.Game).GetALocalPlayerController().Pawn).DisplayDebug(self, out_YL, out_YPos);
}
else
{
super.ShowDebugInfo( out_YL, out_YPos );
}
}
}

DefaultProperties
{
clockTex=Texture2D'ESAT-Assets.Time'
clockIcon=(Texture=Texture2D'ESAT-Assets.Time')
FullClock=30

//IMPORTANT to post render actors!!
bShowOverlays=true
}


Como podéis ver en DrawHUD, lo primero que hacemos es llamar a la clase padre (también podemos copiar y pegarla antes o en su defecto modificar lo que necesitemos). El funcionamiento del HUD es bastante simple. Consta de un Canvas en el que debemos colocar un cursor en la posición que deseemos pintar o escribir (Canvas.SetPos(100,15);). Una vez allí, podemos pintar tiles con DrawTile, texto con DrawText, iconos con DrawIcon, rectángulos con DrawRect. Podemos echarle un vistazo a la clase base Canvas.uc para ver todas las posibilidades. 

La función ShowDebugInfo, se utiliza como sistema de debug en otras clases. Por ejemplo, la función en la clase GameInfo de este mismo proyecto es esta:

ESATGameInfo.uc

[...]

simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
{
   local string T;
   local Canvas Canvas;
 
   Canvas = HUD.Canvas;
 
   out_YPos += out_YL;
 
   Canvas.SetPos(4, out_YPos);
   Canvas.SetDrawColor(255,0,0);
 
   T = "GameInfo [" $ GetDebugName() $ "]";
   Canvas.DrawText(T, FALSE);
   out_YPos += out_YL;
   Canvas.SetPos(4, out_YPos);
 
   Canvas.SetDrawColor(255,255,255);
 
   Canvas.DrawText("Score:" @ Score @ "InternalDataValue:" @ InternalDataValue, FALSE);
   out_YPos += out_YL;
   Canvas.SetPos(4,out_YPos);
 
 
   Canvas.DrawColor.B = 255;
   Canvas.DrawText(" STATE:" @ GetStateName(), FALSE);
   out_YPos += out_YL;
   Canvas.SetPos(4,out_YPos);

Canvas.DrawColor.B = 255;
   Canvas.DrawText(" bWaitingToStartMatch:" @ bWaitingToStartMatch, FALSE);
   out_YPos += out_YL;
   Canvas.SetPos(4,out_YPos);

Canvas.DrawColor.B = 255;
   Canvas.DrawText(" bUsingArbitration:" @ bUsingArbitration, FALSE);
   out_YPos += out_YL;
   Canvas.SetPos(4,out_YPos);

}

[...]

Como véis, se pintan en el HUD los valores que deseemos ver más adelante al abrir la consola de comandos (tabulador al iniciar el juego) y escribiendo ShowDebug y el nombre de la clase que hayamos indicado en la clase HUD.

¡Ejercicio!

Ahora toca realizar un HUD para nuestro juego de monedas. Una vez realizado, ya estará la funcionalidad básica del proyecto que se os pedirá inicialmente (si, mis alumnos no se pueden quejar, que el aprobado lo tienen con leer y practicar un poquillo, que aunque parezca mentira, ¡a veces no hacéis ni eso pillastres!).

viernes, 22 de noviembre de 2013

5) Introducción a estados en UDK. PlayerMove y ProcessMove ¿Cómo modifico cómo se mueve mi Pawn? Adventures

¡Muy buenas!

En esta entrada analizaremos cómo poder modificar el movimiento de nuestro Pawn. Para ello, debemos explorar antes cómo funcionan básicamente los estados integrados en UDK.

Lo primero que hacemos es, irnos a la carpeta Development/Src/Engine/Classes/PlayerController.uc

Una vez allí buscamos "State PlayerWalking"

Allí nos encontraremos el estado que tiene un PlayerController por defecto cuando estamos en el juego. Como vemos, tiene una serie de métodos internos propios independientes de los que hay fuera de las llaves que lo engloban. El esqueleto básico sería algo así:


state PlayerWalking
{
//ignores SeePlayer, HearNoise, NotifyBump, TakeDamage, PhysicsVolumeChange, NextWeapon, PrevWeapon, SwitchToBestWeapon;

event NotifyPhysicsVolumeChange( PhysicsVolume NewVolume )
{
}

//Called when the state begins! Useful when we want to setup things before it starts

event BeginState(Name PreviousStateName)
{
//Acordaos de llamar al método padre para que funcione el estado bien!!!! (Ver PlayerController.uc)
}

//Called when the stateends! Useful when we want to setup things before another one starts

event EndState(Name NextStateName)
{
//Acordaos de llamar al método padre para que funcione el estado bien!!!! (Ver PlayerController.uc)
}

Begin:
}

La nomenclatura sería algo como: [auto] state NameOfTheState [extends NameOfTheParentState]

El flag "auto" se puede colocar antes de declarar el estado para indicar que es un estado inicial. Como ya podéis intuir, podremos pasar de un estado a otro. Ésta acción se realiza mediante GoToState('NombreDelEstado');

Cuando una clase se encuentra en un estado, puede llamar normalmente a los métodos que tiene dentro, pudiendo encapsular de este modo, diferentes comportamientos para los mismos métodos. 

BeginState y EndState, son llamados internamente cada vez que se entra en el estado y se sale de él respectivamente, con información del estado del que venimos y del que vamos. Nótese que los parámetros que reciben no son string, sino Name. La nomenclatura de name es con comilla simple ' ' en lugar de comillas dobles " ".

Por último, debajo observamos un Begin: Aquí comienza una especie de scripting dentro de los hilos de ejecución que pueden darnos más de un quebradero de cabeza. Sólo ciertos comandos y directivas son permitidas en este espacio. Begin no deja de ser una etiqueta pudiéndose desplazarse entre etiquetas mediante goto 'NombreDeEtiqueta'. Hay que tener cuidado con esto, ya que podemos crear un bucle infinito en la ejecución (claramente con goto 'Begin' xD). Igualmente se usan otros comandos como Sleep(númeroDeFramesAEsperar);

Como última mención, si tenéis métodos externos al ámbito del estado que se llaman igual que uno de los internos, se pueden acceder desde los métodos del estado a los exteriores mediante Global.NombreDelMétodo(ParametrosDelMetodo).


Una vez introducidos los estados, pasamos a analizar el estado PlayerWalking:
Añadimos en nuestro NULLPlayerController lo siguiente:

NULLPlayerController.uc
//NOTA: Este símbolo lo coloco para que sepáis que puede haber código donde se encuentre [...]
//En este caso, podéis colocar dónde queráis este pedazo de código, excepto después de DefaultProperties //o dentro de su ámbito. Es decir, dentro del ámbito de la clase, pero fuera de la inicialización de variables

state PlayerWalking
{
ignores SeePlayer, HearNoise, Bump;

function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
{
if( Pawn == None )
{
return;
}

if (Role == ROLE_Authority)
{
// Update ViewPitch for remote clients
Pawn.SetRemoteViewPitch( Rotation.Pitch );
}

Pawn.Acceleration = NewAccel;

CheckJumpOrDuck();
}

function PlayerMove( float DeltaTime )
{
local vector X,Y,Z, NewAccel;
local eDoubleClickDir DoubleClickMove;
local rotator OldRotation;
local bool bSaveJump;

if( Pawn == None )
{
GotoState('Dead');
}
else
{
GetAxes(Pawn.Rotation,X,Y,Z);

// Update acceleration.
NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y;
NewAccel.Z = 0;
NewAccel = Pawn.AccelRate * Normal(NewAccel);

if (IsLocalPlayerController())
{
AdjustPlayerWalkingMoveAccel(NewAccel);
}

DoubleClickMove = PlayerInput.CheckForDoubleClickMove( DeltaTime/WorldInfo.TimeDilation );

// Update rotation.
OldRotation = Rotation;
UpdateRotation( DeltaTime );
bDoubleJump = false;

if( bPressedJump && Pawn.CannotJumpNow() )
{
bSaveJump = true;
bPressedJump = false;
}
else
{
bSaveJump = false;
}

if( Role < ROLE_Authority ) // then save this move and replicate it
{
ReplicateMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
}
else
{
ProcessMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
}
bPressedJump = bSaveJump;
}
}

event BeginState(Name PreviousStateName)
{
DoubleClickDir = DCLICK_None;
bPressedJump = false;
GroundPitch = 0;
if ( Pawn != None )
{
Pawn.ShouldCrouch(false);
if (Pawn.Physics != PHYS_Falling && Pawn.Physics != PHYS_RigidBody) // FIXME HACK!!!
Pawn.SetPhysics(Pawn.WalkingPhysics);
}
}

event EndState(Name NextStateName)
{
GroundPitch = 0;
if ( Pawn != None )
{
Pawn.SetRemoteViewPitch( 0 );
if ( bDuck == 0 )
{
Pawn.ShouldCrouch(false);
}
}
}


Begin:
}

//[...]


Como podéis ver, hemos copiado directamente el estado de la clase PlayerController.
El funcionamiento principal reside en los métodos PlayerMove y ProcessMove.

PlayerMove

Inicialmente llama a GetAxes(Pawn.Rotation,X,Y,Z); que divide la rotación del Pawn en tres vectores X, Y, y Z que contendrán los valores de su rotación como vectores. Después, crea una variable NewAccel, con la que, utilizando PlayerInput (clase que tiene el control del mando y teclado), accediendo a aForward y aStrafe (Movimiento hacia delante y lateral respectivamente), rellena unos valores que indicarán hacia donde se mueve el Pawn. 

Después podemos ver que llama a actualizar a la rotación UpdateRotation que se encarga de Pitch y Roll de la cámara así como de llamar a actualizar la rotación de nuestro Pawn (Método FaceRotation). 

También realiza algunas comprobaciones para red y finalmente llama a ProcessMove para dar los últimos retoques a la nueva aceleración del Pawn. Por defecto, Pawn.Acceleration = NewAccel; le da la aceleración tal cual la hemos creado anteriormente en PlayerMove.

Viendo este proceso, ya podemos atacar al sistema de movimiento de nuestro personaje, haciendo que se mueva como nosotros deseamos. Del mismo modo, podemos ver que, al cambiar a tipos de volúmenes se llama a un método event NotifyPhysicsVolumeChange( PhysicsVolume NewVolume )

Podemos trabajar con nuevos estados y maneras de movernos haciendo uso de todas estas herramientas.

Un buen ejercicio ahora, sería el dar controles de SideScroller y realizar una cámara acorde.

3) EJERCICIO Cámaras (Con solución al final)

¡Muy buenas!

Al llegar al final del apartado 3, ¡ya podéis hacer vuestros experimentos con cámaras! Y por ello os propongo un reto:

Tratar de realizar una cámara que siempre siga lateralmente al Pawn, como si fuera de una cámara Sidescroller pero con el Pawn pudiendo moverse libremente.

Para ello os dejo algunas anotaciones:

- Las variables locales en UnrealScript se colocan siempre justo debajo de un método o función. No podéis declararlas justo antes de dónde las necesitéis. Unreal es así de caprichoso... ¡Por cierto! Se declaran como otra variable pero en vez de poner var, se pone local ;)

- Podéis realizar "castings" de tipos en UDK, pero se realiza de manera inversa a como se realiza en C.
Se coloca la clase que queréis convertir primero y entre paréntesis a continuación, la clase a convertir. De este modo, os dejo una de las primeras pistas: Vector (Rotator).

Podéis coger la rotación del Pawn para ver dónde está mirando y convertirlo a un Vector con coordenadas de mundo para poder colocar la cámara dónde deseemos. Del mismo modo, podemos añadir a la rotación del Pawn un valor (offset en grados).

Os recuerdo que la clase Rotator tiene tres variables: Roll, Pitch y Yaw. Yaw es la variable que necesitáis para rotar "alrededor" de un objeto. Igualmente, las unidades de dicho valor están en Unreal Rotator, pero tranquilos, que existe una constante global por la que podéis multiplicar una variable que tenga esas unidades para pasarlas a grados y viceversa. Las constantes son: unrrottodeg y degtounrrot.

- Existe un método llamado Normal que recibe como parámetro un Vector y os lo "escupe" normalizado :D

¡A darle al coco y las mates!


------------------------------------------------------------------------------------------------------------

























¿Ya lo tienes? ¿Seguro? No hagas trampas ehhhh





























******************SOLUCIÓN*********************

Bueno, ahora si. En primer lugar, le decimos a NULLPlayerController.uc que queremos usar nuestra nueva cámara:

NULLPlayerController.uc

class NULLPlayerController extends PlayerController
    //Como vimos antes, hace referencia a un fichero de
    //configuración del que se podrá configurar variables
    //Bajo el apartado [NULLGame.NULLPlayerController]
    config(Game);
 
defaultproperties
{
CameraClass=class'NULLGame.NULLCameraSideScrollerFree'

}


Y ahora la clase nueva de cámara:

NULLCameraSideScrollerFree.uc

class NULLCameraSideScrollerFree extends Camera;
    /**
    * Query ViewTarget and outputs Point Of View.
    *
    * @param    OutVT        ViewTarget to use.
    * @param    DeltaTime    Delta Time since last camera update (in seconds).
    */
    function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime)
    {

local Vector pawnFacingVector;
local Rotator tempRotator;

    // Don’t update outgoing viewtarget during an interpolation
    if( PendingViewTarget.Target != None && OutVT == ViewTarget && BlendParams.bLockOutgoing )
    {
    return;
    }

//Cogemos la rotación de nuestro Pawn para darle un offset de 90 grados
tempRotator = OutVT.Target.Rotation;
//Le damos un offset en Yaw (alrededor del Pawn)
tempRotator.Yaw -= 90.0f*DegToUnrRot;

//Convertimos la rotación del Pawn (Target en nuestro caso) y la convertimos a coordenadas de mundo
pawnFacingVector = Vector(tempRotator);
pawnFacingVector = Normal(pawnFacingVector);

//Le damos la vuelta y lo alargamos una distancia que deseemos. Podemos usar una variable para ello
//(Mucho más correcto) que darle un valor a pelo
pawnFacingVector *= -250.0f;

//Ahora tenemos el vector que nos indica dónde debe colocarse la cámara
//Debemos añadir a dicha posición la posición de nuestro Pawn (Target)
OutVT.POV.Location = OutVT.Target.Location + pawnFacingVector;

//Ahora tenemos que mirar siempre hacia nuestro Pawn!!!
OutVT.POV.Rotation = Rotator(OutVt.Target.Location-OutVT.POV.Location);

    // Apply camera modifiers at the end (view shakes for example)
    ApplyCameraModifiers(DeltaTime, OutVT.POV);

    }

    DefaultProperties
    {
    }





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: 





    miércoles, 20 de noviembre de 2013

    6) Colisiones básicas: Recolectando cosillas, ¡Hazte con todas!

    ¡Muy buenas!

    Llegados a este punto, es el momento de explicar las bases de las colisiones en UDK para poder crear nuestros objetos recolectables. Algunos sabréis que existen clases PickUp y demás, importantes para recoger objetos con un inventario. No entraremos en ello todavía, si no en la manera de colisionar con cosas, basándonos en el ejemplo del apartado anterior de recolección de monedas.

    Procedo a traducir mi antigua entrada en http://theudknown.wordpress.com/2013/03/02/6-lets-pickup-something/ ya que allí se cubre en inglés lo necesario para este punto.

    Procedemos pues a crear un pickup simple para nuestro Pawn. Con respecto al ejercicio anterior, de recolección de monedas, podréis aplicar al final el informar a GameInfo de las monedas que recolectamos y de la posibilidad de terminar la partida. En esta explicación nos centramos en las colisiones.

    Lo más importante para la tarea que nos atañe es la colocación de las "flags" de colisión correctamente. Si utilizamos StaticMeshComponents con colisiones, tendremos que asegurarnos que sus flags de colisión sean correctas. Nuestro pickup va a heredar de la clase base Actor.uc

    Las dos variables bool (esas flags de las que hablo) más importantes para la colisión tanto en Actor.uc como en los componentes de malla que utilicemos son las siguientes:

    bCollideActors : Si es false, el actor no reacciona a ningún tipo de colisión (independientemente de los valores de sus componentes, claro, por tanto, deben estar correctamente indicadas en ambos).
    bBlockActors: Si es true, el motor Unreal no permitirá que se solapen dos actores con este valor en dicha flag.

    Los eventos que utiliza Unreal para indicar que se ha realizado una colisión son los siguientes:

    event Touch(Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal):
    Este evento se llama cuando dos actores se solapan, pero sólo ocurre si uno de ellos tiene el flag
    bBlockActors con valor false.
    event Bump(Actor Other, PrimitiveComponent OtherComp, Vector HitNormal)
    Este evento se llama cuando dos actores colisionan con movimiento uno con otro, y sólo si los dos tienen el flag bBlockActors con valor true.

    Esta información se ha extraído de Romero UnrealScript blog

    Contando que nuestro pickup queremos que sea más un "trigger" deberemos configurar sus flags como
    bCollideActors=true y bBlockActors=false.

    Del mismo modo, nos aseguramos que su StaticMeshComponent es ignorado (Quizá prefieras detectar la colisión con el componente de malla en lugar de un componente de primitiva geométrica. En tal caso, hay que configurarlo acorde a lo explicado anteriormente):

    begin object Class=StaticMeshComponent Name=MyStaticMesh
    StaticMesh=StaticMesh’NodeBuddies.3D_Icons.NodeBuddy__BASE_SHORT’
    LightEnvironment=MyLightEnvironment
    bCastDynamicShadow=true
    CollideActors=false
    BlockActors=false
    //BlockRigidBody=true
    //BlockNonZeroExtent=true
    //CanBlockCamera=true
    end object
    Components.Add(MyStaticMesh)
    Si no hemos aplicado las flags de colisión con el componente de malla, lo hacemos por tanto con un componente de primitiva geométrica, en este caso un cilindro (Cyllinder):


    Begin Object Class=CylinderComponent Name=CylinderComp
    CollisionRadius=32
    CollisionHeight=48
    CollideActors=true
    BlockActors=false
    End Object
    Components.Add( CylinderComp )
    CollisionComponent=CylinderComp
    Como he explicado, dependiendo del comportamiento que desees, hay que jugar con la combinación de estas flags en el actor y en sus componentes attachados. La clase Actor indica en su variable CollisionComponent qué ActorComponent es el que utiliza para las colisiones. Al parecer los Actores sólo pueden tener una colisión, pero puedes hacer que extienda su bounding box a varios componentes si en ellos le colocas la variable CheckAlwaysCollision=true. No he hecho muchas pruebas con esto, pero si al iniciar vuestro juego pulsáis Tab os aparecerá la consola de comandos. Desde allí podéis teclear Show Collisions o Show Bounds si hacéis pruebas para activar y desactivar que se vean los mismos con malla alámbrica.

    Coloco ahora un ejemplo de pickup:


    Here it is a really simple example:
    //Placeable is necessary if you want to put this actor from the editor!
    class NULLPickUp extends Actor ClassGroup(NULLGame) placeable;
    event Touch( Actor Other, PrimitiveComponent OtherComp, Vector HitLocation, Vector HitNormal )
    {
    `log(“Touched by “@ Other.Name@”!”);
    }
    DefaultProperties
    {
    //*****************COLLISIONS*************************************
    // If false, the actor does not respond to any type of collision.
    bCollideActors=true
    // If true, the Unreal engine will not allow the two Actors to overlap.
    bBlockActors=false
    Begin Object Name=MyLightEnvironment
    bEnabled=TRUE
    End Object
    Components.Add(MyLightEnvironment)
    begin object Class=StaticMeshComponent Name=MyStaticMesh
    StaticMesh=StaticMesh’NodeBuddies.3D_Icons.NodeBuddy__BASE_SHORT’
    LightEnvironment=MyLightEnvironment
    bCastDynamicShadow=true
    //Scale=0.25
    CollideActors=true
    BlockActors=false
    //BlockRigidBody=true
    //BlockNonZeroExtent=true
    //CanBlockCamera=true
    end object
    Components.Add(MyStaticMesh)
    //Use in the console command the command Show Collision and check if this component collisions thanks to BlockNonZeroExtent
    //If you put that flag to false then no collision happens. In that case, you can put CollideActors from the static mesh = true
    Begin Object Class=CylinderComponent Name=CollisionCylinder
    CollisionRadius=+0020.000000
    CollisionHeight=+0200.000000
    CollideActors=false
    BlockActors=false
    BlockRigidBody=false
    //BlockNonZeroExtent – A non zero extent trace is an axis aligned bounding box check, which is commonly used for player movement. If this is true, then the Actor will block non zero extent traces used in Unrealscript.
    BlockNonZeroExtent=true
    End Object
    Components.Add(CollisionCylinder)
    CollisionComponent=CollisionCylinder
    }
    Como ejercicio, es interesante crearos vuestra clase PickUp y que experimentéis con `log() en las funciones de Touch y Bump y cambiéis las flags de vuestro Pawn y de vuestro PickUp para ver qué llama a qué y cuando. Dentro de `log() podéis colocar valores de variables concatenadas con texto utilizando @. Por ejemplo: `log("[MyPickUp] He tocado "@other@" y concretamente su ActorComponent " @ otherComp);
    Si colocamos esa línea en Touch o Bump de la clase PickUp, nos indicará con qué ha tocado o chocado y el componente en concreto con el que lo ha hecho.

    jueves, 24 de octubre de 2013

    4) Dale al diseñador lo que quiere. Arquetipos para todos

    ¡Muy buenas!

    Aquí nos encontramos de nuevo en el mágico mundo Digimon de la programación en UnrealScript. Hoy con nosotros: ¡Arquetipos! ¿Y qué narices es esto? Instancias de objetos referenciadas que se pueden tocar en tiempo real para el disfrute y frote de la gente que tiene que montar el juego. De este modo, desde el Content Browser, guardaremos en un paquete nuestros propios Pawn con una configuración determinada, que podremos modificar mientras el juego está funcionando. En el código, referenciaremos ese Pawn para que lo lea tal y como lo queramos.
    Igualmente, haremos un "fake" de arquetipo para nuestras cámaras, ya que, de momento, y aunque pueden ser actores también en las escenas, nosotros creamos una clase que no tiene "materialización" en la escena hasta que no comienza el juego. Crearemos una clase NULLCameraTDAttributes, cuyas variables leeremos desde nuestra cámara NULLCameraTopDown, para poder modificar en tiempo real sus características. ¡Al lío!

    Nuestra aventura de hoy nos lleva a adentrarnos en el magico mundo del editor. En el Content Browser, pestaña Actor Classes, buscamos bajo la categoría NULLGame, nuestro NULLPawn. Botón derecho y crear Arquetipo. Nos pide un Paquete, un nombre de agrupamiento y el nombre del arquetipo. Podemos poner NULLCharacters como nombre del paquete, de agrupamiento Characters y como nombre de arquetipo arc_NULLTestPawn.

    Ahora si volvemos al content browser, bajo el apartado Packages abajo a la izquierda por defecto, hay una carpeta llamada NewPackages. La desplegamos y vemos nuestro paquete. Botón derecho y guardar. Lo lleváis a la carpeta Content/NULLContent/ y lo guardáis con la jerarquía de carpetas que queráis como os sintáis más cómodos y ordenados.

    Ya tenemos nuestro arquetipo. Tendremos que buscarlo en el content browser después para conocer su Full Name y dárselo a una de nuestras variables de NULLGameInfo.

    En primer lugar, GameInfo tiene una función encargada de "spawnear" un Pawn por defecto para que PlayerController lo posea (no me pongo érotico, es que PlayerController tiene una función UnPossess y Possess cual espíritu maligno para controlar los Pawns que le indiquemos). Dicha función es "function Pawn SpawnDefaultPawnFor(Controller NewPlayer, NavigationPoint StartSpot)"

    Mi recomendación es que le echéis un ojo en la clase base. Ahora colocamos esta función en la clase que ya tenemos de GameInfo

    NULLGameInfo.uc

    ...
    /**
     * Returns a pawn of the default pawn class
     *
     * @param    NewPlayer - Controller for whom this pawn is spawned
     * @param    StartSpot - PlayerStart at which to spawn pawn
     *
     * @return    pawn
     */
    function Pawn SpawnDefaultPawnFor(Controller NewPlayer, NavigationPoint StartSpot)
    {
        //local class<Pawn> DefaultPlayerClass;
        local Rotator StartRotation;
        local Pawn ResultPawn;

        //Comentamos esta línea ya que no vamos a utilizar una clase para el jugador
        //Si no nuestro propio arquetipo
        //DefaultPlayerClass = GetDefaultPlayerClass(NewPlayer);

        // don't allow pawn to be spawned with any pitch or roll
        StartRotation.Yaw = StartSpot.Rotation.Yaw;

        //ResultPawn = Spawn(DefaultPlayerClass,,,StartSpot.Location,StartRotation);
        //Aquí podéis ver la diferencia entre el original de la función
        //Y como spawneamos nuestro arquetipo.
        //Nos faltará declarar la variable
        //var const archetype Pawn DefaultPawnArchetype;
        //Al inicio de nuestra clase
        ResultPawn = Spawn(DefaultPawnArchetype.Class,,,StartSpot.Location,StartRotation,DefaultPawnArchetype);

        if ( ResultPawn == None )
        {
            `log("[NULLGameInfo] Couldn't spawn player of type "$DefaultPawnArchetype.Class$" at "$StartSpot);
        }
        return ResultPawn;
    }
    ...


    Bien, como indico en los comentarios nos faltará añadir la variable:

    var const archetype Pawn DefaultPawnArchetype;

    y darle un valor en nuestro DefaultProperties. El cual, iremos al editor, buscaremos nuestro Pawn creado en su paquete y copiaremos su nombre total Copy Full Name to Clipboard y lo pegaremos de este modo

    DefaultProperties
    {
      DefaultPawnArchetype=<IntroduzcaAquí SuTabacoGracias>
    }

    Comentamos la línea: //DefaultPawnClass=class'NULLGame.NULLPawn' ya que ya no usamos un Pawn normal, si no un arquetipo.

    Y Voilá. Podremos ejecutar en el Editor nuestro juego y modificar mediante el content browser y seleccionando el arquetipo con F4 sus propiedades.

    Podemos hacer la prueba y buscar otra skeletal Mesh y ver cómo cambia en tiempo real :D

    ¡Por cierto! Si no sabéis ejecutar un tipo de juego concreto en el Editor, es en la pestaña View->WorldProperties->GameType y allí en DefaultGameType y GameType for PIE colocáis vuestra clase de juego NULLGameInfo. Luego le dáis a la flechita verde que hay en la barra de arriba o en la venta que queráis ejecutar el juego y a disfrutar.

    Tened en cuenta, que la malla se carga al iniciar el juego. Si desligáis el skeletalMeshComponent actual mientras se ejecuta, la malla desaparecerá pero no se cargará la nueva hasta volver a iniciar. Al ser placeable, un diseñador puede arrastrar a la escena el arquetipo y comprobar el aspecto final que desee en el juego.

    Ahora podemos, colocar las variables de nuestra cámara en una clase que herede de las más básicas en UDK, que es Object, ocultar todo componente que no nos interese y dejar sólo al descubierto las variables y valores que queramos modificar.

    Para ello generamos una clase llamada NULLCameraTDAttributes:

    NULLCameraTDAttributes.uc

    class NULLCameraTDAttributes extends Object
    HideCategories(Object);

    var (Camera) float altitude;

    defaultproperties
    {
        altitude=500.0f
    }

    HideCategories es un flag que oculta las categorías indicadas separadas por comas que no queramos mostrar en las propiedades desde el editor (con F4). De este modo, creamos una categoría camera (al colocar entre paréntesis en una variable como se indica), con la variable altitude.

    Vamos al editor y creamos nuestro arquetipo de NULLCameraTDAttributes (ojo que seguramente tendréis que desactivar algun booleano de los que aparecen en la pestana Actor Classes del Content Browser para poder ver esta clase) y siguiendo pasos parecidos a los que hemos realizado antes con el Pawn.

    Ahora en nuestra cámara deberemos consultar este valor. Creamos una variable:
    var const NULLCameraTDAttributes CameraAttributes;

    En la funcion UpdateViewTarget(out TViewTarget OutVT, float DeltaTime) cambiamos donde teníamos a pelo la altura de la cámara en unidades de Unreal por CameraAttributes.altitude.

    Ahora nos faltará colocar en nuestra cámara en default properties en valor de la variable CameraAttributes.

    IMPORTANTE: En DefaultProperties de vuestro Pawn, debéis colocar Mesh=NameDelSkeletalMesh para poder modificar la malla del mismo mediante el arquetipo.

    ¡Y ya podemos en tiempo real cambiar la altura de nuestra cámara!

    ¡Nos vemos en el siguiente tutorial!