Introducción a Unreal Script – parte 2

En este tutorial continuamos con el proyecto que dejamos en el pasado tutorial. Ahora se comienzan a complicar un poco las cosas.

  • Configuraremos en el Pawn del player un grupo de propiedades para ajustar su comportamiento físico con el mundo y le agregaremos un mesh para que luzca mucho más cool :-).
  • Crearemos una clase para configurar la cámara principal del juego, creando un estilo de juego 3ra persona.
  • Jugaremos con las propiedades del Pawn para modificar su comportamiento físico

Comencemos!!

Agregándole un mesh al Player

Del proyecto que dejamos en el tutorial pasado, cuando ejecutábamos nuestro juego lo que teníamos era una especie de estilo de juego 1ra persona donde podíamos controlar al player pero no tenía ninguna forma. Vamos a cambiar esto dándole un cuerpo a nuestro personaje.

Evidentemente lo primero que tenemos que tener es el mesh que le vamos a aplicar al player. Para este ejemplo usaremos uno de los mesh que trae el UDK, vamos a darle un vistazo.

Abre el UDK Editor y localiza el Content Browser (View – Browser Windows – Content Browser) y en la sección Packages, en el campo para filtrar, escribe: CH_LIAM_Cathode. Al momento aparecerá este paquete en los resultados de la búsqueda, despliégalo y aparecerá un Mesh, lo seleccionas y veras el preview del SkeletalMesh SK_CH_LIAM_Cathode.

Da doble clic sobre el SK_CH_LIAM_Cathode y se abrirá el AnimSet Editor del UDK con este mesh. Puedes pasar unos instantes admirándolo (para acercar la cámara desplaza hacia arriba el mouse con el clic derecho presionado).

Bien, cierra el AnimSet Editor, ya que sabemos el nombre del mesh que vamos a usar y en que paquete está, vamos a indicárselo al Pawn de nuestro player. Abre el archivo SamplePawn.uc que dejamos en el proyecto pasado. Vamos a configurar uno de sus miembros, el Mesh.

Actualmente nuestro SamplePawn consta de una sola línea (class SamplePawn extends Pawn;) ahora vamos a agregarle un bloque defaultproperties que del tutorial pasado sabemos que es para darle valor por defecto a las variables miembro que queremos. En este caso lo que buscamos es agregarle a nuestro player el mesh que acabamos de ver. Pues para esto, lo que debemos es inicializar el objeto Mesh que tiene nuestro SamplePawn (lo hereda de Pawn) Este atributo es una instancia de SkeletalMeshComponent.

Agrega el siguiente bloque defaultproperties a SamplePawn (a continuación de: class SamplePawn extends Pawn;)

defaultproperties
{
	Begin Object Class=SkeletalMeshComponent Name=WPawnSkeletalMeshComponent
		SkeletalMesh=SkeletalMesh'CH_LIAM_Cathode.Mesh.SK_CH_LIAM_Cathode'
	End Object
	Mesh=WPawnSkeletalMeshComponent
	Components.Add(WPawnSkeletalMeshComponent)
}

Como ya hemos mencionado, la sintaxis para las inicializaciones en los bloques deafultproperties son distintas a la sintaxis normal de UnrealScript. En este caso lo que hacemos es crear una instancia de SkeletalMeshComponent, inicializar su miembro SkeletalMesh y asignársela al atributo Mesh de SamplePawn. Por último lo agregamos como componente del Pawn.

Bien, realmente con esto tenemos para lograr lo que buscamos, que es agregarle un mesh al player. Pero antes debemos configurar el atributo bDelayedStart de nuestro GameInfo. Este atributo es para demorar el inicio del juego, y es usado generalmente en juegos multiplayers, por lo que en nuestro caso no lo necesitamos.

Abre el SampleGame.uc y dentro del bloque defaultproperties agrega la siguiente línea:

bDelayedStart=false

Listo, hora de ver que hemos logrado con todo esto. Guardamos los cambios y abrimos el UDK Editor, nos preguntará, si queremos recompilar los scripts, le decimos que sí. Cuando termine cargamos el level que hicimos en el tutorial pasado y damos clic derecho – Play from Here.

