Notas sobre UnrealScript

Bruno Torijano y Federico Peinado

Introducción

UnrealScript es el lenguaje de guiones que utiliza la tecnología Unreal. La versión de Unreal a la que nos referimos en este artículo es la del motor del juego Unreal Tournament 2004 (UT2004).

De entre los distintos formatos de fichero que podemos encontrar en la distribución del UT2004, los más interesantes para el programador de UnrealScript son los ficheros UC (código fuente en formato de texto plano) y los ficheros U (paquetes de código compilado y otros recursos del juego).

Entorno de edición y compilación

Para programar en UnrealScript es necesario configurar nuestro entorno de desarrollo y conocer cómo funciona en ese entorno la edición de los ficheros fuente (UC) y posterior compilación a ficheros objeto (U).

Podemos usar cualquier editor de texto plano para escribir el código, aunque se recomienda usar ConTEXT por ser capaz de resaltar la sintaxis de UnrealScript. Descargamos el instalador y el addon específico para resaltar la sintaxis UnrealScript, una vez instalado el programa se selecciona UnrealScript en la pestaña adecuada de Highligh y listo. Los ficheros UC deben almacenarse dentro de [UT2004]\[Proyecto]\Classes siendo [UT2004] el directorio donde hayamos instalado el UT2004 y [Proyecto] el nombre de nuestro proyecto. También se puede colocar en ese subdirectorio todos los demás ficheros que vayamos a utilizar (audio, texturas, etc.).

Para compilar el código que hayamos escrito, se recomienda utilizar directamente el fichero ejecutable ucc.exe (compilador de UnrealScript) que encontramos en el subdirectorio System del directorio [UT2004]. En ese mismo subdirectorio es donde se generará el fichero compilado U, para que sea ejecutable. Al tratarse de una operación que repetiremos mucho, es buena idea crear un fichero BAT en ese mismo subdirectorio con el que llamaremos al compilador. ¡Atención! cuando se recompila un proyecto, el compilador no genera nuevos ficheros U si no hemos borrado previamente los viejos, por lo que conviene poner esa tarea en primer lugar dentro del fichero BAT.

Ejemplo de fichero make.bat:

@echo off
del %1.u
ucc make -ini=UT2004_make.ini

Haciendo una llamada a make [Proyecto] se borra primero el fichero [Proyecto].u y luego se llama al compilador indicándole el comando make con un fichero UT2004_make.ini, que explicamos a continuación, como parámetro donde se especifican los nombres de las carpetas donde se habrá de buscar el código fuente.

El fichero INI es una versión modificada del fichero UT2004.ini que está en [UT2004]/System. Una vez copiado este fichero (para evitar modificar el original) lo renombramos y lo editamos. Hay que buscar dentro de él la sección EditPackages e incluir la entrada EditPackages=[Proyecto] al final de la lista de entradas de dicha sección. Cuando se ejecute este fichero BAT, el compilador nos proporcionará los avisos o errores correspondiente, deteniéndose el proceso de compilación en este último caso. La información del proceso de compilación queda registrada en [UT2004]/System/UCC.log mientras que la información de la salida estándar se registra en [UT2004]/System/StdOut.log. Si no hay errores, se genera el fichero [UT2004]/System/[Proyecto].u.

Fundamentos del lenguaje

UnrealScript es un lenguaje orientado a objetos y en estas notas suponemos que el lector está familiarizado con los conceptos de este paradigma de programación. El lector puede considerar que el lenguaje más similar en cuanto a sintaxis y funcionamiento es C++.

Variables

UnrealScript soporta de forma nativa los siguientes tipos de datos básicos para las variables:

Las constantes se declaran con esta sintaxis:

const tipo nombre = valor;

Las variables locales (a nivel de función) se declaran con esta sintaxis:

local tipo nombre, nombre ...;

Las variables globales (a nivel de clase) se declaran según una de estas tres posibles sintaxis:

var(grupo) modificadores tipo nombre, nombre, ...;
var() modificadores tipo nombre nombre, ...;
var modificadores tipo nombre, nombre, ...;

La sintaxis más común es la segunda, ya que los paréntesis tras la palabra clave var permiten que sea reconocida por el editor UnrealEd (lo cual es interesante para aquellos que lo utilizan). El nombre que se escribe dentro de esos paréntesis sirve para agrupar las variables en el interfaz de dicho editor. Algunos de los modificadores posibles son los siguientes:

Estos tipos básicos pueden utilizarse para construir subtipos, tipos enumerados, arrays, estructuras, etc.

Tipos enumerados

Esta es la sintaxis para definir un tipo enumerado:

enum NombreEnum { ValorEnum1, ValorEnum2, ... };