Uuuufff :-( Que pasa??, sigo viendo lo mismo… Exacto, lo que pasa es lo siguiente, nuestro player ya tiene cuerpo, pero la cámara por defecto de nuestro juego es estilo 1ra persona, por lo que teóricamente estamos detrás de la cámara. Vamos a solucionar este problema configurando la cámara principal de nuestro juego para lograr un estilo 3ra persona.

Configurando la cámara principal del juego para tener un estilo 3ra persona:

Este método que usaremos para configurar la cámara no es el único, pero al hacerlo en una clase independiente nos permitirá tener toda la lógica de la cámara centralizada. Para esto, necesitamos crear una nueva clase, por suerte para nosotros el UDK nos brinda una clase llamada Camera que nos ayudará con nuestro objetivo. Si buscamos esta clase en el UnCodeX, veremos que como comentario dice: Camera: defines the Point of View of a player in world space. (UDKInstall-2011-04-BETA)(No es necesario acudir a Google Translate para saber qué significa esto no ?? :-D)

Para crear nuestra cámara agregaremos una nueva clase a nuestro SampleGame que herede de Camera. Dentro de la carpeta Classes del proyecto agrega el siguiente archivo: SamplePlayerCamera.uc y copia dentro el siguiente código.

class SamplePlayerCamera extends Camera;

var Vector CamOffset;
var float CameraZOffset;
var float CameraScale, CurrentCameraScale; /** multiplier to default camera distance */
var float CameraScaleMin, CameraScaleMax;

function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime)
{
   local vector      HitLocation, HitNormal;
   local CameraActor   CamActor;
   local Pawn          TPawn;

   local vector CamStart, CamDirX, CamDirY, CamDirZ, CurrentCamOffset;
   local float DesiredCameraZOffset;

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

   // Default FOV on viewtarget
   OutVT.POV.FOV = DefaultFOV;

   // Viewing through a camera actor.
   CamActor = CameraActor(OutVT.Target);
   if( CamActor != None )
   {
      CamActor.GetCameraView(DeltaTime, OutVT.POV);

      // Grab aspect ratio from the CameraActor.
      bConstrainAspectRatio   = bConstrainAspectRatio || CamActor.bConstrainAspectRatio;
      OutVT.AspectRatio      = CamActor.AspectRatio;

      // See if the CameraActor wants to override the PostProcess settings used.
      CamOverridePostProcessAlpha = CamActor.CamOverridePostProcessAlpha;
      CamPostProcessSettings = CamActor.CamOverridePostProcess;
   }
   else
   {
      TPawn = Pawn(OutVT.Target);
      // Give Pawn Viewtarget a chance to dictate the camera position.
      // If Pawn doesn't override the camera view, then we proceed with our own defaults
      if( TPawn == None || !TPawn.CalcCamera(DeltaTime, OutVT.POV.Location, OutVT.POV.Rotation, OutVT.POV.FOV) )
      {
         /**************************************
          * Calculate third-person perspective
          * Borrowed from UTPawn implementation
          **************************************/
         OutVT.POV.Rotation = PCOwner.Rotation;
         CamStart = TPawn.Location;
         CurrentCamOffset = CamOffset;

         DesiredCameraZOffset = 1.2 * TPawn.GetCollisionHeight() + TPawn.Mesh.Translation.Z;
         CameraZOffset = (DeltaTime < 0.2) ? DesiredCameraZOffset * 5 * DeltaTime + (1 - 5*DeltaTime) * CameraZOffset : DesiredCameraZOffset;

         CamStart.Z += CameraZOffset;
         GetAxes(OutVT.POV.Rotation, CamDirX, CamDirY, CamDirZ);
         CamDirX *= CurrentCameraScale;

         TPawn.FindSpot(Tpawn.GetCollisionExtent(),CamStart);
         if (CurrentCameraScale < CameraScale)          {             CurrentCameraScale = FMin(CameraScale, CurrentCameraScale + 5 * FMax(CameraScale - CurrentCameraScale, 0.3)*DeltaTime);          }          else if (CurrentCameraScale > CameraScale)
         {
            CurrentCameraScale = FMax(CameraScale, CurrentCameraScale - 5 * FMax(CameraScale - CurrentCameraScale, 0.3)*DeltaTime);
         }
         if (CamDirX.Z > TPawn.GetCollisionHeight())
         {
            CamDirX *= square(cos(OutVT.POV.Rotation.Pitch * 0.0000958738)); // 0.0000958738 = 2*PI/65536
         }
         OutVT.POV.Location = CamStart - CamDirX*CurrentCamOffset.X + CurrentCamOffset.Y*CamDirY + CurrentCamOffset.Z*CamDirZ;
         if (Trace(HitLocation, HitNormal, OutVT.POV.Location, CamStart, false, vect(12,12,12)) != None)
         {
            OutVT.POV.Location = HitLocation;
         }
      }
   }

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

defaultproperties
{
   CamOffset=(X=12.0,Y=0.0,Z=-13.0)
   CurrentCameraScale=1.0
   CameraScale=9.0
   CameraScaleMin=3.0
   CameraScaleMax=40.0
}

Tomado de: udn.epicgames.com

😮 ¡!! Como vez, para lograr la cámara que queremos no es un par de líneas solamente. El tema de cámaras en UnrealScript es bastante extenso por lo que será un tutorial completo de esta serie. Por el momento es suficiente con saber que básicamente lo que hacemos con esta clase, es sobrescribir el método UpdateViewTarget para desplazar la cámara del player un poco hacia atrás y hacia arriba logrando el estilo 3ra persona. UpdateViewTarget es llamada en cada frame de nuestro juego para actualizar la orientación y rotación de la cámara de acuerdo a la posición del Player.

Te repito, no te preocupes si no entiendes cada una de las líneas de esta clase, en el tutorial de cámaras de esta serie abarcaremos detalladamente este tema.

Solamente queda indicarle al PlayerController que use esta cámara. Abre la clase SamplePlayerController y a continuación de la línea: class SamplePlayerController extends PlayerController; agrega el siguiente bloque.

defaultproperties
{
   CameraClass=class'SampleGame.SamplePlayerCamera'
}

Ya a estas alturas, supongo que no tengas problemas con esto, no?? :-)