Se puede declarar una variable al mismo tiempo que se define un tipo enumerado, así:

var enum NombreEnum { ValorEnum1, ValorEnum2, ... } nombre, nombre, ...;

Para obtener el número de elementos de un tipo enumerado se puede usar cualquiera de estas dos expresiones:

EnumCount(NombreEnum)
NombreEnum.EnumCount

Estructuras

Esta es la sintaxis para definir una estructura:

struct modificadores StructName {
   declaración de variable 1;
   declaración de variable 2;
   ...
};

También existen modificadores para las estructuras, pero no son tan interesantes como los de las variables. Se puede declarar una variable al mismo tiempo que se define una estructura, así:

var ModificadoresVariable struct ModificadoresStruct NombreStruct {
   declaración de variable 1;
   declaración de variable 2;
   ...
} nombre, nombre, ...;

Las estructuras pueden extenderse para construir nuevos tipos, como puede verse en este ejemplo:

// Un punto o vector dirección en un espacio 3D
struct Vector {
   var() config float X, Y, Z;
};

// Un plano en un espacio 3D
struct Plane extends Vector {
   var() config float W;
};

Arrays

Esta es la sintaxis para definir un array estático de una dimensión:

var/local tipo nombre[longitud], ...;

El tipo no puede ser bool.

Para definir un array estático multidimensional es necesario anidar estructuras definidas previamente que contienen arrays estáticos de una dimensión, así:

struct TMultiArray {
   var int Y[100];
};

var TMultiArray X[100];

Esta es la sintaxis para definir un array dinámico, lo más parecido a una plantilla de una lista ordenada que existe en UnrealScript:

var/local array NombreArray, ...;

Además del acceso normal a arrays utilizando el operador [], es posible añadir y eliminar elementos con las siguientes funciones predefinidas:

Funciones

Debido a la orientación a objetos de UnrealScript, bastante estricta, todas las funciones son en realidad métodos de clases. Esta es la sintaxis para definir una función:

modificadoresFuncion function tipoDevuelto nombreFuncion( parametros )

Los modificadores que pueden aplicarse en una función son los detallados a continuaci¢n:

Esta es la sintaxis para definir los parámetros de una función:

modificador tipo nombreParametro

Los modificadores que pueden aplicarse en los parámetros de una función son los detallados a continuaci¢n:

Clases

Todas las clases de UnrealScript deben heredar de otra (habitualmente Object o Actor). Esta es la sintaxis para definir la cabecera de una clase:

class miClase extends clasePadre modificadores

Los modificadores que pueden aplicarse en la cabecera de una clase son los detallados a continuación:

Las clase de UnrealScript se distinguen por no tener constructores: todos los objetos instanciados se inicializan a partir de una misma configuración por defecto. Esta configuración es un campo defaultproperties que suele ubicarse al final de su definición, como una secuencia encerrada entre llaves de sentencias de asignación sin puntos y comas entre ellas.

Ejemplo de definición de clase (fichero MinigunHEAltFire.uc extraído del wiki oficial de UT2004)

class MinigunHEAltFire extends MinigunAltFire;
var() class ExplosionClass;
var() class ExplosionDecalClass;
//=============================================================================
function BlowUp(vector HitLocation){
   Instigator.HurtRadius(15, 45, DamageType, 250, HitLocation );
   Instigator.MakeNoise(1.0);
}
//=============================================================================
simulated function Explode(vector HitLocation, vector HitNormal){
   Instigator.PlaySound(sound'WeaponSounds.BExplosion3',,2.5*TransientSoundVolume);
   if ( Instigator.EffectIsRelevant(HitLocation,false) ){
     Instigator.Spawn(ExplosionClass,,,HitLocation + HitNormal*16,rotator(HitNormal));
     Instigator.Spawn(ExplosionDecalClass,,,HitLocation, rotator(HitNormal));
   }
   BlowUp(HitLocation);
}
//=============================================================================
simulated function SpawnBeamEffect(Vector Start, Rotator Dir, Vector HitLocation, Vector HitNormal, int ReflectNum){
   Super.SpawnBeamEffect( Start, Dir, HitLocation, HitNormal, ReflectNum);
   Explode( HitLocation, HitNormal );
}
//=============================================================================
defaultproperties {
   ExplosionClass=class'NewExplosionB'
   ExplosionDecalClass=class'RocketMark'
}

Esta es la sintaxis para declarar e instanciar un objeto de una clase:

var class nombreObjeto;
nombreObjeto = class'nombreClase';

Los objetos pueden comportarse como máquinas de estados, definiento secciones marcadas con etiquetas de estado en las funciones, en las cuales se codificarán comportamientos distintos.

Copyright © 2005 por Federico Peinado