Listo!!!. Compila y prueba.

EEEHHH!! Ya el player tiene forma (lo que te debe dar una buena alegría) pero tenemos algunos problemas que resolver: primero, a pesar de tener una luz en la habitación, el mesh está muy oscuro, segundo, si nos tratamos de mover nos desplazamos pero sin mover ni una articulación :-S Bien…, vamos a arreglar esto.

Lo primero, es adjuntarle al Mesh del Player, además de su “esqueleto”, la animación de sus movimientos, para esto la clase SkeletalMeshComponent (de la que es instancia Mesh) tiene dos miembros AnimTreeTemplate y el array AnimSets. El primero (AnimTreeTemplate) debe hacer referencia a un AnimTree. Para nuestro ejemplo usaremos uno de los que trae el UDK en este caso CH_AnimHuman_Tree.AT_CH_Human. Puedes buscarlo en el Content Browser del UDK Editor como mismo hicimos para el esqueleto. Y el segundo es un arreglo donde podemos almacenar las distintas secuencias de animación del personaje. En este caso, usaremos CH_AnimHuman.Anims.K_AnimHuman_BaseMale, puedes buscar también ésta con el Content Browser, da doble clic y en la ventana Unreal AnimSet Editor, en el panel Browser de la izquierda, selecciona la pestaña Anim y verás un ListBox con todas las Secuencias de Animaciones para los distintos movimientos del personaje.

Por suerte para nosotros!!!, todo esto es trabajo de los diseñadores de nuestro equipo 😀 .

Entonces, en la clase SamplePawn agregamos a continuación de la línea SkeletalMesh=SkeletalMesh’CH_LIAM_Cathode.Mesh.SK_CH_LIAM_Cathode’, las siguientes líneas:

AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'

Aquí lo único nuevo que tenemos, en cuanto a sintaxis de UnrealScript es la inicialización de un index del arreglo AnimSets, nada raro, sencillamente entre paréntesis indicamos el index del array.

Listo!!!. Compila y prueba.

😀 Ahora sí tenemos a nuestro robot, moviéndose correctamente animado por nuestra habitación, pero aún nos queda un problemita…., la luz.

Para solucionar esto debemos agregar a nuestro Pawn una instancia de DynamicLightEnvironmentComponent, esta clase es usada para iluminar los componentes y/o los actores durante el juego.

Abre el archivo SamplePawn.uc, e inmediatamente después de la línea class SamplePawn extends Pawn; y antes del bloque defaultproperties agrega la siguiente línea:

var DynamicLightEnvironmentComponent LightEnvironment;

Con esta línea estamos agregando un nuevo miembro a SamplePawn de tipo DynamicLightEnvironmentComponent. En UnrealScript las variables de instancia de una clase son declaradas con la palabra clave var.

Bien, hecho esto ahora debemos inicializar este miembro y agregarlo como componente a nuestro Pawn. Dentro del bloque defaultproperties agrega el siguiente bloque (antes de: Begin Object Class=SkeletalMeshComponent Name=WPawnSkeletalMeshComponent)

Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
      // Configuración de los atributos
End Object
LightEnvironment=MyLightEnvironment
Components.Add(MyLightEnvironment)

Bien, a esta altura tampoco te debe sorprender la sintaxis de aquí. Dentro del bloque Begin Object podemos poner la inicialización de los atributos de DynamicLightEnvironmentComponent, para lograr objetivos específicos con las luces. En futuros tutoriales ampliaremos el tema de las luces con UnrealScript, por el momento con los valores por defecto tenemos.

Ya configurado MyLightEnvironment debemos indicarlo en el LightEnvironment del SkeletalMeshComponent. Para esto dentro del bloque Begin Object Name=WPawnSkeletalMeshComponent agrega la siguiente línea:

LightEnvironment=MyLightEnvironment

Listo!!!. Compila y prueba.

Very cool ehh?? 😀

Jugando con las propiedades de Pawn:

Bien, ya tenemos un personaje para nuestro juego. Ahora, podemos jugar un poco con las propiedades de Pawn para modificar las características de nuestro robot, por ejemplo, si queremos modificar la velocidad máxima de desplazamiento de nuestro robot, podemos ajustar el valor del atributo GroundSpeed. Para esto, en la clase SamplePawn dentro del bloque defaultproperties agregamos la siguiente línea:

GroundSpeed=440.0

Si revisas con el UnCodeX la clase Pawn verás tooooodoooosss 😮 los atributos que podemos ajustar, te dejo a ti para que juegues un poco con ellos 😉 con los comentarios que traen es suficiente para tener una noción de lo que hacen.

Conclusión

Hasta aquí esta segunda parte (y final) de Introducción a UnrealScript. Con lo que hemos hecho hasta ahora ya estamos listos para jugar un poco con los atributos que tienen las distintas clases de las que hemos heredado.

En el próximo tutorial veremos una introducción a Actors (casi todo en el mundo UDK hereda de Actor). Veremos como nuestro robot puede interactuar con otros elementos del juego. Aprenderemos a compilar y ejecutar nuestro juego desde el Unreal FrontEnd, entre otras cosas.

Hasta entonces… chao!!! 😉

PD: Aquí te dejo el contenido de todas las clases de nuestro proyecto para una referencia rápida.

SampleGame.uc

class SampleGame extends GameInfo;

defaultproperties
{
   PlayerControllerClass=class'SampleGame.SamplePlayerController'
   DefaultPawnClass=class'SampleGame.SamplePawn'
   bDelayedStart=false
}

SamplePawn.uc

class SamplePawn extends Pawn;

var DynamicLightEnvironmentComponent LightEnvironment;

defaultproperties
{
	GroundSpeed=440.0

	Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
		//...
	End Object
	Components.Add(MyLightEnvironment)
	LightEnvironment=MyLightEnvironment
   
	Begin Object Class=SkeletalMeshComponent Name=WPawnSkeletalMeshComponent
		SkeletalMesh=SkeletalMesh'CH_LIAM_Cathode.Mesh.SK_CH_LIAM_Cathode'
		AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
		AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
		LightEnvironment=MyLightEnvironment
	End Object
	Mesh=WPawnSkeletalMeshComponent
	Components.Add(WPawnSkeletalMeshComponent)
} 

SamplePlayerCamera.uc

class SamplePlayerCamera extends Camera;

var Vector CamOffset;
var float CameraZOffset;
var float CameraScale, CurrentCameraScale; /** multiplier to default camera distance */
var float CameraScaleMin, CameraScaleMax;

function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime)
{
   local vector      HitLocation, HitNormal;
   local CameraActor   CamActor;
   local Pawn          TPawn;
   
   local vector CamStart, CamDirX, CamDirY, CamDirZ, CurrentCamOffset;
   local float DesiredCameraZOffset;

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

   // Default FOV on viewtarget
   OutVT.POV.FOV = DefaultFOV;

   // Viewing through a camera actor.
   CamActor = CameraActor(OutVT.Target);
   if( CamActor != None )
   {
      CamActor.GetCameraView(DeltaTime, OutVT.POV);

      // Grab aspect ratio from the CameraActor.
      bConstrainAspectRatio   = bConstrainAspectRatio || CamActor.bConstrainAspectRatio;
      OutVT.AspectRatio      = CamActor.AspectRatio;

      // See if the CameraActor wants to override the PostProcess settings used.
      CamOverridePostProcessAlpha = CamActor.CamOverridePostProcessAlpha;
      CamPostProcessSettings = CamActor.CamOverridePostProcess;
   }
   else
   {
      TPawn = Pawn(OutVT.Target);
      // Give Pawn Viewtarget a chance to dictate the camera position.
      // If Pawn doesn't override the camera view, then we proceed with our own defaults
      if( TPawn == None || !TPawn.CalcCamera(DeltaTime, OutVT.POV.Location, OutVT.POV.Rotation, OutVT.POV.FOV) )
      {   
         /**************************************
          * Calculate third-person perspective
          * Borrowed from UTPawn implementation
          **************************************/
         OutVT.POV.Rotation = PCOwner.Rotation;                                                   
         CamStart = TPawn.Location;
         CurrentCamOffset = CamOffset;
         
         DesiredCameraZOffset = 1.2 * TPawn.GetCollisionHeight() + TPawn.Mesh.Translation.Z;
         CameraZOffset = (DeltaTime < 0.2) ? DesiredCameraZOffset * 5 * DeltaTime + (1 - 5*DeltaTime) * CameraZOffset : DesiredCameraZOffset;
         
         CamStart.Z += CameraZOffset;
         GetAxes(OutVT.POV.Rotation, CamDirX, CamDirY, CamDirZ);
         CamDirX *= CurrentCameraScale;
      
         TPawn.FindSpot(Tpawn.GetCollisionExtent(),CamStart);
         if (CurrentCameraScale < CameraScale)
         {
            CurrentCameraScale = FMin(CameraScale, CurrentCameraScale + 5 * FMax(CameraScale - CurrentCameraScale, 0.3)*DeltaTime);
         }
         else if (CurrentCameraScale > CameraScale)
         {
            CurrentCameraScale = FMax(CameraScale, CurrentCameraScale - 5 * FMax(CameraScale - CurrentCameraScale, 0.3)*DeltaTime);
         }                              
         if (CamDirX.Z > TPawn.GetCollisionHeight())
         {
            CamDirX *= square(cos(OutVT.POV.Rotation.Pitch * 0.0000958738)); // 0.0000958738 = 2*PI/65536
         }
         OutVT.POV.Location = CamStart - CamDirX*CurrentCamOffset.X + CurrentCamOffset.Y*CamDirY + CurrentCamOffset.Z*CamDirZ;
         if (Trace(HitLocation, HitNormal, OutVT.POV.Location, CamStart, false, vect(12,12,12)) != None)
         {
            OutVT.POV.Location = HitLocation;
         }
      }
   }

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

defaultproperties
{
   CamOffset=(X=12.0,Y=0.0,Z=-13.0)
   CurrentCameraScale=1.0
   CameraScale=9.0
   CameraScaleMin=3.0
   CameraScaleMax=40.0
}

SamplePlayerController.uc

class SamplePlayerController extends PlayerController;

defaultproperties
{
   CameraClass=class'SampleGame.SamplePlayerCamera'
}

Nota: Este proyecto fue hecho con la versión del UDK de Abril de 2011 (UDKInstall-2011-04-BETA)

Fernando Castillo Coello
Follow me

Fernando Castillo Coello

Gameplay Programmer at GameOlic
I've been into games development with Unreal Engine since 2014 and want to share with you some of the tips and tricks that I learned during my journey.
Fernando Castillo Coello
Follow me

0 pensamientos en “Introducción a Unreal Script – parte 2

  1. Cristian (REBIRTH)

    hola, gracias por los tutoriales, soy nuevo en el unreal script y me han ayudado, pero tengo 2 errores y no se si esten afectando el que no se vea mi personaje cuando ejecuto, es como si estuviera en primera persona el juego todavia.

    [000787] c:UDKUDK-2011-12DevelopmentsrcMYGAMEClassesMyPlayerControler.uc(5) : Warning, ClassProperty Engine.PlayerController : CameraClass: unsolved reference to ‘class’GameInfoGame.CamaraJuego”

    y el otro error es igual pero dice : Warning Ivalid property value in defaults

    te agradeceria mucho la ayuda. xD bye

    Responder

Responder a Cristian (REBIRTH) Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>