0% encontró este documento útil (0 votos)
90 vistas237 páginas

Docslide - Es Curso Unity Script

Este documento presenta una introducción a un blog sobre programación en Unity. El autor explica que es un autodidacta sin experiencia profesional pero desea compartir su aprendizaje. Reconoce que puede cometer errores y pide correcciones. El autor también resume los conocimientos previos necesarios como programación básica y familiaridad con Unity. Finalmente, presenta un diagrama del sistema de herencia de clases principales en Unity para orientar el aprendizaje.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
90 vistas237 páginas

Docslide - Es Curso Unity Script

Este documento presenta una introducción a un blog sobre programación en Unity. El autor explica que es un autodidacta sin experiencia profesional pero desea compartir su aprendizaje. Reconoce que puede cometer errores y pide correcciones. El autor también resume los conocimientos previos necesarios como programación básica y familiaridad con Unity. Finalmente, presenta un diagrama del sistema de herencia de clases principales en Unity para orientar el aprendizaje.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 237

0.

PRELIMINARES Y ALGUNOS
PRESUPUESTOS
Bienvenid@s a unityscript.

Antes de entrar en materia y para que nadie se lleve a engaño, considero honesto advertir a los
hipotéticos lectores de que el autor de estas líneas no es programador profesional, ni ha creado ninguno
de los cien videojuegos más vendidos de la historia. De hecho, si he de ser totalmente sincero, hace
menos de tres años este que os está escribiendo no había escrito ni una sola línea de código.

Puede parecer por lo tanto un atrevimiento -y de hecho lo es- que un tipo como yo, un autodidacta sin
ninguna base académica, se atreva a inaugurar un blog de estas carácteristicas, y comprenderé
perfectamente que alguien con conocimientos profundos sobre programación en general o Unity en
particular salga corriendo de este site.

Reconozco ya de antemano, por lo expuesto, que muy probablemente algunas de las cosas que os vaya
explicando en las lecciones que vendrán a continuación puedan contener errores, y pido disculpas
anticipadas por ello. Huelga decir que agradeceré cualquier corrección por parte de personas con más
conocimientos.

Me gustaría explicaros brevemente las razones por las cuales me he decidido a crear este blog, las
razones por las que creo que este blog es necesario:

Yo aterricé en el universo Unity hace unos cuatro meses. Más o menos como todos, me di una vuelta por
la interfaz gráfica, estuve peleándome con las cámaras, encendí las luces y puse niebla a la escena,
trasteé con el terrain hasta crear algo parecido a una isla, importé algunos modelos de Blender (todavía
ando buscando las texturas de esos modelos) y cosas parecidas.

Luego empecé a leerme todos los libros que pillé sobre Unity, que no es que sean demasiados. Y cuando
me los hube leído todos, ya dominaba más o menos decentemente la interfaz del engine, pero seguía sin
tener ni idea sobre la API de Unity. En los libros que en la actualidad hay sobre la materia la parte relativa
a los scripts -que en el fondo es la más importante- se deja en un segundo término. Se incluyen, un poco
a boleo, varios scripts, pero de una manera totalmente desordenada y sin ningún interés pedagógico,
pareciera que más para impresionar al respetable o para copiar y pegar en otros proyectos sin saber a
ciencia cierta qué se está haciendo, que para realmente explicar de dónde sale cada cosa, cuáles son
las clases principales, su cometido y relación de herencia entre ellas, qué demonios en un cuaternión,
etc.

La única vía para hacerse con la API de Unity pasaba pues por ir al manual de referencia de la propia
página web del engine, la cual presenta un triple inconveniente:

1)Está en inglés.
2)Está escrito más como manual de consulta que como instrumento de aprendizaje.
3)Sigue un orden alfabético, no un orden lógico.

El primer inconveniente no lo fue tanto, ya que hace cuatro años (uno antes de empezar a aprender
informática) seguí al pie de la letra el consejo de un amigo:

- Si quieres aprender a programar, antes aprende inglés.

Y como este que os habla suple su falta de talento con una buena dosis de cabezonería, a estudiar
inglés que me puse. De esta manera y gracias al consejo de mi amigo -que ruego os apliquéis los que no
os llevéis bien con la lengua de Shakespeare- durante estas últimas semanas he traducido la casi
totalidad de las clases, estructuras y enumeraciones de Unity al castellano. No es que sea una traducción
muy pulida, aviso desde ya, pero para nuestros fines bastará.

De los otros dos inconvenientes nos tendremos que ir ocupando poco a poco, por el viejo procedimiento
de prueba y error y usando el sentido común y la constancia donde el resto falle. Tengo el firme propósito
de ir haciendo vuestros los avances que a su ver yo vaya haciendo en mi propio aprendizaje, y aunque
ello entrañe ir a un ritmo lento y en ocasiones nos implique tener que retroceder para revisitar algún
concepto, también creo que puede ser interesante para alquien que empieza en esto escuchar las
explicaciones y/u opiniones de otro que recién está aprendiendo también y que -al contrario que algunos
autores con más conocimientos- no da nada por supuesto. Así que no esperéis scripts de cuatro páginas
(no al menos el primer año), con todo lo bueno y lo malo que eso pueda conllevar. Y, por supuesto, sería
muy positivo que hubiera la suficiente retroalimentación con todo aquél que esté interesado en hacer
común el camino de aprendizaje.

Eso sí, para no tener que hacer una parada conjunta antes de dar siquiera el primer paso, esto es, para
poder seguir este "curso" (por llamarlo de alguna manera) presupongo que quien lea esto tiene una serie
de conocimientos de base ya adquiridos, a saber:

1) Algo de programación en general, y de Javascript en particular. (Variables, funciones, bucles,


condicionales, etc.)
2) Conocimientos rudimentarios de POO (Programación Orientada a Objetos)
3) Haberle dado unas cuantas vueltas a la interfaz de Unity y estar familiarizado con su terminología
(gameobjects, assets, y esas cosas)

Si no tenéis esos conocimientos previos, ni podréis seguir las explicaciones ni podréis hacer juego
alguno (desde ya les aseguro a los amigos de los atajos que no existe un software mágico que
obedeciendo a tus impulsos cerebrales te construya de la nada un juego online multijugador listo para ser
distribuido).

Y sin más preámbulos, creo que podemos dar la introducción por acabada y meternos en materia.

1. ALGUNAS PECULIARIDADES DE UNITY


Antes de adentrarnos en la API de Unity, conviene saber que el engine admite tres lenguajes de programación para sus
scripts: C#, Boo (una variante de Python) y algo muy parecido a Javascript, que es lo que vamos a usar nosotros en
este curso.

Javascript es de los tres lenguajes indicados el más sencillo, y si bien algunos programadores con un cierto nivel
aseguran que C# les ofrece más control, para nuestros fines y nivel con Javascript vamos más que sobrados.

Una de las cosas que choca de entrada en Unity para los que tuvieran un conocimiento previo de Javascript es la forma
de declarar las variables. A diferencia del Javascript standard, en Unity hemos de declarar el tipo de la variable, un poco
a la antigua usanza de C y C++. Por ejemplo, para declarar la variable numero de tipo int hariamos:
var numero : int;
numero = 10;

Y si la quisiéramos inicializar en la declaración, sería:


var numero : int = 10;

Esto es, palabra reservada "var" para indicar que vamos a declarar una nueva variable, el nombre que le demos a ésta
seguido de dos puntos y el tipo de la variable declarada.

En otro orden de cosas, hemos de decir que la API de Unity está orientada a objetos y se compone de algo más de un
centenar de clases, de las cuales una tercera parte más o menos están relacionadas entre sí en términos de herencia y
el resto o son conjuntos de funciones auxiliares (clase tiempo, de funciones matematicas, etc) o bien meramente
permiten funcionalidades especiales no contempladas en lo que podríamos llamar las clases troncales (clases relativas
a la GUI o para conectarse a un servidor, por ejemplo)

Entendiendo que las clases que están vinculadas entre sí son a su vez las que nos pueden dar una idea más precisa
del mecanismo de funcionamiento de Unity, son con vuestro permiso -y con alguna excepción que iremos haciendo- por
las que me gustaría empezar. Y ya que -salvo error- nadie se ha tomado la molestia de hacer un esquema del sistema
de herencia de estas clases, me tomé la libertad de hacerlo yo.

Está incompleto, ya que una veintena de clases de las que estuve mirando cuando estaba llevando a cabo la traducción
no me parecieron demasiado importantes, o por lo menos no a estas alturas, pero en esencia este sería el arbolito de
clases con que nos va a tocar lidiar durante una buena temporada.

Para agrandar la imagen pulsa AQUÍ

Recomiendo imprimir este esquema, ya que lo vamos a usar de referencia para, por ejemplo, saber qué funciones
hereda una determinada clase, o qué clase tiene un atributo que es una instancia de otra clase. Por experiencia -poca,
pero experiencia al cabo- os digo que o tenéis una referencia a mano o todo os acabará sonando igual y al final os
quedaréis sin el que debiera ser vuestro objetivo, que no es aprenderse todas las funciones de memoria, sino entender
por qué la API de Unity está estructurada de una manera y no de otra.

Como podréis observar, he incluido en el esquema las variables, funciones y funciones de clase (o atributos y métodos,
si preferís la terminología de la POO) de la clase base (Object), la principal de las que derivan de aquélla (Component) y
la que a su vez hereda de ésta y es heredada por otro número de clases. Esto nos permite, como decía antes, seguir la
pista a las variables y funciones heredadas por -pongamos un ejemplo- la clase Camera, que hereda de Behaviour y por
tanto también de Component y por último de Object. Así, podemos presuponer que esto es válido:
var camaraDeSeguridad : Camera;
camaraDeSeguridad.name = "Mi camara";

Esto es, nuestro objeto camera debería haber heredado una variable name de la clase Object. Teniendo a mano el
esquema nos podemos evitar andar rebuscando por el manual de referencia.

Y con esto finalizamos esta lección. Para la próxima nos tocará ya abrir Unity.

2. ¿DONDE VAN LOS SCRIPTS?

Bueno, tras un par de capítulos con los prolegómenos, creo que ya podemos entrar en materia, para lo cual vamos a
abrir Unity.

Nota: En principio, y salvo que diga lo contrario, todas las características y scripts que vamos a utilizar sirven para la
versión free como para la PRO, así que me da igual la versión que tengáis.

Vamos a crear un proyecto nuevo, que podéis llamar como os dé la gana y guardar donde queráis, que para eso e
ordenador es vuestro. No vamos a importar de momento ningún asset, así que dejamos desmarcadas todas las casillas
y creamos el proyecto.

Lo único que nos aparecerá en la interfaz, por tanto, será la cámara principal. La marcamos y en el inspector, la
colocamos en las siguientes coordenadas: X=0, Y=1, Z =-5.

Ahora vamos a introducir un cubo en la escena. Nos vamos al menú superior, Gameobject => Create other => Cube. Al
cubo primero lo vamos a castellanizar, y en consecuencia le llamaremos Cubo. Le podemos cambiar el nombre en el
inspector, o bien pulsando F2 en la jerarquía con Cube seleccionado. Vamos a ubicar nuestro cubo en las coordenadas
0,0,0.

Vamos a salvar la escena. File =>Save Scene. Para no liarnos, recomiendo que le pongáis el mismo nombre que yo,
esto es, Ejemplo_1.

Si todo ha ido bien, debéis tener algo parecido a esto:

Ahora, en la vista Project, en un espacio libre debajo de donde está la escena guardada, hacemos click derecho con el
ratón, y en el menú emergente seleccionamos Create => Folder. A la carpeta que aparecerá le llamaremos Mis scripts.
Con el puntero del ratón sobre dicha carpeta vamos a hacer de nuevo click derecho, y esta vez seleccionamos
Create=>Javascript. Nos aparecerá un icono representando al nuevo script con el nombre por defecto
"NewBehaviourScript". Lo renombramos, llamándolo MiPrimerScript, y le damos a return para que nos conserve el
cambio.

Como podemos observar, en el inspector al seleccionar nuestro script aparece una función por defecto, la cual nos será
útil en un buen número de casos. Sería lo que se conoce como una función sobreescribible, que quiere decir que Unity
decide cuándo se va a llamar (la función Update, que es la que sale por defecto, es llamada por Unity cada frame) y
nosotros decidimos qué hará cuando sea llamada, introduciendo código en su interior.

Para poder editar y/o crear nuestros scripts, hemos de acceder al editor que viene con Unity, y la mejor manera para
hacerlo es hacer doble click sobre el nombre de nuestro script. Se nos abrirá (en windows) esto:
Vamos a aprovechar que tenemos el editor abierto para explicar otra característica de los scripts en Unity. Unity permite
asignar/modificar los valores a las variables globales desde la propia interfaz de usuario, y concretamente desde el
inspector, bien asignando esos valores "a mano", o bien arrastrando un objeto o componente del tipo de dicha variable
global. Para que podamos hacer eso es preciso, como digo, que la variable tenga un ámbito global, y para ello es
preciso que la misma se declare fuera de cualquier función.

Lo veremos mejor con un ejemplo: en el editor de scripts, encima de donde pone function Update, escribiremos lo
siguiente:
var camaraDeSeguridad : Camera;
camaraDeSeguridad.name = "Mi camara";
Debug.Log(camaraDeSeguridad.name);

Es un miniscript parecido al que veíamos en el capítulo anterior. En él declaramos una variable global (está fuera de
cualquier función) de tipo "Camera", y lo que hacemos a continuación es -vía herencia- asignarle un nombre a la
variable, que será "Mi camara". La tercera declaración de momento nos basta con saber que muestra en pantalla
(imprime) el valor de lo que esté entre sus paréntesis.

Guardamos el script (si no, no funcionará) en el editor de scipts. De vuelta a la interfaz de Unity, si seleccionamos el
nombre del script vemos que en el inspector se ha actualizado dicho script con el código que hemos introducido.

No obstante, nuestro script aún no es funcional, ya que no lo hemos vinculado a ningún objeto de nuestra escena.
Pensemos que los scripts por sí sólos no hacen nada, de la misma forma que cualquier asset que está en la
carpeta/vista del proyecto (por ejemplo, una textura) no participará de alguna manera en una escena de nuestro juego si
no lo arrastramos a esa escena, convirtiéndolo en parte de cualquier gameobject.

En consecuencia, vamos a vincular nuestro script a uno de los game objects de nuestra escena, y en concreto al cubo.
Para hacer eso meramente hemos de arrastrar el script desde la vista de proyecto hasta el propio objeto cubo, bien sea
sobre su nombre en la jerarquía, bien sobre la propia imagen del cubo en la escena.

Tras arrastrar el script, y con el cubo seleccionado en la jerarquia, en el inspector deberíamos estar viendo lo siguiente:

Obsérvese que en el inspector aparece ahora el nombre de nuestro script como una parte más del Cubo que tenemos
en la escena. Y daros cuenta de que la variable global queda expuesta en el inspector, de tal forma que no necesitamos
ir al editor de scripts para asignarle un valor. Si la variable hubiera sido, por ejemplo, de tipo float, podríamos haber
introducido o cambiado su valor meramente escbibiéndolo en el inspector. Si en cambio la variable fuera de tipo bool,
nos aparecería en el inspector con un checkbox al lado del nombre, bien marcado (true) o sin marcar (false) para que lo
cambiáramos a conveniencia.

Pero como en este caso la variable es de tipo Camera, lo único que podemos hacer para inicializarla es proveerla de un
objeto de ese tipo. Dado que en nuestra escena precisamente tenemos una cámara (la main camera), meramente
tendremos que arrastrarla desde la jerarquía hasta el lugar del inspector donde se consigna el valor de la variable,y que
ahora mismo pone "none".

Tras arrastrar la cámara, el inspector lucirá así:

Ya tenemos, por lo tanto, nuestro script asignado a un gameobject (nuestro cubo) y la única variable global inicializada
con otro object (la cámara principal). Procedamos ahora a darle al play y ver qué sucede.

Si nos fijamos, debajo de la ventana game nos aparecerá impreso el nombre que le asignamos a nuestra cámara.

Antes de acabar me gustaría que practicáramos un pequeño ejercicio que nos permitirá comprender las diferentes
maneras (correctas e incorrectas) en que se puede declarar una variable. Borremos todo el contenido de nuestro script
MiPrimerScript y tecleemos lo siguiente:
var sinTipoNiIni;
var conTipoSinIni : int;
var sinTipoConIni = 10;
var conTipoConIni : int = 10;
var aPlazos : int;
aPlazos = 10;
private var miSecreto : int;
var arrastrame : GameObject;
function Update() {
var enLaFuncion : int;
}

Salvamos el script y, con el Cubo seleccionado (recordemos que este script está todavía vinculado al cubo que tenemos
en la escena) echamos un vistazo al inspector. Podemos de manera empírica llegar a las siguientes conclusiones para
cada manera de declarar una variable:

La variable sinTipoNiIni, que como su nombre apunta hemos declarado sin indicar ni el tipo de datos que debería
contener, ni la hemos inicializado conjuntamente con la declaración, no aparece en el inspector, porque éste no tiene
forma de saber qué tipo de variable estamos declarando.

La variable conTipoSinIni sí es tenida en cuenta por el inspector, ya que aunque no la hemos inicializado, sí que hemos
indicado el tipo de dato que queremos almacenar en ella.

La variable sinTipoConIni sí aparece en el inspector, ya que aunque no hemos declarado el tipo que contendrá, al estar
inicializada permitimos a Unity deducir en base al valor en tipo de variable apropiado. Fijémonos que además de
aparecer la variable en el inspector, lo hace con el valor inicial que le hemos dado.

La variable conTipoConIni aparece obviamente en el inspector.

La variable aPlazos aparece en el inspector, pero sin inicializar. En cambio, si pulsamos play observaremos que
automáticamente le es asignado el valor 10 que le dimos en segunda declaración.

La variable miSecreto no es accesible desde el inspector porque es privada. De hecho, esa variable no podrá se
accedida desde ningún script distinto del que la contiene.

La variable arrastrame es recogida en el inspector, y al ser de un tipo propio de Unity, nos aparecerá con una flechita
diminuta en la derecha que nos indica que podemos importar del juego cualquier componente de ese tipo para asignar
un valor a la misma, bien usando el menú que emerge al clickar dicha flecha, bien vía arrastrar el objeto o componente
desde las carpetas de proyecto o jerarquía.

Por último, la variable enLaFunción no aparece en el inspector, ya que al ser declarada dentro de una función no tiene
carácter público.

Bueno, pues con esto ya debería quedar un poco más clara la manera en que las variables y los scripts son tratados en
Unity. Para la próxima lección empezaremos a analizar clase por clase de la API.

3. CLASE OBJECT (I)

Ha llegado el momento de empezar a pelearnos con las clases que componen la API de Unity, y tal como os comentaba
considero lo más conveniente empezar con la clase base.
Es sabido que en POO las clases base de las que heredan las demás suelen tener una funcionalidad bastante limitada,
cuando no inexistente, siendo su función más la de punto de partida de las que habrán de heredarla que la de instanciar
objetos de dicha clase. La clase Object, que es con la que vamos a empezar, no parece ser una excepción a esta regla.

La clase Object consta de dos variables, dos funciones y nueve funciones de clase (FC). Como sabréis, las funciones
de clase se diferencian de las funciones standard en que aquéllas no precisan ser llamadas por una instancia u objeto
de la clase, perteneciendo -como su nombre indica- a la clase y no a las instancias de dicha clase.

VARIABLES:

name:

var name : String

Bueno, esta variable ya la conocíamos. Hace referencia al nombre del objeto, y comparten ese nombre tanto el objeto
en sí como los componentes de dicho objeto. Esta variable nos sirve tanto para cambiar el nombre a nuestro objeto...
name = "Cualquier nombre";

...como para obtener el nombre de un objeto.

Probemos esta segunda opción con un ejemplo muy sencillo. Abrimos Unity y nos vamos a la escena que creamos en la
lección anterior. En la vista del proyecto, hagamos doble click en el script que llamamos miPrimerScript. Una vez se abre
el editor, borramos todo el contenido y:
function Update(){
Debug.Log(name);
}

Como vimos en la lección anterior, Debug.Log nos permite imprimir en pantalla el valor de lo que se halle entre
paréntesis. Salvamos el script (pero no cerramos el editor, meramente lo minimizamos)

Hay una cuestión que no quiero dejar pasar por alto. Lo que hemos modificado y guardado es el script que se halla en la
carpeta de proyecto. No obstante, al hacer eso automáticamente se modifican todas las copias de este script que
tengamos por ahí. En el caso que nos ocupa, recordaremos que a nuestro cubo le habíamos añadido una copia de este
script, que a estas alturas por lo expuesto ya habrá sido modificado.

Probémoslo. Si le damos al play, observaremos que bajo la ventana game aparece el nombre de nuestro cubo.

Todo bien hasta aquí. no obstante, en la definición del manual de referencia dice que todos los componentes del objeto
comparten el mismo nombre. Si echamos un vistazo a nuestro cubo en el inspector, observaremos que entre otros
componentes tiene uno llamado transform, así que vamos a hacer la prueba.

Sustituimos el "name" que habíamos escrito en el editor de scripts entre paréntesis por transform.name. O sea:
Debug.Log(transform.name);

Guardamos el script, le damos al play y....Voilá!, el componente transform comparte el nombre "cubo".

hideFlags:

var hideFlags : HideFlags

Esta variable no parece a priori tener una gran utilidad. Se conseguiría con su manipulación hacer que un determinado
objeto desaparezca o bien del inspector, o bien de la jerarquía, o impedir que dicho objeto se pueda editar, o no permitir
que un objeto sea salvado con la escena y en consecuencia destruído con la nueva escena que se cargue.

Para tal fin esta variable manera las diferentes opciones de una de las muchas enumeraciones que hay en Unity. Esta
enumeración, de nombre HideFlags, tiene las siguientes opciones:

HideInHierarchy____El objeto no aparecerá en la jerarquía.


HideInInspector____El objeto no aparecerá en el inspector.
DontSave_____El objeto no será salvado en la escena ni destruido en la nueva que se cargue.
NotEditable______El objeto no será editable en el inspector.
HideAndDontSave___Combinación de HideInHierarchy y DontSave.

Así, si por ejemplo quisiéramos (no se me ocurre muy bien para qué) que el cubo desapareciera del inspector mientras
se desarrolla el juego, le vincularíamos un script parecido a este:
var meEvaporo: GameObject;meEvaporo.hideFlags = HideFlags.HideInInspector;

Este script lo podríamos arrastrar a la cámara principal, y con posterioridad arrastrar nuestro cubo en la variable global
meEvaporo, que está esperando un gameObject. Observaremos que cuando le demos al play, el cubo desaparece del
inspector.

¿Por qué meEvaporo es de tipo GameObject?¿No debería tratarse de un Object, que es la clase que estamos
tratando?. Pues efectivamente, pero sucede que si sustituimos GameObject por Object en nuestro script,
comprobaremos que desaparece del inspector la variable meEvaporo, posiblemente por lo que decía al principio de la
lección de que la clase Object está pensasa como clase base para dotar de funcionalidad a las que la heredan, que
para ser instanciada ella misma.

En cualquier caso, como digo, no parece que hideFlags sea una variable que vayamos a usar mucho.

4. CLASE OBJECT (II)

FUNCIONES:

ToString:

function ToString () : String

Devuelve un string con el nombre del gameobject que hace la consulta.

GetInstanceID:

function GetInstanceID () : int

Unity asigna a cada objeto un identificador único. Esta función devuelve ese identificador.

Vamos a utilizar estas dos funciones en un mismo ejemplo. En el capítulo anterior teníamos vinculado nuestro script-
para-todo a la cámara principal. Abrimos el editor de scripts, borramos todo y tecleamos lo siguiente:
var todoSobreMi: GameObject;
Debug.Log("El nombre de este objeto es " + todoSobreMi.ToString() +
" y su id unica es " + todoSobreMi.GetInstanceID());

El script no merece mucha explicación. Asegurémonos de arrastrar el cubo a la variable todoSobreMi en el inspector. Al
pulsar el play deberia mostrarse el nombre e id del cubo, tal como se muestra en la imagen (obviamente, la id no tiene
por qué coincidir con la que os aparezca a vosotros)
FUNCIONES DE CLASE

operator bool, == y !=

Estas tres funciones méramente habilitan la posibilidad de establecer comparaciones de igualdad/desigualdad entre dos
objetos o componentes, o (en el caso del operador bool)si existe dicho objeto componente y tiene un valor distinto de
null.

Por ejemplo:
if (rigidbody)
Debug.Log("Este gameobject tiene vinculado un Rigidbody");

Que sería lo mismo que haber escrito


if (rigidbody != null)
Debug.Log("Este gameobject tiene vinculado un Rigidbody");

Instantiate:

static function Instantiate (original : Object, position : Vector3, rotation : Quaternion) : Object

Esta función lo que hace es clonar el objeto que le pasamos como primer parámetro, y devolver ese clon del objeto
original, ubicándolo en posición y rotación determinadas.

Observamos que el parámetro "position" es de tipo Vector3, que es una clase que no tardaremos mucho en estudiar. De
hecho, posiblemente sea la próxima que tratemos, ya que aunque no pertenece a la jerarquía de herencia que hemos
tomado como referencia para el orden de estudio, sí que va a aparecer el número suficiente de veces durante el tránsito
por dicha jerarquía como para no ocuparnos de ella de primeras.

Un Vector3 es, en pocas palabras, un punto en el espacio tridimensional. Dicho punto viene dado por las coordenadas
en el eje X (derecha-izquierda) Y (arriba-abajo) y Z (delante-detrás). Cada unidad se corresponde a una unidad en Unity
(valga la redundancia), que a su vez equivale a un metro. Por lo tanto para que un objeto se desplace un metro a la
derecha, escribiríamos en nuestro Vector3 (1,0,0).

Notemos que esto es ni más ni menos que lo que hace de manera más visual las tres variables position del transform
que aparecen en el inspector.

El otro parámetro de la función, rotation, es de tipo Quaternion. Quien sepa lo que es, felicidades, yo estuve varios días
peleándome con apuntes de matemáticas por la red y a lo sumo me hice una idea abstracta de que un cuaternión se
compone de tres números reales y uno imaginario, el cálculo de los cuales establece la rotación de un objeto. Si os sirve
de consuelo, y eso lo indica el propio manual de referencia oficial de Unity, es muy raro que trabajemos directamente
con cuaterniones, sino que lo haremos con funciones que nos simplifiquen el trabajo. Yo personalmente me quedo
solamente con la idea de que un cuaternión lo componen cuatro elementos y mide las rotaciones. Con eso es suficiente,
creedme.

Bueno, pues dicho esto a ver si somos capaces de instanciar/clonar un cubo.

Nos vamos a Unity, y le quitamos a la cámara principal el script que le habíamos colocado anteriormente. Para hacer
eso, con la cámara seleccionada en el inspector colocamos el ratón sobre el nombre del script, botón
derecho=>Remove Component.

Ahora, para no andar vinculando scripts a gameobjects que nada tienen que ver con él (como hicimos no hace mucho
con la cámara), vamos a crear un gameobject vacío cuya única utilidad sea contener nuestro script. Para ello nos vamos
al menú superior, y le damos a GameObject=>Create empty. A la carpeta que nos aparecerá en la jerarquía la
renombramos (F2) como PortaScripts.
Vale, ahora doble clic sobre nuestro script en la vista de Proyecto para abrir el editor de scipts, y escribimos:
var reproducete : GameObject;

Instantiate(reproducete, Vector3(2.0,0,0), Quaternion.identity);


Aquí lo que hemos hecho meramente es dejar una variable global "expuesta" (que pueda ser accesible desde el
inspector) y llamar a la función instantiate, de tal manera que clonará el Gameobject que le arrastremos y lo situará dos
unidades/metros a la derecha del objeto original. Quaternion.identity meramente significa que el objeto clonado tendrá
rotación cero, esto es, su transform rotation estará a 0,0,0 (salvo que el objeto clonado dependa a su vez de otro objeto,
en cuyo caso tendrá la misma rotación que el objeto padre, pero esto ya lo explicaremos cuando toque.

Salvamos el script y lo arrastramos hasta nuestro PortaScripts en la jerarquía. Vemos que en el inspector, con
PortaScripts seleccionado, aparece el script y la variable expuesta que llamamos reproducete. Arrastramos el cubo
hasta ella y ya podemos darle al play. Debería aparecernos un segundo cubo, tal que así:

Podemos observar alguna cosa más. En la jerarquía aparece -mientras se está reproduciendo la escena- un segundo
cubo, el cual Unity nos indica expresamente que es un clon del original. Si lo seleccionamos, podemos ver en su
transform en el inspector que la variable x (el eje derecha/izquierda) no está en el cero, sino en el dos, tal como le
habíamos indicado.

Probemos otra cosa. Detenemos la reproducción de la escena. Seleccionamos el cubo original, y le damos a los ejes X
e Y de transform rotation los valores 25 y 65 respectivamente.El cubo girará sobre dichos ejes. Démosle al play.

Podemos observar que el cubo clonado se muestra alineado con la cuadrícula global, y no con el cubo original. Esto es
lo que conseguimos con Quaternion.identity.

La función instantiate es muy usada para crear proyectiles, partículas en explosiones e incluso AI (inteligencia artificial)
para enemigos.

Cabe una segunda forma distinta para la función Instantiate, que es esta:

static function Instantiate (original : Object) : Object

Como podemos observar, aquí meramente estamos duplicando el objeto, se tal suerte que el clonado se ubicará en el
mismo lugar y con la misma rotación que el original. Podemos probarlo en nuestro script meramente eliminando el
segundo y tercer parámetro. Al darle al play, sabemos que ha aparecido un clon porque así lo indica la jerarquía, pero
no podemos distinguirlo porque está justo en el mismo sitio que el original y se están solapando.

Destroy:

static function Destroy (obj : Object, t : float = 0.0F) : void

Como su nombre indica, esta función borra del juego un gameobject, un componente o un asset. Tiene dos parámetros,
siendo el primero el elemento a borrar y el segundo el tiempo en segundos que tardará en borrarlo desde que se llame a
la función (si no se indica lo contrario, por defecto el parámetro indica cero segundos, esto es, la destrucción del objeto
es automática).

Hay una cuestión que igual ahora no se entiende muy bien, pero la dejo apuntada para tratarla más en profundidad en
otro momento: si en el parámetro obt colocamos un Componente, la función sólo eliminará ese componente, haciéndolo
desaparecer del Gameobject al que pertenezca. Pero si en cambio lo que le pasamos a la función es un gameobject, se
destruirá tanto el gameobject como todos los hijos de ese gameobject (esto es, todos los objetos y componentes e
inclusive otros gameobjects que dependan del eliminado en una relación de parentesco). Para tener un acercamiento
intuitivo a esta relación de dependencia, pensemos en un gameobject coche que tiene, entre otros, cuatro componentes
rueda y un gameobject conductor que hemos vinculado al gameobject coche para que allá donde se desplace el coche
vaya el conductor. Si un misil destruye un componente rueda usando la función destroy, sólo se destruirá la rueda. Si en
cambio lo que destruye es el gameobject coche, se destruirá tanto el vehículo como las ruedas como el conductor.
Vamos a probar la función Destroy. Para empezar devolvamos a nuestro cubo original a su rotación original (0,0,0).
Vamos a rehacer ahora nuestro sufrido script, que tenemos vinculado al cubo. En el editor de scripts modificamos
nuestro código así:
var reproducete : GameObject;
var nasioPaMorir : GameObject;

nasioPaMorir = Instantiate(reproducete, Vector3(2.0,0,0), Quaternion.identity);


Destroy (nasioPaMorir, 10.0);

Creo que es bastante fácil de entender. Lo que hemos hecho es añadir una segunda variable de tipo GameObject, que
no vamos a inicializar desde el inspector, porque lo que hará será almacenar el cubo clonado que devuelve la función
Instantiate. Inmediatamente es llamada la función Destroy, que borrará el elemento clonado que hemos almacenado en
nasioPaMorir pasados diez segundos.

Salvamos, le damos al play, contamos diez, y adiós clon.

DestroyImmediate:

static function DestroyImmediate (obj : Object, allowDestroyingAssets : boolean = false) : void

Esta función destruye inmediatamente un objeto, igual que la función anterior. Desde el manual de Unity se nos dice, no
obstante, que es preferible usar Destroy en lugar de esta función, ya que puede borrar más de lo que deseamos.

Por lo tanto, olvidémonos de DestroyImmediate.

5. CLASE OBJECT (y III)

Continuamos con las funciones de clase de la clase Object. Recordemos que las funciones de clase, como su nombre
indica, no van vinculadas a objetos o instancias de una clase, sino a la clase en sí, esto es, no se utiliza el operador
punto entre el nombre del objeto/instancia y el de la función, como sucedería en una función normal (o método) dentro
de una clase.

FindObjectOfType

static function FindObjectOfType (type : Type) : Object

Esta función devuelve el primer objeto activo que Unity encuentre que sea del tipo que le pasamos como parámetro.

Veámoslo en un ejemplo. Recuperamos una vez más nuestro script, borramos todo y tipeamos lo siguiente:
var dameAlgo : Object;

dameAlgo = FindObjectOfType(Camera);
Debug.Log("Deberia haber encontrado 1 " + dameAlgo);

Salvamos, le damos al play y observamos que Unity ha encontrado nuestra cámara principal.

Nos advierte el manual de referencia que esta función puede ser un poco lenta y costosa en términos de rendimiento,
por lo que se recomienda no usarla a su vez como parte de una función que se actualice cada frame (como por ejemplo
la función Update, que estudiaremos a no mucho tardar)
FindObjectsOfType

static function FindObjectsOfType (type : Type) : Object[]

No hace falta ser muy perspicaz para darse cuenta de que esta función es idéntica a la anterior, con la diferencia de que
aquí lo que se devuelve no es el primer objeto activo de tipo Type, sino que se devuelven en un array todos los objetos
activos cuyo tipo coincide con el que solicitamos.

Veamos la diferencia rehaciendo nuestro script:


var dameAlgo : Object[];
var chivato : String;

dameAlgo = FindObjectsOfType(GameObject);
chivato = "He encontrado " + dameAlgo.Length + " objetos: ";

for(var contador = 0; contador < dameAlgo.Length; contador++)


chivato += dameAlgo[contador].name + " ";

Debug.Log(chivato);
Lo que hemos hecho aquí es lo siguiente: primero hemos reconvertido nuestra variable dameAlgo en un array de
Objects. Declaramos después una variable de tipo string para que vaya recopilando toda la información que al final
imprimiremos. Inicializamos luego nuestro array con todos los objetos de tipo GameObject que Unity encuentre en
nuestra escena. A partir de ahí,montamos un bucle for para ir añadiendo a nuestro string "chivato" el nombre de todos
los objetos encontrados. Finalmente, imprimimos. Si todo ha salido bien, el resultado tendría que ser este al darle al
play: DontDestroyOnLoad static function DontDestroyOnLoad (target : Object) : void
Con esta función conseguimos que el objeto que colocamos como parámetro no se destruya cuando se cargue una
nueva escena. Por defecto, cuando el jugador cambia de escena o nivel, todos los objetos de la escena que abandona
se destruyen antes de crear los que corresponden al nuevo nivel. Con esta función, conseguimos que los objetos que
queramos pervivan al cambio de escena.

Dado que aún no sabemos cargar escenas nuevas, no vamos a realizar ningún ejemplo. Ya lo haremos cuando
expliquemos las oporunas funciones.

Y con esto acabamos el análisis de nuestra primera clase. Espero que os hayan quedado los conceptos más o menos
claros (al principio cuesta un poquillo, así que no desesperéis). Y por supuesto, si hay alguna duda, queja (educada) o
corrección que hacer, sentiros libres de comentar.

6. ESTRUCTURA VECTOR3 (I)

Lo primero que cabría decir de Vector3 es que, a diferencia de Object, por ejemplo, no es en sí una clase, sino una
estructura. En la corta historia de la programación las estructuras surgieron antes de las clases, en los viejos tiempos de
la programación procedimental, y originalmente eran poco más que una colección de variables que guardaban algún
tipo de relación entre sí. Hoy por hoy, no obstante, no hay ninguna diferencia entre una estructura y una clase.

Aunque Vector3 no está contenido dentro del esquema de herencia principal de Unity, es una clase imprescindible para
la creación de cualquier juego, ya que precisamente es la que permite que representemos puntos y vectores en el
espacio 3D, y por ende la que nos permite trabajar con distancias y desplazamientos.

El concepto de punto, siendo sencillo cuando se entiende, a veces provoca en los recién llegados no pocas
confusiones. Quizás lo mejor para captar el concepto de punto en 3D previamente tengamos que aprehender el
concepto de punto en 2D. Vamos a intentar explicarlo de una manera llana, y ruego me disculpen los que ya tenían claro
el concepto:

Delante vuestro posiblemente tengáis la pantalla de vuestro ordenador o del dispositivo electrónico desde el que estáis
viendo estas líneas. En algún lugar de la pantalla estará seguramente el cursor del ratón, apuntando a algún punto
(valga la redundancia) determinado de ésta. Si quisiéramos explicarle a alguien que no está viendo la pantalla en qué
lugar está el cursor, o si meramente quisiéramos dejar por escrito de una manera fiable en qué punto exacto está ahora
nuestro cursor, una buena manera de hacerlo sería decir: el cursor está X centímetros (digamos por ejemplo 16) a la
derecha del borde izquierdo de la pantalla e Y centímetros por debajo del borde superior de la pantalla (pongamos 20).
Así, si por ejemplo convenimos que la esquina superior izquierda de la pantalla es el punto 0,0, nuestro cursor en el
mundo 2D de la pantalla estará situado en el punto 16,20.

Por lo tanto, observamos que para determinar la situación exacta de un punto necesitamos a su vez otro punto que nos
sirva de referencia (Y es importante retener este concepto, sobre el que no tardaremos en volver). Así, para saber
dónde está el punto al que señala el ratón necesitamos primero compararlo con el punto que tomamos como referencia,
y la resta entre ambos puntos es la distancia que los separa, que es este caso ya no sería un punto sino un vector,
aunque se represente igual.

Lo mismo es aplicable a las tres dimensiones, aunque a nivel intuitivo nos cueste un poco más de pillarlo. Meramente
entendamos que si nuestra pantalla de ordenador no fuera plana, sino por ejemplo un holograma, para localizar el
objetivo de nuestro ratón tendríamos que añadir un nuevo punto (o eje) Z, para calcular la profundidad con relación a un
punto de origen (que podría ser la esquina superior izquierda más cercana a nosotros de la pantalla holográfica en 3d)

La estructura Vector3, pues, nos permite averiguar en qué punto se halla cada gameobject, cambiar su posición,
calcular las distancias entre ellos, etc.

Es conveniente advertir que el tipo Vector3 no es la única clase/estructura que maneja lo relativo a puntos y
desplazamientos. Cumplen funcionalidades parecidas (que no iguales) la clase Quaternion y la Matrix4X4. Existen,
antes de que nos alarmemos en exceso, funciones que permiten convertir, por ejemplo, un Vector3 en un Quaternion (y
viceversa), así que de momento no nos obsesionemos más de la cuenta con conceptos que nos suenen extraños.

Y tras esta extensa introducción, en el próximo capítulo empezamos con la estructura en sí.

7. ESTRUCTURA VECTOR3 (II)

CONSTRUCTOR:

1)static function Vector3 (x : float, y : float, z : float) : Vector3

Como sabemos de nuestros conocimientos de programación orientada a objetos, el constructor es una función especial
cuyo cometido es crear de manera adecuada las instancias/objetos de una determinada clase o estructura. La función
constructora se caracteriza por tener el mismo nombre que la clase.

Tal como vemos en el prototipo de nuestro constructor, le hemos de pasar al mismo los tres componentes de nuestro
punto 3D, referidos al eje horizontal, vertical y de profundidad respectivamente, en formato de punto flotante. Así:
var miPunto = Vector3(2.0, 5.0, 4.8);

referenciamos un punto que con respecto al origen se desplazará dos unidades a la derecha, cinco hacia arriba y 4,8
hacia el fondo.

Vale, pero ¿cuál es el origen?, se preguntará alguien.

Pues depende, tendremos que responder. Si un objeto no depende de otro, entendemos por origen el punto 0,0,0 de las
coordenadas globales (que es donde deberíamos tener nuestro cubo) y desde ese punto origen se calcula el
desplazamiento, pero si un objeto depende de otro (recordemos el ejemplo de la rueda y el coche) caben para ese
objeto dos orígenes o coordenadas diferentes: las globales (distancia de la rueda respecto del eje 0,0,0 del caso
anterior) y las locales (referidas al objeto del que depende)

2)static function Vector3 (x : float, y : float) : Vector3


Como vemos, hay una segunda función constructora que no toma en cuenta el eje Z (la profundidad) asumiendo que
ésta es cero. Por tanto, si a la función constructora Vector3 sólo le pasamos dos parámetros, automáticamente será
llamada esta segunda versión del constructor.

VARIABLES:

x, y, z:

Estas variables se refieren a los componentes X, Y y Z del vector, respectivamente.

this:

var this[index : int] : float

Esta es otra forma para acceder a los componentes X, Y y Z del vector, siendo los respectivos índices de cada
componente 0, 1 y 2.

Así, estas dos declaraciones son equivalentes:


var miVector : Vector3;
miVector.x = 3.0;
miVector[0] = 3.0; //Equivale a la anterior

magnitude:

var magnitude : float

Esta variable devuelve la longitud o tamaño de un vector. Habíamos hablado previamente de que un vector3 contiene el
número de unidades que lo separan del origen dado en los ejes X, Y y Z. Obviamente, cuanta mayor es la distancia
entre nuestro Vector3 y el punto de origen, mayor la longitud del vector.

La longitud del vector equivale a la raiz cuadrada de (x*x+y*y+z*z).

Soy consciente de que a estas alturas la utilidad de funciones como estas es difícil de apreciar. Con un poco de
paciencia, y cuando las combinemos con otras clases, veremos el partido que se le puede sacar a todo esto.

sqrMagnitude:

var sqrMagnitude : float

Si de lo que se trata es de comparar dos vectores, es preferible usar esta función que la precedente, ya que aunque
parezca antiintuitivo, Unity calcula con más rapidez los cuadrados de las magnitudes que las magnitudes en sí.

normalized:

var normalized : Vector3

Esta variable devuelve un vector con la misma dirección que el original, pero con magnitud 1. Es importante constatar
que el vector original permanece inalterado y lo que se devuelve es meramente una copia que conserva la dirección de
dicho original, aunque con la longitud alterada. Si en lugar de normalizar una copia quisiéramos hacer lo propio con el
original, entonces tendríamos que usar la función Normalize, que veremos en breve.

Decir también que si el vector es demasiado pequeño para ser normalizado, lo que se devuelve es un vector a cero.

VARIABLES DE CLASE:

zero:

static var zero : Vector3

Es meramente un atajo para poner un vector a cero.


transform.position = Vector3.zero;
// Es lo mismo que escribir
transform.position = Vector3(0,0,0);

one, forward, up y right:

Son respectivamente atajos para escribir Vector3(1,1,1), Vector3(0,0,1), Vector3(0,1,0) y Vector3(1,0,0).

Vamos a ver algunos de los conceptos de esta lección en un ejemplo. Abrimos nuestro script favorito y escribimos:
var unObjeto : GameObject;
var desplazamiento : Vector3;
var magnitud : int = 0;
desplazamiento = Vector3.right;
for(var contador = 0; contador < 3; contador++)
{
unObjeto.transform.position += desplazamiento;
magnitud += desplazamiento.magnitude;
Debug.Log("Longitud del Vector del cubo: " + magnitud);
}

Salvamos, arrastramos nuestro cubo para que se convierta en la variable unObjeto y le damos al play. Nuestro cubo se
mueve tres unidades a la derecha y bajo la ventana Game aparece la magnitud de desplazamiento del vector.

El script funciona de la siguiente forma: Además de la variable que sostendrá a nuestro cubo, creamos una variable de
tipo Vecto3 para poder almacenar a modo de impulsos maniobras de desplazamiento de una unidad a la derecha. Ese
input a la derecha se lo daremos tres veces mediante un bucle for, y cada magnitud individual (que obviamente vale 1
cada vez) se acumula en una variable de tipo int que hemos creado a tal efecto.

8. ESTRUCTURA VECTOR3(III)
Scale:

function Scale (scale : Vector3) : void

Multiplica el Vector3 que llama la función por el Vector3 que le pasamos a dicha función como parámetro.

Lo veremos mejor con un ejemplo. Vamos a darle un meneo de nuevo a nuestro script. Tecleamos:
var unObjeto : GameObject;
var miVector : Vector3;
miVector = unObjeto.transform.position = Vector3(2.0, 1.0, 3.0);
Debug.Log("Magnitud inicial del cubo " +miVector.magnitude);
miVector.Scale(Vector3(3.0, 1.5, 2.0));
unObjeto.transform.position = miVector;
Debug.Log("Magnitud final del cubo " +miVector.magnitude);

Antes que nada vamos a explicar lo que pretendemos hacer: dejamos expuesta la variable unObjeto para arrastrar con
posterioridad nuestro cubo desde el inspector. Asimismo declaramos una variable para sostener el valor original y
modificado para un vector3.

Aunque aún no lo hemos estudiado, a nadie sorprenderá que los diferentes gameobjects que ocupan la escena han de
ir ineludiblemente vinculados a un objeto transform, el cual a su vez es el que sirve para fijar en qué punto del espacio
3D se halla el gameobject. Así, para obtener o indicar la posición de un gameobject en el espacio usaríamos la sintaxis
gameobject.transform.position. Se da la circunstancia que la variable position de la clase transform es de tipo Vector3,
por lo que nos sirve para nuestro ejemplo.

Hacemos una iniciación doble, de tal manera que damos unos valores a nuestro Vector3 miVector, que a su vez tendrá
el mismo valor que le damos a la posición actual del cubo. Obtenemos la magnitud de dicho vector y la imprimimos.

Luego usamos la función Scale, y la magnitud del vector modificado es impreso de nuevo, y a la par el cubo se desplaza
por segunda vez.

Obtendremos al darle al play un resultado como este

Si nos fijamos en la parte inferior de la ventana Game, sólo aparece impresa la magnitud final del vector de nuestro
cubo. Para ver también el valor inicial, hemos de hacer un click sobre dicho texto impreso, de tal suerte que nos
aparecerá una ventana emergente con ambos valores, tal como mostramos:

La función Scale tiene un segundo prototipo, que es el que sigue

static function Scale (a : Vector3, b : Vector3) : Vector3

El fin último es, al igual que en la función homónima, multiplicar dos vectores. La diferencia es que aquí pasamos
expresamente los dos vectores que queremos multiplicar entre sí. También difieren ambas funciones en que la primera
no devuelve ningún valor (el cambio resultante de la multiplicación es directamente asignado al objeto que la llama) y
esta en cambio sí devuelve un Vector3 con el resultado, que podemos por lo tanto asignar a una variable.

Normalize:

function Normalize () : void


Esta función hace que el Vector3 que la llama pase a tener una magnitude de 1, conservando, eso sí, la misma
dirección. Como decimos, esta función cambia el vector que la llama; si lo que pretendemos es conservar sin cambios el
vector corriente y crear un nuevo vector normalizado, hemos de usar -como ya aprendimos en el capítulo anterior- la
variable normalized.

ToString:

function ToString () : String

Devuelve nuestro vector convertido en un string y formateado.

Para ver como funciona, añadamos al final del script que usamos en el ejemplo anterior la siguiente línea:
Debug.Log("Mi vector como string formateado: " + miVector.ToString());

Veremos que nos aparece el vector resultado de la multiplicación del anterior ejemplo convertido en un string que
podemos imprimir.

Dejamos para el próximo capítulo las dos funciones de clase que nos restan para acabar la explicación de la estructura
Vector3.

9. ESTRUCTURA VECTOR3 (y IV)

FUNCIONES DE CLASE:

Lerp:

static function Lerp (from : Vector3, to : Vector3, t : float) : Vector3

Esta función interpola linealmente entre dos vectores, desde from hacia to en el valor t.

Vamos a intentar explicarlo de la manera más clara posible, ya que se trata de una función importante: Lerp realiza una
línea imaginaria que va del punto que le pasamos como primer parámetro (from) al punto que le pasamos como
segundo parámetro (to). Esa línea imaginaria es entonces una recta con un origen y un final, y entre ambos extremos
tiene muchos puntos intermedios. Pensemos por ejemplo en una regla, para tener más o menos claro el concepto.
Pongamos que al punto de origen (from) le asignamos el valor 0, y al punto final (to) le damos el valor 1. En ese caso, el
punto que está en la mitad de nuestra recta imaginaria es evidente que valdrá 0,5.

Con esto claro planteemos ahora el mismo asunto pero al revés. Tenemos el punto origen y el punto final y queremos
saber dónde está la mitad exacta entre esos dos puntos. Lo que haríamos es llamar a la función Lerp, asignar a los
parámetros primero y segundo respectivamente las coordenadas de origen y final y darle al tercer parámetro (que en el
prototipo recibe el nombre de "t", y es de tipo float) el valor 0.5. El valor retornado por la función se corresponderá con el
punto situado en el centro de los dos extremos dados.

El parámetro t puede recibir valores entre 0.0 (que coincide con el origen) hasta 1.0 (que coincide con el final).

Vamos a practicar.
En Unity, le quitamos provisionalmente a nuestro gameobject PortaScripts el script, para que no nos dé errores en lo
que a continuación haremos.

Acto seguido vamos al menú gameobject=> creater other => capsule. Colocamos la cápsula a un valor -3 en el eje x del
transform en el inspector. Renombramos a esta cápsula y la llamamos Origen.

Creamos luego otra cápsula, y en la coordenada x de su transform la pondremos en 3. La rebautizamos como Fin (Final
es una palabra reservada)

Es preferible que tengamos la escena enfocada desde la vista superior (lo que conseguimos haciendo click en el eje Y
del gizmo)

Ahora hacemos doble click en el script (que aunque hemos borrado del Portascripts sigue estando en el Proyecto), y
tecleamos:
var principio : Transform;
var fin : Transform;
function Update () {
transform.position = Vector3.Lerp(principio.position, fin.position, 0.3);
}

Salvamos el script y lo arrastramos a nuestro cubo. Acto seguido seleccionamos el cubo y en el inspector nos
aparecerán nuestras dos variables. Arrastramos origen a principio y destino a fin.

Si todo va bien, nuestro cubo debiera situarse a tres décimas partes de la línea hipotética que separa las cápsulas,
contando desde la que hemos dado en llamar origen. ¿Lo probamos?

Bien. Parece que funciona, pero podemos ir un poco más allá. Para ello vamos a recurrir a una clase que aún no hemos
estudiado, que es la clase Time, y concretamente a la variable time de la clase Time. Time.time es una variable que
representa los segundos transcurridos desde que el juego se inicia. Por lo tanto, la variable vale cero al iniciarse el
juego, 1 un segundo después, etc.

Con esto en mente, vamos a nuestro último script y dentro de la función Lerp sustituimos el 0.3 por Time.time, salvamos
y le damos al play.

Efectivamente, nuestro cubo empieza pegado al origen (el parámetro t, representado por Time.time vale cero) y va
desplazándose hacia el fin hasta que t vale 1, o sea, hasta que transcurre un segundo.

Sabiendo esto, podemos hacer que el trayecto de nuestro cubo dure por ejemplo diez segundos en lugar de uno.
Conseguimos esto meramente multiplicando Time.time * 0.1.

Podemos, dándole una vuelta más de tuerca al asunto, crear una variable expuesta (susceptible de ser accesada desde
el inspector) de tipo float, que el usuario pueda modificar, y que al ser multiplicada por Time.time haga que nuestro cubo
viaje más rápido o más lento.

Slerp:

static function Slerp (from : Vector3, to : Vector3, t : float) : Vector3

Esta función es similar a la anterior, con la particularidad de que en lugar de interpolar en línea recta lo hace en forma
esférica. Para probarla no tienes más que sustituir el término "Lerp" del script anterior por "Slerp", y poner el cubo en
órbita. Si tienes tiempo y ganas, ubica los gameobjects Origen y Destino en otras posiciones a través de sus respectivos
transforms en el inspector (desplázalos también en sus ejes Y y Z).
10. CLASE TRANSFORM (I)

Vamos a dedicar los próximos capítulos a estudiar una de las clases más importantes de la API de Unity. La clase
Transform, como vemos en el gráfico, hereda de Component, que a su vez deriva de Object, y por lo tanto esta clase
tiene como propias las variables y funciones de las dos clases de las que hereda.

La pregunta parece obligada: si Transform hereda de Component, ¿por qué no estudiamos la clase Component
primero?

No lo hacemos porque la clase Component, en el fondo, es poco más que una recopilación del resto de clases. Lo
veremos mejor si recuperamos el gráfico general de clases que vimos en su día:

Si nos fijamos con atención, observaremos que la mayoría de las variables de la Clase component son a su vez
instancias (objetos) de un buen número de las otras clases, algunas de las cuales incluso -como la propia clase
Transform- derivan de ésta. Nos veríamos pues, caso de estudiar primero la clase Component, obligados a explicar lo
que es un transform, un rigidbody, una camera, etc, antes de explicar las clases que los desarrollan.

Por lo tanto, para no liarnos en exceso, dejaremos la clase Component para bastante más adelante.

Bien. ¿Qué es un transform?.

Posiblemente muchos de los que ya hayan trasteado con la interface de Unity asimilarán esta palabra a la sección
homónima del inspector. Efectivamente, si seleccionamos cualquier gameobject de la escena, automáticamente el
inspector nos muestra en la sección transform los valores de posición, rotación y escala de dicho gameobject.

No puede existir un gameobject en la escena sin su propio transform. Si nos fijamos, hasta nuestro PortaScripts tiene
vinculado un transform, pese a que como mero contenedor de scripts no es renderizado en la escena, y por lo tanto es
invisible. En consecuencia, todo gameobject en nuestra escena está situado en un lugar determinado de ésta (position),
con una determinada inclinación (rotation) y le es aplicada una determinada escala comparativa de tamaño (scale).

Con esto en mente, empecemos.

VARIABLES:

position:

var position : Vector3

Esta variable nos indica en qué punto del espacio global se ubica nuestro transform (y por ende el gameobject al que el
mismo va vinculado). Recordemos que el espacio global es el que hace referencia a las coordenadas de la escena, y no
a las de un determinado objeto. Si quisiéramos consultar/indicar el lugar del espacio local de un objeto, utilizaríamos la
variable localPosition.

La variable position es de tipo Vector3, y nos permite tanto obtener los datos de posición del transform como
modificarlos para darle una nueva ubicación. Por ejemplo, si quisiéramos ubicar un objeto en el centro del espacio
global, haríamos:
elObjetoQueSea.transform.position = Vector3(0, 0, 0);

Podemos asimismo acceder a uno sólo de los ejes de la posición, tal como sigue:
Debug.Log(elObjetoQueSea.transform.position.x);
Lo cual nos permitiría imprimir la posición que tiene nuestro transform referida al eje horizontal.

localPosition:

var localPosition : Vector3

Como adelantábamos hace un momento, localPosition indica la posición del transform al que dicha variable pertenece,
calculada en términos relativos al transform del que aquél depende.

Parece más difícil de lo que es en realidad, tal como vamos a demostrar con un ejemplo sencillo:

Nos vamos a Unity. Si hemos guardado nuestra última sesión, debemos tener en nuestra escena, aparte de nuestro
viejo amigo el cubo, dos cápsulas de nombre origen y destino. Eliminamos Destino. Si por lo que sea no hubiéramos
conservado las cápsulas, creamos un gameobject capsule nueva. Esa cápsula (u Origen, si guardamos la última
escena)la ubicamos desde el inspector en la posición (3, 4, 2)

Resumiendo, tenemos en la escena(o debiéramos tener) el cubo en las coordenadas de posición (0,0,0) y una cápsula
en la posición (3,4,2). Ninguno de estos gameobjects es hijo a su vez de otro, por lo que están y se mueven en
coordenadas globales.

Abrimos nuestro script y escribimos:


transform.position = Vector3(-2,-1,-1);

Salvamos y arrastramos el script a nuestro cubo. Al darle al play, vemos que éste se desplaza respecto la posición que
ocupaba en las coordenadas globales (el centro justo) dos unidades a la izquierda, una hacia abajo y una hacia delante.
Al darle al pause, nuestro cubo vuelve a su lugar original, en 0,0,0 (es importante retener esto)

Vamos ahora a, en la Jerarquía, arrastrar el gameobject cubo y soltarlo dentro de gameobject cápsula (u Origen).
Vemos que el cubo mantiene su posición en la vista de escena, pero si lo seleccionamos (está ahora contenido en la
cápsula), sus coordenadas en el transform del inspector son ahora las inversas que las que le dimos al gameobject
cápsula. Esto es así porque ahora para el cubo sus coordenadas de posición no dependen ya del espacio global, sino
del local de la cápsula de la cual depende. Para el cubo la coordenada 0,0,0 no es ya el centro de la escena, sino el
centro del gameobject capsula, y dado que con respecto a la cápsula el cubo está desplazado tres unidades a la
izquierda, cuatro hacia abajo y dos hacia delante, son las que muestra.

El hecho de que el cubo dependa de la cápsula o dicho en terminología Unity, el cubo sea hijo de la cápsula, no implica
que no pueda seguir siendo localizado/ubicado en coordenadas globales. De hecho, si le damos al play de nuevo,
habida cuenta de que estamos utilizando en nuestro script la variable position (global), el cubo se seguirá desplazando
al mismo sitio al que lo hacía cuando aún no dependía de la cápsula.

Pero, obviamente, al tener ahora un padre, le podemos aplicar ahora al cubo la variable localPosition, y así podemos
modificar el script como sigue:
transform.localPosition = Vector3(-2,-1,-1);

Vemos ahora que el movimiento del cubo lo es en relación con la posición que ocupa papá capsula (o para ser más
precisos el transform de papá cápsula). Probad si no lo veis claro a pasarle un vector (0,0,0) a nuestro script en lugar
del que hemos escrito antes.

Si le aplicáramos esta variable localPosition a un transform sin padre, dicho transform se movería en el espacio global,
tal como si le hubiéramos aplicado position y no localPosition.

11. CLASE TRANSFORM (II)


eulerAngles:

var eulerAngles : Vector3

Esta variable indica la rotación del transform en grados. Se corresponde plenamente con los valores que aparecen en el
apartado rotation de cada transform en el inspector. Los valores de esta variable vienen dados, al igual que los de
position, en formato Vector3. Lo que sucede es que un valor de (90,0,0) referido a la posición implica que nuestro
transform se desplace 90 unidades a la derecha, mientras que los mismos valores relativos a la rotación implicarían que
nuestro transform gire 90 grados (el equivalente a un cuarto de vuelta) sobre el eje X.

Al igual que con la variable position, eulerAngles nos permite consultar individualmente la rotación de nuestro transform
sobre un determinado eje. Pero si lo que queremos no es consultar sino cambiar la rotación, deberemos indicar los tres
valores vía un Vector3, y Unity los convertirá entonces en los valores que él usa internamente.

Porque -esto no sé si lo he dicho antes- Unity trabaja internamente con Quaterniones, por motivos de rendimiento y
exactitud, mientras que a los humanos nos resulta más sencillo hacerlo con grados Euler, así que en la práctica esisten
unos "atributos de traducción" que nos permite pasar de quaterniones a grados y viceversa, a fin de que hombres y
máquina sigan trabajando con los elementos que mejor se adapten a sus capacidades.

Al igual que con position, existe una variable eulerAngles para funcionar con valores globales (la que estamos
estudiando) y otra localEulerAngles que trabaja con valores locales (dependientes del padre del transform que rota)

A esta variable sólo podemos asignarle valores exactos, no de incremento, ya que fallaría pasados los 360º. Para
incrementar una rotación de esta forma, tenemos que usar la función Transform.rotate.

Usemos un ejemplo muy simple. Antes arrastremos nuestro cubo en la jerarquía fuera de la cápsula, para
desemparentarlo. Y rehacemos por enésima vez nuestro script (que debería continua vinculado al cubo)
transform.eulerAngles = Vector3(60,0,0);

Podéis ver que nuestro cubo ha girado un 60 grados sobre el eje X, entendido éste como un alambre invisible que cruza
de manera horizontal la escena (por decirlo de alguna manera)

Como colofón, añadir que Unity automáticamente convierte los angulos Euler que le pasamos al transform en los
valores usados para la rotación almacenada en Transform.rotation.

localEulerAngles:

var localEulerAngles : Vector3

Aquí no deberíamos necesitar grandes explicaciones: localEulerAngles se refiere a la rotación en grados de un


transform con relación a la que tiene el transform del cual depende en una relación hijo/padre.

Al igual que con la variable anterior, Unity automáticamente convierte los angulos Euler en los valores usados para la
rotación almacenada en Transform.localRotation.

rotation:

var rotation : Quaternion

Es la variable que contiene la rotación del transform en el mundo global almacenada en quaterniones.
Como ya hemos dicho, Unity almacena internamente todas las rotaciones en quaterniones, lo cual no quiere decir que
nosotros tengamos que trabajar directamente con ellos. Por norma general, o bien trabajaremos con grados Euler que
luego se convertirán implícita o explicitamente en quaterniones, o bien a lo sumo usaremos alguna función que nos
permita encapsular las operaciones con quaterniones.

localRotation:

var localRotation : Quaternion

Obviamente, esta variable indica la rotación en quaterniones de un transform relativa a la rotación de su padre

right, up y forward:

var right : Vector3


var up : Vector3
var forward : Vector3

Estas variables hacen referencia respectivamente al eje X(derecha/izquierda), Y (arriba/abajo) y Z(delante/detrás) de las
coordenadas globales del transform. No las hemos de confundir con las variables del mismo nombre de la estructura
Vector3. Así, Vector3.up es un atajo que equivale a Vector3(0,1,0), esto es, implica un movimiento de una unidad hacia
arriba. En cambio, transform.up meramente hace referencia al eje arriba/abajo, pero no implica movimiento. Para mover
un transform por esta vía tendríamos que hacer algo como esto:

En Unity eliminamos el script que tenemos vinculado a nuestro cubo. En Proyecto abrimos nuestro script y tecleamos:
var unObjeto : GameObject;
unObjeto.transform.position = transform.right*10;
unObjeto.transform.eulerAngles = transform.up*45;

Arrastramos nuestro script tras salvarlo a PortaScripts en la Jerarquía y con Portascripts seleccionado arrastramos el
cubo a la variable expuesta. Play.

Efectivamente, el cubo se desplaza 10 unidades a la derecha y 45 grados sobre el eje Y.

12. CLASE TRANSFORM (III)

Vamos a explicar las variables que nos quedan en esta lección.

localScale:

var localScale : Vector3

Como su nombre indica, es una variable de tipo Vector3 que permite consultar o establecer la escala de un transform en
relación con la de su padre. Obviamente, un transform con esta variable en 0,0,0 tendrá la misma escala que su padre.
parent:

var parent : Transform

Hace referencia al padre del transform (que es obviamente otro transform), y a través de esta variable se puede
consultar y/o cambiar dicho padre.

Veamos primero de qué forma podemos consultar valores relativos al padre de nuestro transform: Nos vamos a Unity,
arrastramos en la Jerarquía el cubo dentro de la cápsula, y a PortaScripts le borramos el script para que no nos dé
errores. En Proyecto le damos doble click a miPrimerScript y escribimos lo que sigue:
Debug.Log(transform.parent.gameObject.name);

Arrastramos tras salvar el script dentro del cubo (que está dentro de la cápsula). Play.

Aparece el nombre de la cápsula,que es lo que pretendíamos. Fijaros cómo a través del operador punto le decimos al
Unity (leyendo de derecha a izquierda) que busque el nombre del gameobject al que pertenece el transform que es el
padre del transform del gameobject que hace la consulta (al que va vinculado el script) y lo imprima en pantalla.

Vamos ahora a darle un padre nuevo a nuestro cubo. Abrimos el script y, sin borrarla, desplazamos la declaración que
habíamos escrito un par de renglones hacia abajo, y encima escribimos lo siguiente:
var nuevoPadre : Transform;
transform.parent = nuevoPadre;
Debug.Log(transform.parent.gameObject.name);//Esto ya lo teníamos escrito

Salvamos, y seleccionamos el cubo para que en el inspector quede expuesta la variable nuevoPadre. Arrastramos hasta
ella la cámara principal desde la jerarquía y le damos al play. Nuestro cubo ha cambiado de padre.

Pensemos, llegados a este punto, que nuestro transform hijo tiene una posición, rotación y escala relativa con respecto
a su padre. Si cambiamos su padre, éstas obviamente cambiarán.

root:

var root : Transform

Esta variable hace referencia al transform más alto de la jerarquía, por lo que guarda muchas similitudes con parent,
pero referida al “parent de los parent” del que depende nuestro transform. Si éste tiene un padre sin padre, el resultado
equivaldrá a parent. Si su padre tiene un padre que tiene un padre que tiene un padre, la variable irá escalando hasta
llegar al último padre y devolverá su valor (o nos permitirá modificar dicho valor). Para el caso de que el objeto que
invoca esta variable no tenga padre, lo que se devuelve no es null, sino el propio transform que la invoca.

childCount:

var childCount : int

Es meramente una variable de tipo int que indica el número de hijos que tiene un transform.

lossyScale:

var lossyScale : Vector3


Es esta una variable de tipo Vector3 que representa la escala global del objeto. Es una variable de sólo lectura, por lo
que obviamente no podemos modificar la escala global (la local ya vimos que sí con localScale) asignando valores a
esta variable.

Y con esto acabamos las variables y nos lanzamos a por las funciones, que también son unas cuantas.

13. CLASE TRANSFORM (IV)

FUNCIONES:

Translate:

function Translate (translation : Vector3, relativeTo : Space = Space.Self) : void

Esta función nos permite mover el transform en la dirección y distancia indicados en el parámetro de tipo Vector3
traslation. El segundo parámetro nos permite decidir si el transform se moverá según sus propias coordenadas (locales)
o lo hará en base al espacio global. Por defecto, si no indicamos el segundo parámetro, Unity entiende que el transform
se moverá según sus propias coordenadas (Space.Self).

Desarrollemos esto con un ejemplo:

Para empezar, desemparentamos nuestro cubo respecto de la cápsula, arrastrándolo a un espacio vacío de la jerarquía.
Acto seguido, con el cubo seleccionado, en el inspector le asignamos a la coordenada Y de rotación un valor de 35.
Abrimos el script que tenemos asignado al cubo y tecleamos:
transform.Translate(Vector3.forward * 5);

Salvamos y presionamos play. El cubo avanza cinco unidades en su eje Y, y recalcamos lo de "su" eje, ya que al no
añadirle un segundo parámetro que diga lo contrario, se mueve según sus coordenadas locales.

Para ver la diferencia, añadamos como segundo parámetro a la función la variable World de la enumeración Space,
para que sustituya al parámetro por defecto Space.Self:
transform.Translate(Vector3.forward * 5, Space.World);

function Translate (x : float, y : float, z : float, relativeTo : Space = Space.Self) : void

Existe un segundo prototipo para la función Translate. Como se puede comprobar, se diferencia del primero en que en
lugar de pasar como parámetro para establecer la dirección y longitud del movimiento un Vector3, se pasa cada eje
como un float independiente.

function Translate (translation : Vector3, relativeTo : Transform) : void


function Translate (x : float, y : float, z : float, relativeTo : Transform) : void

Estos dos prototipos varían de los anteriores en el segundo parámetro, que ya no versa sobre si el movimiento se hará
tomando en cuenta las coordenadas locales del objeto que se mueve o las globales de la escena. En cambio, aquí el
movimiento de nuestro transform vendrá fijado por otro transform. De esta manera, nos moveremos de acuerdo con las
coordenadas locales de ese segundo objeto al que hacemos referencia. Si por ejemplo el parámetro relativeTo es una
cámara, la derecha del traslation no es la derecha local de nuestro transform, o la derecha global, sino la derecha de la
cámara. Por ejemplo:
Tecleamos lo siguiente en nuestro script
transform.Translate(Vector3.right * 5);

Esto hará que nuestro cubo se mueva cinco unidades a su derecha.

Y ahora modificamos el script para que quede así:


var miCamara : Transform;
transform.Translate(Vector3.right * 5, miCamara);

Arrastramos la cámara principal hasta la variable miCamara. De nuevo al play.

Ahora el cubo se mueve cinco unidades a la derecha de la cámara.

Como último apundo hemos de añadir que si relativeTo es nulo, las coordenadas del movimiento pasarán a ser las
globales.

Rotate:

function Rotate (eulerAngles : Vector3, relativeTo : Space = Space.Self) : void

Esta función permite rotar un transform. Acepta como parámetro un Vector3 con los grados de rotación en ángulos
Euler. Por defecto, al igual que sucediera con la función translate, el transform rota sobre sus coordenadas locales,
pudiendo hacerlo según las coordenadas globales si se lo indicamos con Space.World como segundo parámetro.

Veamos un ejemplo. Rehagamos nuestro script con el siguiente contenido:


function Update() {
transform.Rotate(Vector3.right * 25 * Time.deltaTime);
transform.Rotate(Vector3.up * 20 * Time.deltaTime, Space.World);
}

Al darle al play, observaremos que nuestro cubo rota sobre su eje X a razón de 25 grados por segundo mientras a la vez
gira sobre el eje Y global a razón de 20 grados por minuto.

Es de señalar que ambas instrucciones se encuentran contenidas dentro de la función Update. Esta función, que en su
momento estudiaremos con la debida profundidad, es llamada cada frame por nuestro ordenador (el framerate de cada
ordenador varía), de tal manera que el movimiento es contínuo, pues los valores de rotación de nuestro cubo son
actualizados constantemente.

Precisamente porque el framerate de cada ordenador es distinto, y para evitar que en función de cada PC los objetos se
movieran más o menos deprisa (lo que tendría indeseables consecuencias, sobre todo en juegos en línea multijugador),
se suele utilizar la variable de la clase Time Time.deltaTime. Time.deltaTime lo que consigue es transformar la unidad de
frecuencia de cualquier tipo de movimiento de frames a segundos. Si por ejemplo en el último script no hubiéramos
usado estas variables de tiempo, el cubo giraría 25 y 20 grados cada frame, quedando al albur de cada ordenador la
frecuencia real que eso supondría. Al multiplicarlo por Time.deltaTime las rotaciones dependerán del tiempo y en
consecuencia se producirán con la misma cadencia en cualquier ordenador.

function Rotate (xAngle : float, yAngle : float, zAngle : float, relativeTo : Space = Space.Self) : void

En esta versión de la función, la única diferencia es que se sustituye el Vector3 por tres floats, en los cuales se
consignarán igualmente los grados de rotación.

function Rotate (axis : Vector3, angle : float, relativeTo : Space = Space.Self) : void
La variante que nos ofrece este tercer prototipo es que por un lado se indica en el primer parámetro sobre qué eje
queremos que rote el transform, y en un segundo parámetro de tipo float le hemos de indicar el número de grados ha de
rotar.

14. CLASE TRANSFORM (V)

RotateAround:

function RotateAround (point : Vector3, axis : Vector3, angle : float) : void

Esta función nos permite que nuestro transform rote alrededor de un punto (u objeto situado en ese punto), como si
orbitara.

El parámetro point sería el punto, descrito por un Vector3, alrededor del cual queremos hacer girar nuestro transform.
Axis nos servirá para indicar sobre qué eje queremos girar, y angle el número de grados por frame (si está dentro de la
función update) o por segundo (si implicamos la variable de clase Time.deltaTime) que queremos que gire. Obviamente,
aquí estamos variando tanto la rotación como la posición de nuestro transform.

si por ejemplo quiriéramos que nuestro cubo girara sobre su eje Y alrededor del centro exacto del espacio global, a
razón de 20 grados por segundo, escribiríamos este script:

function Update() {
transform.RotateAround (Vector3.zero, Vector3.up, 20 * Time.deltaTime);
}

Dado que el cubo se hallaba ya en las coordenadas globales 0,0,0 el script anterior lo único que consigue es que el
cubo parezca girar sobre sí mismo. Vamos a hace lo siguiente: colocamos en el inspector a nuestro cubo en position.x =
-1, y volvemos a darle al play.

Y vemos ya más claramente cómo orbita alrededor del punto dado, cambiando simultáneamente de posición y de
rotación.

Podemos hacer también con esta función que un objeto orbite alrededor de otro. Escribimos:
var centroDeGravedad: Transform;
function Update() {
transform.RotateAround (centroDeGravedad.position, Vector3.up, 20 * Time.deltaTime);
}

Acto seguido, arrastramos el objeto sobre el que queremos que orbite nuestro cubo, en este caso la cápsula. Le damos
al play y comprobamos.

Observemos que aunque arrastramos un gameobject, lo que está esperando nuestro scrips es un transform. Lo que
sucede es que -tal como vimos en las primeras lecciones- todos los componentes de nuestro gameobject comparten
nombre, de tal manera que cuando en casos como este Unity detecta que uno de los componentes del gameobject que
le estamos arrastrando tiene el nombre y el tipo del que está esperando para cumplimentar una variable,
automáticamente selecciona -en este caso- el transform homónimo y no el gameobject.

Ese transform, no obstante, no es del tipo Vector3 que espera nuestra función. Sí que lo es la variable position del
mismo, que además contiene las coordenadas globales del transform sobre el que queremos orbitar.

Podéis probar a sustituir en el segundo parámetro Vector3.up por Vector3.right (o Vector3.forward), y comprobar qué es
lo que sucede.

LookAt:

function LookAt (target : Transform, worldUp : Vector3 = Vector3.up) : void

El nombre de esta función se podría traducir al castellano como "mira a", y es exactamente eso lo que hace: Rotar
nuestro transform hacia un objetivo -parámetro target- que es asimismo de tipo transform (y no Vector3, como en el
caso de la función anterior). Esa rotación se efectúa sobre un eje global, y por defecto éste será el eje Y. Ello implicará
que nuestro transform, si no le indicamos lo contrario, girará hacia la derecha y la izquierda "mirando" al transform que
hayamos designado como objetivo.

Huelga decir que esta función se usa mucho para cámaras, cuando pretendemos que éstas sigan en todo momento a
un determinado personaje.

Es importante avisar de que para que el transform gire sobre el eje que le indiquemos, ha de estar totalmente
perpendicular a dicho eje global.

Vamos a la práctica. En el Proyecto, colocamos el ratón sobre la carpeta Mis Scripts y le damos al botón
derecho=>create=>javascript. Al nuevo script lo renombramos como MiSegundoScript y le damos doble click para se se
nos abra el editor.

Desplazamos un poco hacia abajo la función Update, y la dejamos como sigue:


var sigueme : Transform;
function Update () {
transform.LookAt(sigueme);
}

Salvamos, y la arrastramos hasta nuestra cámara en la jerarquía. Seleccionamos entonces la cámara y arrastramos
ahora hasta la variable sigueme nuestro cubo.

Previamente a darle al play nos aseguramos de que el otro script (MiPrimerScript) que tenemos vinculado al cubo,
contenga lo siguiente:
var centroDeGravedad: Transform;
function Update() {
transform.RotateAround (centroDeGravedad.position, Vector3.up, 20 * Time.deltaTime);
}

Y ahora sí, ya podemos darle al play y perseguir a nuestro cubo.

function LookAt (worldPosition : Vector3, worldUp : Vector3 = Vector3.up) : void

Este segundo prototipo se diferencia del primero en que el objetivo que ha de seguir nuestro transform no es otro
transform, sino un Vector3. Esto tiene sentido, siguiendo con el ejemplo de la cámara, si quisiéramos que ésta enfocara
un punto concreto de la escena (en coordenadas globales)

Por ejemplo, si queremos que una cámara enfoque al centro de la escena, le vinculamos este script:
transform.LookAt(Vector3.zero);
Otra posibilidad que nos permite esta función es que sea nuestro jugador, desde su teclado, quien se encargue de
decidir dónde ha de enfocar la cámara. Para ello tendremos que recurrir a una clase que aún no hemos visto, la clase
input.

Abrimos el script MiSegundoScript, y tecleamos lo que sigue:


function Update () {
transform.LookAt(Vector3(Input.GetAxis("Horizontal") * 10.0,0,0));
}

Le damos al play y movemos la cámara con las flechas de desplazamiento horizontal. Quizás notemos que el resultado
es un poco basto, pero meramente quería que tuviérais una idea de las utilidades de esta función. También podríamos
controlar la cámara desde el ratón y no desde el teclado. Veremos todo esto en profundidad cuando estudiemos la clase
input.

Nos vemos en el siguiente capítulo.

15. CLASE TRANSFORM (VI)

TransformDirection:

function TransformDirection (direction : Vector3) : Vector3

Esta función toma como único parámetro la dirección local de un transform almacenada en un Vector3 y la convierte en
dirección global, devolviéndola en otro Vector3.

La utilidad de esta función puede resultar un poco confusa al principio. Pensemos, para intentar aproximarnos al
concepto, que quisiéramos hacer nuestra propia versión del FIFA 2011. Modelamos un campo de futbol y, para darle
más realismo y emoción al juego, colocamos varias cámaras en los laterales del campo, cámaras que mediante LookAt
irían siguiendo los lances del juego. El problema que nos encontraríamos es que cuando Messi (por poner un caso) está
avanzando hacia delante (en las coordenadas globales), en cambio en nuestra cámara lateral pareciera que lo está
haciento -por ejemplo- hacia la izquierda. Y en consecuencia, cuando intentamos que nuestro defensa lo ataje
moviéndolo hacia atrás (según la perspectiva que nos da la cámara lateral) veremos consternados que el defensa en
lugar de retroceder se desplaza hacia la derecha.

Esto no nos pasaría si, gracias a esta función, convertimos la coordenada "hacia detrás" de nuestra cámara en su
equivalente en coordenadas globales. Si esa función se la aplicamos a todas las cámaras, no tendremos que estar
pensando "Ojo, que esta cámara está en el gol norte, por lo que si me baso en ella cuando haga un movimiento, he de
recordar que arriba es abajo y viceversa y la derecha es la izquierda y bla, bla, bla".

Veámoslo en un ejemplo.

En Unity borramos el script MiSegundoScript de la cámara. Colocamos en el inspector a nuestro cubo en las
coordenadas 0.0.0. con una rotación igualmente de 0,0,0. Asimismo, la cámara debería estar en las coordenadas de
posición 0,1,-5 con los tres ejes de rotación a 0.

Abrimos MiPrimerScript. Tecleamos esto:


var miCamara : Transform;
var estaEsMiDerecha : Vector3;
estaEsMiDerecha = miCamara.TransformDirection(Vector3.right);
transform.Translate(estaEsMiDerecha * 3);

Arrastramos la cámara a miCamara. Le damos al play. El cubo se moverá 10 unidades a la derecha. Pero, ¿a la
derecha de quién?. Si observamos, la derecha del cubo es también la derecha de la cámara.

Para averiguarlo, vamos a recolocar la cámara en estas coordenadas:


Position: -10, 1, -0.5
Rotation: 0, 90, 0

Y de nuevo le damos al play. Obviamente, el cubo se mueve a la derecha de la cámara. Visto desde la ventana game,
coincidirá ahora "nuestra" derecha (entendiendo como tal la que nos muestra la pantalla) con el sentido el movimiento.

El script no necesita mucha explicación. Inicializamos una variable con el transform de la cámara que hemos arrastrado.
La derecha de esa cámara la almacenamos en una variable de tipo Vector3, la cual luego pasamos como parámetro a
nuestra función TransformDirection para que nos convierta la derecha de nuestra cámara en la derecha de las
coordenadas globales. A partir de ahí, todo lo que le suceda a la derecha de nuestra cámara (por así decirlo) le estará
pasando a la derecha del mundo.

function TransformDirection (x : float, y : float, z : float) : Vector3

Es la misma función, pero aplicando como parámetros 3 floats para cada eje en lugar de un Vector3.

InverseTransformDirection:

function InverseTransformDirection (direction : Vector3) : Vector3

o bien

function InverseTransformDirection (x : float, y : float, z : float) : Vector3

Se trata obviamente de la función inversa a la anterior, y por consiguiente transforma una dirección global en dirección
local.

Veamos un ejemplo:

Devolvemos antes que nada a nuestra cámara a su lugar y rotación originales:


Position: 0,1,-5
Rotation: 0,0,0

La posición y rotación de nuestro cubo, por su parte, está totalmente a 0.

Abrimos MiPrimerScipt, y tecleamos:


var estaEsLaDerechaDelMundo : Vector3;
estaEsLaDerechaDelMundo = transform.InverseTransformDirection(Vector3.right);
transform.Translate(estaEsLaDerechaDelMundo * 2);

Como vemos, declaramos una variable de tipo Vector3 que luego inicializamos de la siguiente forma: le pasamos a la
función InverseTransformDirection el parámetro Vector3.right, que en esta función representa la derecha en
coordenadas globales (no la derecha de ningún transform). Esa derecha del mundo, global, es "traducida" por la función
en una coordenada local susceptible de usar por cualquier transform y es asignada,como decíamos, a nuestra variable
estaEsLaDerechaDelMundo. Dicha variable, por último, es pasada como parámetro de movimiento al transform del
gameobject que tiene vinculado el script (en este caso el cubo).

¿La probamos?

El cubo se desplaza a la derecha. Pero, para saber si la derecha es la derecha del cubo o la derecha global, podemos
en el inspector darle un valor al rotation.Y del cubo de 45 grados, por ejemplo. Y probamos de nuevo.

Definitivamente, el cubo se mueve ahora siguiendo el eje X global, y no el suyo local.


16. CLASE TRANSFORM (y VII)
TransformPoint:

function TransformPoint (position : Vector3) : Vector3

function TransformPoint (x : float, y : float, z : float) : Vector3

A diferencia de TransformDirection, lo que esta función transforma de local en global es la posición y no la dirección.
Esto es, esta función no versa sobre si un transform se desplaza a su derecha o a la derecha de las coordenadas
globales, sino de si las coordenadas en que se encuentra el transform son globales (respecto al mundo) o locales
(respecto al padre de dicho transform). Con esto entendido, como decimos, esta función acepta como parámetro las
coordenadas locales de un transform (su ubicación respecto de la de su padre) y devuelve dichas coordenadas
traducidas a coordenadas globales.

Lo veremos más claron con un ejemplo. Con el cubo en position 0,0,0 y rotation en 0,0,0 (si no,no funcionará)
arrastramos el cubo dentro de la cápsula en la Jerarquía, para convertir a cubo en hijo de la cápsula. Si seleccionamos
el cubo, vemos en el inspector que sus coordenadas de posición han pasado a ser locales respecto de su padre.
Abrimos MiPrimerScript (que debería seguir siendo parte del cubo) y escribimos el siguiente script:
var coordenadasLocales : Vector3;
var coordenadasGlobales: Vector3;
var coordenadasTransformadas: Vector3;

coordenadasLocales = transform.localPosition;
coordenadasGlobales = transform.position;
coordenadasTransformadas = transform.position =
transform.TransformPoint(transform.localPosition);

Debug.Log("El cubo tiene las coordenadas locales " + coordenadasLocales.ToString() +


" las globales " + coordenadasGlobales.ToString() + " y las transformadas " +
coordenadasTransformadas.ToString());

El ejemplo parece más complicado de lo que es. Declaramos tres variables de tipo Vector3 para que contengan las tres
coordenadas que imprimiremos para nuestro cubo (los nombres de las variables son lo suficientemente explicativos).
Acto seguido inicializamos las dos primeras variables con la posición global y local del transform, y la tercera con las
coordenadas que tendrá el transform cuando convirtamos sus coordinadas locales (respecto de la cápsula) en globales.

Salvamos y le damos al play. Observamos que por un lado el cubo se desplaza en dirección y distancia opuesta al
cilindro, ya que el -3,-4,-2 que constituían sus coordenadas locales ahora ha pasado a ser su posición en coordenadas
globales.

InverseTransformPoint:

function InverseTransformPoint (position : Vector3) : Vector3

function InverseTransformPoint (x : float, y : float, z : float) : Vector3

Función inversa a la precedente, que por tanto transforma la posición de un transform dado del espacio global al
espacio local.
DetachChildren:

function DetachChildren () : void

Es una función muy sencilla que sirve para desparentar los hijos. Para probarla debemos eliminar el script vinculado al
cubo, y posteriormente hacer doble click en MiPrimerScript en el Proyecto para teclear lo siguiente:
transform.DetachChildren();

Salvamos y lo arrastramos a la cápsula, que es el transform que tiene hijos. Le damos al play y observaremos cómo en
la Jerarquía el cubo deja de aparecer como hijo de la cápsula.

Esta función es útil, entre otras cosas, cuando por alguna razón queramos destruir al objeto padre sin destruir a sus
hijos:
transform.DetachChildren();
Destroy(gameObject);

Find:

function Find (name : String) : Transform

Con esta función podemos buscar por su nombre -y en su caso acceder- a un transform hijo de nuestro transform. La
función devuelve dicho transform hijo, si lo encuentra. Si no lo encuentra, retorna null.

Si tenemos que buscar a varios niveles, esto es, hijos de los hijos de nuestros hijos, podemos utilizar un slash o barra
inclinada (“/”) para recrear la jerarquía donde queremos buscar (p ej. "Cuerpo/Brazo/Mano/Indice")

Dado que deberíamos tener el cubo aún dentro de la cápsula, y MiPrimerScript vinculado a ésta, lo aprovecharemos
para realizar un ejemplo:
var aquiMiHijo : Transform;

function Update() {
aquiMiHijo = transform.Find("Cubo");
aquiMiHijo.Rotate(Time.deltaTime*60, 0, 0);
}

Como podemos comprobar al darle al play, la función Find encuentra un transform hijo llamado "Cubo" (recordemos
que, por un lado hemos de suministrarle un string, esto es, no nos debemos olvidar de las comillas, y por otro lado que
todos los componentes de un gameobject comparten por defecto el mismo nombre que éste, así que el transform del
Cubo se llama Cubo), y almacena ese transform que encuentra dentro de la variable aquiMiHijo. A partir de ahí,
podemos utilizar esa variable como si fuera un alias del propio transform.

IsChildOf:

function IsChildOf (parent : Transform) : boolean

Esta función casi no requiere explicación. Devuelve true si el transform que hace la pregunta es hijo, "nieto" o incluso
idéntico al transform parent que pasamos como parámetro. En otro caso, devuelve false.

Y con esto acabamos la clase transform, que como decía al inicio es una de las diez clases más importantes de la API
de Unity.
17. CLASE RIGIDBODY (I)

Nos disponemos a estudiar otra de las clases vitales para entender el funcionamiento de Unity. Es raro en juego en el
que no interviene de alguna manera la física (un enemigo se cae, un personaje salta, y coche choca contra un muro...)
La clase Rigidbody controla la posición de un objeto a través de simulación de física. El componente Rigidbody, al ser
añadido a un gameobject, toma el control sobre la posición de un objeto, haciendo que caiga bajo la influencia de la
gravedad, y puede calcular cómo los objetos responderán a las colisiones.

Vamos a inspeccionar unas cosas en Unity: antes que nada, si estáis siguiendo por orden estas lecciones tendréis
(como yo) emparentado el cubo dentro de la cápsula, así que lo arrastramos fuera para desemparentarlo. Luego, con el
cubo seleccionado, echamos un vistazo al inspector. Vemos en él diferentes apartados (su transform, su malla, un
collider y elementos de renderizado) pero no un rigidbody. Lo mismo nos pasaría si examinamos la cápsula. Ambos
objetos, por lo tanto, no están sometidos a leyes físicas, y en consecuencia es como si carecieran de masa y por ende
fueran ajenos a la gravedad.

Vamos a regalarles un rigidbody a nuestros objetos. Para hacer eso nos vamos -con uno de los obtetos seleccionado- al
menú superior, y le damos a Component=>Physics=>Rigidbody. El gameobject que teníamos selecionado tiene ahora
un componente nuevo:

Hacemos lo mismo con el otro gameobject. Acto seguido, en la ventana Escena pulsamos la X de nuestro gizmo,para
tener una visión de los objetos desde el eje horizontal, y comprobar mejor lo que pasará ahora. Le damos al play...

y los objetos caen, porque ya no son inmunes a la gravedad.

Lo que procede ahora, para evitar que nuestros objetos abandonen la escena a la primera de cambio, es colocar un
suelo. Vamos a ello.

Antes que nada, vamos a eliminar a nuestra vieja amiga la cápsula (conocida por los más veteranos por Origen). La
razón es que por su forma específica no es demasiado estable para nuestros ejemplos una vez se ve afectada por la
gravedad. Así que adiós, cápsula.

Y como a rey muerto, rey puesto, nos vamos al menú y le damos a GameObject=>Create other=>Sphere. La
renombramos como "Esfera".

A continuación vamos a colocar a nuestros dos objetos a ras de suelo (antes no importaba que estuvieran flotando por
ahí, pero ahora no querremos empezar el juego con un objeto pegándose un tortazo contra el suelo). Colocamos, como
decimos, nuestros objetos en las siguientes coordenadas de posición (las rotaciones deben estar a 0):
Cubo: 0,0,0.
Esfera: 2,0,2.

Nos vamos ahora al menú=>Gameobject=>Create Other=>Plane. Lo renombramos antes que nada como "Suelo".
Luego colocamos nuestra ventana de escena en vista superior (click en eje Y del gizmo), y para acabar, en la
coordenada Y de su transform.position escribimos -0.5.

Esto último supongo que merece una explicación. Veamos, tanto el cubo como la esfera tienen una altura de una unidad
y su origen (el 0,0,0) de dichos objetos se halla en su centro geométrico. Esto quiere decir que cuando un cubo está en
el valor 0 de su eje Y, la mitad del cubo (0,5) se halla por debajo del nivel 0 (que es donde hemos colocado el suelo). En
definitiva, si tanto el cubo como la esfera y el suelo los dejáramos con sus respectivos ejes Y a cero, los dos primeros
aparecerían semienterrados en el tercero. Para evitarlo, o bien levantamos todos los objetos o bien bajamos el suelo,
que es lo que hemos hecho.

Démosle al play. Observamos que los objetos ya no se caen, aunque a decir verdad y siendo todos los elementos de
nuestra escena del mismo color, quizás nos interesaría ponerle a nuestro suelo un color que los hiciera resaltar.

Con el mouse situado sobre la carpeta Mis Scripts, pulsamos el botón derecho y creamos otro script (javascript,
recordemos). Lo llamaremos ColorSuelo, y dice así:
function Start() {
renderer.material.color=Color.red;
}

La función Start es llamada por Unity, como su nombre indica, cuando se inicia el juego. Sólo es llamada una vez, a
diferencia de Update, y en este caso nos interesa que sea así ya que vamos a dejar el color de nuestro suelo fijo
durante todo el juego. Salvamos y arrastramos el script a Suelo. Le damos al play.

Y voilá, el suelo se nos vuelve de color rojo y los objetos contrastan algo más. Aún y todo, la escena continúa un poco
oscura, así que mejor iluminarla un poco, ¿no?

Introducimos en la escena desde el menú gameobject una luz direccional, que renombraremos como "Luz" y
ubicaremos/rotaremos en las siguientes coordenadas:
Posición: -5,15,-10
Rotación: 20,0,0

Si le damos al play, deberíamos ver algo parecido a esto:

Y más o menos con esto tendríamos ya la escena "preparada" para empezar a trabajar con la clase Rigidbody.

18. CLASE RIGIDBODY (II)

De un mero vistazo a este gráfico podréis sin duda deducir dos cosas:
1.- La clase Rigidbody es "hermana" de la clase Transform. Están al mismo nivel y heredan de las mismas clases.
2.- Nos va a ocupar un buen número de lecciones estudiarla. Pero el esfuerzo veréis que merece la pena, ya que
dominando las clases Transform, Rigidbody (y Collider con sus dos clases hijas, que estudiaremos tras Rigidbody)
tendremos controlado en gran parte el tema del movimiento y reacción ante el movimiento ajeno de nuestros
gameobjects.

Y sin más dilación, vamos allá.

VARIABLES:

velocity:

Var velocity : Vector3

Representa a través de un vector la velocidad del rigidbody.

No es aconsejable en la mayoría de casos modificar esta variable directamente, ya que derivaría en un comportamiento
poco realista (no habría una transición entre la velocidad (o incluso inactividad) anterior y la nueva velocidad. Sí está
justificado en cambio su uso para aquellos cambios de velocidad que provienen de un impulso, como por ejemplo un
salto.
function FixedUpdate () {
if (Input.GetButtonDown ("Jump")) {
rigidbody.velocity = Vector3(0,6,0);
}
}

Tecleamos este script en MiPrimerScript, lo salvamos y lo arrastramos al cubo.

Hay varias cosas del mismo que necesitarán una explicación. Para empezar, vemos que se está usando una función
llamada FixedUpdate. Esta función es parecida a la función Update, de la que ya habíamos hablado, pero a diferencia
de ésta, FixedUpdate es llamada (actualizada) a intervalos regulares (fijos). Por lo tanto, así como en la función Update
la actualización depende del framerate de cada ordenador, en la función FixedUpdate la actualización se produce en
intervalos fijos, iguales para todos los ordenadores. Por esa razón, cuando tratamos con cuestiones relacionadas con la
física (y toda la clase Rigidbody está relacionada con la física) habremos de usar la función FixedUpdate y no la función
Update (salvo que queramos que las caídas, colisiones y velocidad de los objetos de nuestro juego sean diferentes en
función del ordenador con el que juguemos o que, yendo aún más allá, en un juego online multijugador el personaje de
cada user vaya a una velocidad distinta, por ejemplo.)

Lo segundo nuevo del script es una referencia a la clase input. La clase input (otra de las clases que ocupan el top ten
de importancia en Unity) es la que controla los eventos, esto es, qué tiene que hacer el usuario para disparar, qué tecla
hace qué cosa, qué tipo de colisiones provocan que pase otra cosa distinta, etc. En este caso,
Input.GetButtomDown(string) dispara un evento cuando el usuario pulsa la tecla que le pasamos a la función como un
string. En nuestro Script le pasamos el string "Jump", que por defecto se corresponde a la barra espaciadora.

¿Y qué sucede cuando le damos a la barra espaciadora?. Pues que al rigidbody que en el capítulo anterior le añadimos
a nuestro cubo le será aumentada de golpe la velocidad en el vector Y (arriba/abajo). Para comprobarlo, dale al play y
cuando el juego esté corriendo presiona la barra espaciadora.

angularVelocity:

Var angularVelocity : Vector3

Vector que representa la velocidad angular del rigidbody. Por velocidad angular nos referimos a la velocidad de rotación.
Al igual que con velocity, en la mayoría de casos no cambiaremos la velocidad directamente, pues salvo casos en que
precisamos un impulso súbito de la velocidad, no queda realista.

Para entender bien la diferencia entre velocidad y velocidad angular, vamos a completar el script anterior,
permitiéndonos aplicar una u otra velocidad, o incluso ambas a la par. MiPrimerScript quedará así:
function FixedUpdate () {
if (Input.GetButtonDown ("Jump")) {
rigidbody.velocity = Vector3(0,6,0);
}
if (Input.GetButtonDown ("Horizontal")) {
rigidbody.angularVelocity = Vector3(7,0,0);
}
}

Como podemos observar, hemos añadido un segundo if de tal manera de que si el jugador en lugar de presionar la
barra espaciadora (jump) presiona cualquiera de las dos flechas horizontales de dirección del teclado, la velocidad que
se aplique al cubo será velocidad angular.

Salvamos, le damos al play y pulsamos cualquiera de las flechas horizontales. Probablemente observaremos que el
cubo hace intención de girarse, pero no tiene la fuerza suficiente para hacerlo. Si, en cambio, pulsamos la barra
espaciadora y mientras el cubo está en el aire presionamos la flecha horizontal, asistiremos seguramente a una bonita
pirueta de nuestro cubo.

19. CLASE RIGIDBODY (III)

drag:

Var drag : float

Indica la resistencia al movimiento de un objeto y por lo tanto se usa para enlentecer dicho objeto. Cuando más algo el
valor, mayor resistencia.

Veámoslo añadiendo un tercer if a nuestro script, de tal forma que quede así:
function FixedUpdate () {
if (Input.GetButtonDown ("Jump")) {
rigidbody.velocity = Vector3(0,6,0);
}
if (Input.GetButtonDown ("Horizontal")) {
rigidbody.angularVelocity = Vector3(7,0,0);
}
if (Input.GetKeyDown ("z")) {
rigidbody.drag = 10;
}
}

Salvamos. La función GetKeyDown de la clase input dispara un evento cuando el user presiona una determinada tecla,
en nuestro ejemplo la zeta. Le damos al play y lanzamos al aire nuestro cubo presionando la barra espaciadora, y
cuando esté en el aire presionamos la z. Observaremos cómo los movimientos del cubo se enlentecen. Si a
continuación volvemos a presionar la barra espaciadora o la flecha horizontal, comprobaremos que la resistencia al
movimiento de nuestro cubo es mucho mayor.

angularDrag:

rigidbody.angularDrag = 10;

Indica la resistencia angular del objeto. Puede ser usado para enlentecer la rotación de un objeto.

mass:

Var mass : float

Variable que representa la masa del rigidbody, su peso. Recomienda el manual de referencia que el valor de la masa
debería estar entre 0.1 y 10. Masas grandes -según el manual- hacen las simulaciones físicas inestables.

Obviamente, los objetos con mayor masa proyectan a los que tienen menos masa cuando chocan. Pensemos en un
camión grande golpeando un coche pequeño.

Vamos a verlo con un ejemplo sencillo. Para llevarlo a cabo eliminamos el script que tiene vinculado nuestro cubo.
Luego seleccionamos la esfera, y la ubicamos en las coordenadas -3,0,0. Acto seguido hacemos doble click en
MiPrimerScript para abrir el editor y tecleamos lo que sigue:
function FixedUpdate () {
if (Input.GetButtonDown ("Horizontal")) {
rigidbody.velocity = Vector3(9,0,0);
}
}

Salvamos y arrastramos el script a la esfera. Este no tiene mucho misterio: al presionar la flecha horizontal del teclado,
nuestra esfera debería tomar una velocidad de aceleración hacia su derecha, por lo que en principio debería impactar
con el cubo. Probémoslo. Play.

Observamos que efectivamente al pulsar la flecha de desplazamiento horizontal, la esfera golpea contra el cubo con la
suficiente fuerza como para incluso volcarlo.

Si nos fijamos, en los respectivos rigidbodies del cubo y la esfera que aparecen en el inspector, ambos tienen una mass
de 1. Debido a ello, a raiz del impacto no hay un objeto que parezca "salir victorioso" de este (el cubo gira una cara y la
esfera retrocede lentamente tras el impacto).

¿Que pasaría, no obstante, si uno de los dos rigidbodies tuviera una masa sensiblemente distinta del otro?

Probémoslo añadiendo una expresión más a nuestro script:


function FixedUpdate () {
if (Input.GetButtonDown ("Horizontal")) {
rigidbody.velocity = Vector3(9,0,0);
rigidbody.mass = 0.1;
}
}

Ahora nuestra esfera tendrá una masa de 0.1, esto es, una décima parte de la masa del cubo. Salvamos, play, flecha
lateral...

Casi ni mueve el cubo.

Podéis probar a la inversa, si queréis, darle más masa a la esfera (sin pasar de 10, recordad). Comprobad que una vez
pulsais la flecha de desplazamiento horizontal, la masa de la esfera (si es que la tenéis seleccionada) cambia en el
inspector y se convierte en la que habéis asignado en el script.

Para acabar, advertiros de que un error común es asumir que objetos pesados caen más rápido que los ligeros. Esto no
es verdad, ya que la velocidad depende de la gravedad y la resistencia (drag), y no de la masa.

useGravity:

Var useGravity : boolean

Booleano que controla si la gravedad afecta a este rigidbody. Si está en false el rigidbody se comportará como si
estuviera en el espacio.

Probamos. Rehacemos MiPrimerScript:


function FixedUpdate () {
if (Input.GetButtonDown ("Jump")) {
rigidbody.velocity = Vector3(0,7,0);
}
if (Input.GetButtonDown ("Horizontal")) {
rigidbody.useGravity = false;
}
if (Input.GetButtonDown ("Vertical")) {
rigidbody.useGravity = true;
}
}

Salvamos, y le damos impulso hacia arriba a nuestra esfera. Alternad las flechas de desplazamiento horizontal y vertical
para comprobar la diferencia de movimientos sin y con gravedad.

20. CLASE RIGIDBODY (IV)

isKinematic:

Var isKinematic : boolean

Es un booleano que controla si las físicas afectan al rigidbody. Si isKinematic está habilitado (es true), a nuestro
rigidbody no le afectarán ni fuerzas, ni colisiones ni junturas (joints). Ello quiere decir que no podemos esperar que
frente -por ejemplo- a una colisión ese rigidbody kinemático se comporte de la manera lógica que por su masa,
resistencia y gravedad debiera comportarse. Sus movimientos, reacciones y respuestas, por tanto, deberemos
marcárselos específicamente mediante scripts o animaciones.

Los cuerpos kinemáticos, por el contrario y a su vez, sí afectan el movimiento de otros rigidbodies no kinemáticos a
través de colisiones o joints.

Quisiera aprovechar para recordar que las variables "expuestas" de tipo booleano son mostradas por el inspector como
un checkbox, donde el cuadro marcado se corresponde a true y el desmarcado al false.

freezeRotation:

Var freezeRotation : Boolean

Esta variable vendría a ser una especialización de la anterior. freezeRotation controla si las físicas cambiarán la rotación
del objeto. Si esta variable está habilitada (true), la rotación no será mofificada por la simulación de físicas, y en todo
caso tendremos que establecerla nosotros manualmente mediante scripts o animaciones.

constraints:

Var constraints : RigidbodyConstraints

Controla qué grados de libertad están permitidos para la simulación de nuestro rigidbody. Por defecto esta variable tiene
el valor RigidbodyConstraints.FreezeNone, permitiendo rotación y movimiento en todos los ejes.
En algunos casos, puedes querer constreñir un rigidbody para que sólo se mueva o rote sobre algunos ejes, como por
ejemplo cuando desarrolles juegos en 2D. Puedes usar el operador de bits OR ('||') para combinar múltiples constraints.

Vamos a probar con un sencillo ejemplo la utilidad de esta variable. En Unity alejamos un poco la cámara para que se
vea tanto la esfera como el lateral izquierdo (según se mira) del plano. Abrimos nuestro script habitual:
function FixedUpdate () {
if (Input.GetButtonDown ("Horizontal")) {
rigidbody.velocity = Vector3(-6,0,0);
}
}

Salvamos, play, flecha desplazamiento lateral. La esfera sobrepasa la superficie del plano y arrastrada por la gravedad
cae.

Podríamos solucionar esta eventualidad quitándole la gravedad a la esfera, pero tenemos una solución más elegante:
no permitir que nuestra esfera se mueva en el eje Y (arriba/abajo).

Así que modificamos nuestro script:


function FixedUpdate () {
if (Input.GetButtonDown ("Horizontal")) {
rigidbody.velocity = Vector3(-6,0,0);
rigidbody.constraints = RigidbodyConstraints.FreezePositionY;
}
}

Y volvemos a probar. Dado que le hemos congelado ("freeze") a nuestra esfera la posibilidad de alterar el valor de su
eje de posición Y, la esfera no puede ni ascender ni caer, razón por la cual continúa rodando allende el plano.

Observamos que esta variable es de tipo RigidbodyConstraints, lo cual en el fondo es una enumeración que nos permite
elegir entre una serie de valores, a saber:
None Sin limitaciones de posición ni de rotación
FreezePositionX Congela la posición en el eje X.
FreezePositionY Congela la posición en el eje Y.
FreezePositionZ Congela la posición en el eje Z.
FreezeRotationX Congela la rotación sobre el eje X.
FreezeRotationY Congela la rotación sobre el eje Y.
FreezeRotationZ Congela la rotación sobre el eje Z.
FreezePosition Congela la posición sobre todos los ejes.
FreezeRotation Congela la rotación sobre todos los ejes
FreezeAll Congela rotación y posición de todos los ejes

collisionDetectionMode:

var collisionDetectionMode : CollisionDetectionMode

Esta variable nos permite establecer el modo de detección de colisiones del rigidbody. Como podemos observar, es de
tipo CollisionDetectionMode, tratándose ésta al igual que en el caso anterior de una enumeración que admite tres tipos
de valores, que pasamos a desarrollar:

Distrete: Este es el modo de detección de colisiones por defecto. Es el que menos recursos consume, pero no garantiza
que nuestro rigidbody detecte un conjunto de colisiones, sobre todo si estas acontencen a gran velocidad. En modo
discrete Unity comprueba si el rigidbody ha sido colisionado cada vez que se llama a la función fixedUpdate, que como
ya explicamos es llamada un número fijo de veces por segundo. Si la colisión se produce en el impass entre una
llamada a fixedupdate y la siguiente, nuestro rigidbody no la captará.

Continuous: Con este modo on, nuestro rigidbody detectará las colisiones con cualquier malla geométrica estática que
se tropiece en su camino, incluso si la colisión ocurre entre dos llamadas a FixedUpdate. Por malla geométrica estática
Unity entiende cualquier MeshCollider (no tardaremos en estudiarlo) que no tenga un rigidbody vinculado.

ContinuousDinamic: Si establecemos este sistema de detección de colisiones para nuestro rigigbody, éste detectará
colisiones tanto con mallas geométricas estáticas como con otros rigidbodies que tengan a su vez activado el modo
continous collision detection. Este sistema consume bastantes recursos y debe ser sólo usado para prevenir
movimientos muy rápidos de objetos. Sólo es soportado, además, por rigidbodies con sphere o box collider.

A efectos de sintaxis, para colocar el sistema de detección de un rigidbody en modo ContinuousDinamic, por ejemplo, lo
escribiríamos así:
rigidbody.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;

21. CLASE RIGIDBODY (V)

centerOfMass:

CenterOfMass : Vector3

Representa el centro de masa relativo al origen del transform. El centro del transform se corresponde a las coordenadas
locales 0,0,0, y si no reubicamos vía un script el centro de masa, Unity lo calcula automáticamente (suele coincidir con el
centro del transform)

Si bien no es una variable con demasiada utilidad, sí que a veces puede servirnos para, por ejemplo, hacer más estable
y menos propenso a los vuelcos a un coche. Un coche con un centro de masa/gravedad bajo es menos propenso a
volcar.

rigidbody.centerOfMass = Vector3 (0, -2, 0);

worldCenterOfMass:

var worldCenterOfMass : Vector3

Esta variable nos indica el centro de masa/gravedad de un rigidbody en el espacio global. Tengamos presente que a
diferencia de la anterior esta variable es de solo lectura.

detectCollisions:

Var detectCollisions : Boolean

Indica si la detección de colisiones está habilitada (por defecto, sí). Deshabilitar esta variable es util si tenemos un
objeto que es kinemático y queremos evitar que el sistema realice cálculos inútiles de detección de colisiones para él.
interpolation:

Var interpolation : RigidbodyInterpolation

La interpolación nos permite suavizar el efecto de correr físicas en un framerate fijo. Por defecto la interpolación está off.
Comunmente se usa la interpolación de rigidbodies en los personajes de los jugadores. Las físicas suelen transcurrir en
pasos fijos (FixedUpdate), mientras los gráficos son renderizados en framerates variables. Esto puede llevar a que los
objetos parezcan temblorosos, porque físicas y gráficos no están completamente sincronizados. El efecto es sutil pero
perceptible, y sobre todo afecta a los personajes principales que son seguidos por alguna cámara. Es recomentable
poner en on la interpolación para el personaje principal pero deshabilitarla para los demás.

La enumeración RigidbodyInterpolation tiene los siguientes valores:


None Significa que no hay interpolación. Es la opción por defecto.
Interpolate La interpolación irá con un poco de retraso, pero puede ser un
poco más suave que la extrapolación.
Extrapolate La extrapolación predecirá la posición del rigidbody en base a la
velocidad actual.

sleepVelocity:

var sleepVelocity : float

La velocidad lineal, por debajo de la cual los objetos empiezan a dormir (por defecto 0.14) en un rango que va de 0 a
infinity. La hecho de que los objetos duerman es una optimización que permite que el engine de físicas deje de procesar
para estor rigidbodies. Así, cuando el objeto se duerme deja de detectar colisiones o realizar simulaciones, por ejemplo.

sleepAngularVelocity:

var sleepAngularVelocity : float

La velocidad angular, por debajo de los cuales los objetos empiezan a dormirse (por defecto 0.14)

maxAngularVelocity:

Var maxAngularVelocity : float

Representa la maxima velocidad angular del rigidbody (por defecto 7) en un rango que va de 0 a infinity.

Se tiene que fijar una velocidad angular máxima para evitar inestabilidades numéricas con objetos rotando demasiado
rápido.

Bueno, y hasta aquí las variables de la clase Rigidbody. He omitido la explicación de unas cuantas que me han parecido
poco importantes, de poco uso.

22. CLASE RIGIDBODY (VI)


FUNCIONES:

SetDensity:

Function SetDensity (density : float) : void

Nos permite asignar una masa en base al collider vinculado, lo cual es útil para asignar una masa en unos valores
acordes al tamaño de los colliders.

Podemos aplicar un ejemplo similar al que realizamos con la variable mass. Abrimos nuestro script habitual y
tecleamos:
function FixedUpdate () {
if (Input.GetButtonDown ("Horizontal")) {
rigidbody.velocity = Vector3(6,0,0);
rigidbody.SetDensity(6.0);
}
}

Salvamos. Play. Presionamos la flecha de desplazamiento horizontal y comprobamos que la masa de la esfera le
permite literalmente arrastrar al cubo.

AddForce:

function AddForce (force : Vector3, mode : ForceMode = ForceMode.Force) : void

Esta función (una de las más importantes de la API de Unity) añade una fuerza al rigidbody. Como resultado de esto el
rigidbody comenzará a moverse.

El primer parámetro es un Vector3, que nos servirá para indicar la potencia y dirección de la fuerza que aplicamos. Por
ejemplo, podemos teclear en nuestro script de cabecera:
function FixedUpdate () {
rigidbody.AddForce (Vector3.up * 10);
}

Y al darle al play nuestra esfera ascenderá, fruto de una fuerza constante (aplicada en cada actualización de la función
FixedUpdate). Obviamente, la velocidad de movimiento dependerá -además de la fuerza aplicada - de la masa del
rigidbody y de la resistencia, esto es, varios rigidbodies diferentes tendrán reaccione diferentes cuando les es aplicada
la misma fuerza.

function AddForce (x : float, y : float, z : float, mode : ForceMode = ForceMode.Force) : void


Este segundo prototipo de la función, como ya viene siendo habitual, meramente sustituye el parámetro de tipo Vector3
por 3 de tipo float.
//Equivale al script anterior
function FixedUpdate () {
rigidbody.AddForce (0, 10, 0);
}

El segundo parámetro observamos que es de tipo ForceMode, que como posiblemente sospechéis es una nueva
enumeración made in Unity, que tiene los siguientes valores:
Force Añade una fuerza contínua al rigidbody, dependiendo de su masa. Es
el valor por defecto y que obtiene resultados más realistas, ya que
como decíamos costará más esfuerzo mover objetos más pesados que
más livianos.
Acceleration Añade una aceleración contínua al rigidbody, ignorando su masa. A
diferencia de ForceMode.Force, Acceleration moverá cualquier
rigidbody de la misma forma sin tener en cuenta su masa, lo que es
útil si sólo queremos controlar la aceleración de un objeto sin
preocuparnos de la fuerza que debíeramos aplicarle para obtenerla
teniendo en cuenta su masa y resistencia.
Impulse Añade un impulso puntual de fuerza al rigidbody, teniendo en cuenta
su masa. Hay que tener la precaución de no incluir este modo dentro
de una función que se actualice a menudo (como FixedUpdate). Este
modo es útil para aplicar fuerzas que acontecen de repente, como
explosiones o colisiones.
VelocityChange Añade un cambio de velocidad instantáneo al rigidbody, ignorando su
masa. Con esa salvedad (una misma fuerza tendrá los mismos
resultados con rigidbodies de diferente masa), le es aplicable lo
mismo que al modo precedente.

Podemos ver la diferencia entre fuerzas aplicadas en el mismo script de arriba. Lo modificamos para que quede así:
function FixedUpdate () {
rigidbody.AddForce (Vector3.up * 10, ForceMode.Impulse);
}

Podéis ver que la esfera sale disparada. No retorna porque como la tenemos dentro de la función FixedUpdate está
recibiendo un impulso periódico. Vamos a liberarla:
rigidbody.AddForce (Vector3.up * 10, ForceMode.Impulse);

Supongo que queda demostrada la diferencia entre fuerza constante e impulso.

AddRelativeForce

function AddRelativeForce (force : Vector3, mode : ForceMode = ForceMode.Force) : void

Añade una fuerza al rigidbody relativa al sistema local de coordenadas de dicho rigidbody.

Para explicar qué quiere decir esto, vamos a preparar un ejemplo que requerirá varias fases: Para empezar, eliminamos
el script que tenemos vinculado a la esfera. Nos colocamos en la vista vertical en la ventana Scene, haciendo click en la
Y del gizmo. En Proyecto hacemos doble click en MiPrimer Script para abrir el editor, y tecleamos:
function FixedUpdate(){
rigidbody.AddForce (Vector3.right * 15);
}
Salvamos, arrastramos el script al cubo y le damos al play. Tal como era de esperar, se aplica una fuerza lateral al cubo,
pero éste da demasiadas vueltas sobre sí como para hacer una demostración respecto a las coordenadas de
desplazamiento, así que vamos a completar el script con una de esas variables que cuando uno las estudia cree que no
las usará nunca.
rigidbody.centerOfMass = Vector3(0,-0.3,0);
function FixedUpdate(){
rigidbody.AddForce (Vector3.right * 15);
}

Exacto. Le bajamos el centro de gravedad al cubo y en principio deberíamos ahora poderlo arrastrar como un mueble.
Probemos si esto funciona en la práctica.

Lo suficiente para poder realizar la explicación. Ahora giremos en el inspector el cubo 35 grados sobre el eje Y.
Volvamos a darle al play.

Vemos que la fuerza que aplica la función AddForce lo es en relación a las coordenadas globales, y no a las del objeto.

Sustituimos en el script AddForce por AddRelativeForce y probemos de nuevo.

Ahora la fuerza no es aplicada a la derecha del mundo, sino a la derecha relativa del cubo.

23. CLASE RIGIDBODY (VII)

AddTorque:

function AddTorque (torque : Vector3, mode : ForceMode = ForceMode.Force) : void

function AddTorque (x : float, y : float, z : float, mode : ForceMode = ForceMode.Force) : void

Esta función añade una torsión (no he podido encontrar una traducción mejor para torque) al rigidbody. Como resultado
el rigidbody empezará a girar alrededor del eje de torsión.

Así, si al eje Y de rotación de nuestro cubo le reintegramos su valor de 0 y luego modificamos de la siguiente manera
nuestro script del último ejemplo...
rigidbody.centerOfMass = Vector3(0,-0.3,0);
function FixedUpdate(){
rigidbody.AddTorque (Vector3.right * 15);
}
...vemos que efectivamente se le ha añadido a nuestro cubo una fuerza de torsión/rotación sobre el eje X. Recordemos
que cuando hablamos de rotación, hemos de considerar a cada eje como si se tratara de una línea rígida alrededor de
la cual nuestro objeto gira. Esto es, si quisiéramos que el cubo se moviera como una peonza, tendriamos que hacerlo
rotar sobre su eje Y. Probad a cambiar "Vector.right * 15" por "Vector.up * 25". (Y si queremos que rote en dirección
opuesta, le anteponemos un signo - al Vector.)

AddRelativeTorque:

function AddRelativeTorque (torque : Vector3, mode : ForceMode = ForceMode.Force) : void

function AddRelativeTorque (x : float, y : float, z : float, mode : ForceMode = ForceMode.Force) : void

Como podréis suponer, esta función añade una fuerza de torsión relativa a las coordenadas locales/propias del
rigidbody. Es a AddTorque lo mismo que AddRelativeForce a AddForce.

AddForceAtPosition:

Function AddForceAtPosition (force : Vector3, position : Vector3, mode : ForceMode = ForceMode.Force) : void

Aplica fuerza a un rigidbody en una posición determinada.

AddExplosionForce:

Function AddExplosionForce (explosionForce : float, explosionPosition : Vector3, explosionRadius : float,


upwardsModifier : float = 0.0F, mode : ForceMode = ForceMode.Force) : void

Aplica una fuerza al rigidbody que simula el efecto de una explosión. La fuerza de la explosión decaerá de manera lineal
con la distancia del rigidbody.

Esta función también funciona bien con objetos inertes. Si el radio es 0, la fuerza plena será aplicada sin importar cuan
lejos se produzda la explosión del rigidbody. UpwardsModifier aplica la fuerza como si fuera aplicada desde debajo del
objeto.

MovePosition:

Function MovePosition (position : Vector3) : void

Mueve el rigidbody de posición. Por ejemplo:


private var velocidad : Vector3 = Vector3 (3, 0, 0);
function FixedUpdate () {
rigidbody.MovePosition(rigidbody.position + velocidad * Time.deltaTime);
}

En este script declaramos primero una variable de tipo Vector3. Le anteponemos la palabra clave "private", que implica
que dicha variable no será accesible desde el inspector (no quedará expuesta). Se declara como privada una variable
que no queremos que se modifique desde la interface.

El resto del script no merece mucha explicación. Se suma el Vector3 con la velociadd a la posición del rigidbody, y para
que el desplazamiento se produzca en segundos y no en porciones de frames, lo multiplicamos por Time.deltaTime.
MoveRotation:

function MoveRotation (rot : Quaternion) : void

Rota el rididbody.

Si observamos el prototipo de la función, vemos que hemos de pasarle a la misma un parámetro de tipo Quaternion. Ya
hemos dicho en otras ocasiones que resulta bastante complejo para los humanos trabajar con cuaterniones, razón por
la cual por norma general trabajaremos en grados Euler se los pasaremos a la función convenientemente traducidos.

Un ejemplo:
private var velocidadEnAngulos : Vector3 = Vector3 (0, 100, 0);
function FixedUpdate () {
var velocidadEnCuaternionesPorTiempo : Quaternion =
Quaternion.Euler(velocidadEnAngulos * Time.deltaTime);
rigidbody.MoveRotation(rigidbody.rotation * velocidadEnCuaternionesPorTiempo);
}

Como podemos comprobar, meramente tenemos que usar la función Quaternion.Euler, que recibe grados euler como
parámetro y devuelve cuaterniones. El resto es sencillo.

25. CLASE COLLIDER (I)

Esta es la clase base para todos los tipos de colliders (que en castellano traduciríamos por "colisionadores"). Las clases
BoxCollider, SphereCollider, CapsuleCollider y MeshCollider derivan de ella.

Un collider vendría a ser la estructura que hace sólidos a los objetos. Seleccionemos en Unity la esfera. En el inspector
observamos que tiene un Sphere Collider, que no es más que un collider con forma de esfera. Si desmarcamos el
checkbox de dicho collider y le damos al play, automáticamente la esfera deja de ser sólida y, por efecto de la gravedad
que le da el rigidbody, atraviesa el suelo y cae.

Podemos seleccionar el cubo. Obviamente, el cubo no tiene una sphere collider, sino una box collider. Y si importáramos
un gameobject capsule, tendría un capsule Collider.

El problema viene cuando importamos un game object que no tiene una de estas formas primitivas. A veces se puede
"encajar" una capsule collider en un árbol, o una box collider en un coche. A veces no necesitamos que el colisionador
de un objeto coincida al 100% con dicho objeto y uno de estos colliders básicos nos pueden hacer el apaño.

Pero hay veces en que, bien por la importancia del game object en el juego, bien por la forma compleja que tiene dicho
game object, bien en la mayoría de casos por ambas cosas (pensemos en el ninja protagonista de nuestro juego, por
ejemplo) necesitamos un collider que sea completamente fiel a la forma del gameobject. Para ello tenemos el mesh
collider (que estudiaremos en breve), que es meramente la malla del objeto convertida en la estructura sólida del mismo
que interactúa con el resto del mundo.

Existe bastante confusión entre los nuevos en Unity respecto la diferencia entre un collider y un rigidbody. Un collider,
como decimos, es meramente la superficie de nuestro objeto. Un rigidbody en cambio implica la aplicación de las leyes
físicas a dicho objeto. Un objeto puede tener un collider y no un rigidbody (chocará con otros objetos, aunque no
podremos controlar sus reacciones a las colisiones y será Unity quien se encargue de ellas automáticamente), y puede
tener un rigidbody y no un collider (aunque entre otras cosas tendremos que desactivarle la gravedad, para que no
atraviese los suelos). Obviamente, un objeto puede tener un rigidbody y un collider, y de hecho Unity recomienda que si
tu objeto es previsible que vaya a intervenir en muchas colisiones, además de un collider es recomendable añadirle un
rigidbody kinemático.

Vamos a ello:

VARIABLES:

enabled:

var enabled : boolean

Si el collider está habilitado (true), colisionará con otros colliders. Se corresponde al checkbox que está en el inspector
en el apartado del collider.

attachedRididbody:

var attachedRigidbody : Rigidbody

Esta variable hace referencia al rigidbody vinculado a este collider, y permite acceder a él. Devuelve nulo si el collider no
tiene rigidbody.

Los colliders son automáticamente conectado al rigidbody relacionado con el mismo game object o con algún game
object padre. Esto es, por ejemplo el rigidbody que le añadimos en su momento al cubo automáticamente quedó
vinculado al box collider de éste. Probémoslo rehaciendo MiPrimerSript:
function FixedUpdate() {
collider.attachedRigidbody.AddForce(0,15,0);
}

Salvamos, nos aseguramos de que el script siga vinculado al cubo, y le damos al play.

isTrigger:

var isTrigger : boolean


Si esta variable está habilitada (true) el collider se convierte en un trigger (lo podríamos traducir por "desencadenante" o
"disparador"). Un trigger no colisiona con rigidbodies, y en su lugar envía los mensajes OnTriggerEnter, OnTriggerExit y
OnTriggerStay (que estudiaremos al final de la clase) cuando un rigidbody entra o sale de él. De esta manera nos
permite a nosotros diseñar de manera específica su comportamiento o las consecuencias de entrar en contacto con un
trigger (pensemos en una puerta que nos lleva a otra dimensión, que se puede atravesar pero dispara un evento que
nos teletransporta, por ejemplo)

material:

var material : PhysicMaterial

El material usado por el collider. Si el material es compartido por varios colliders, al ser asignado a esta variable se hará
una copia de dicho material que le será asignada al collider.

La variable es del tipo PhysicMaterial, que como en su momento estudiaremos es una clase que nos permite manejar
aspectos de los materiales como la fricción o la capacidad de rebote y el grado de la misma.

sharedMaterial:

var sharedMaterial : PhysicMaterial

El material compartido de este collider. Modificando este material cambiaremos las propiedades de la superficie de
todos los colliders que estén usando el mismo material. En muchos casos es preferible modificar en su lugar el
Collider.material.

bounds:

var bounds : Bounds

Los límites/bordes del collider en coordenadas globales.

27. CLASE MESHCOLLIDER


La clase MeshCollider, como vemos, hereda de la recién estudiada Collider, razón por la que hereda todas sus
variables, funciones y mensajes que envía, amén de hacer lo propio con las clases Component y Object.

Una MeshCollider (o colisionador de malla), tal como dijimos no hace mucho, es meramente la malla de un gameobject
que convertimos en collider, por norma general porque nuestro gameobject tiene una forma en la que difícilmente se
puede encajar una malla primitiva.

Como ventajas de usar esta opción, obviamente que habrá una mayor coherencia entre la apariencia del gameobject y
su superficie de colisión. Como mayor desventaja, el aumento de consumo para que nuestro ordenador haga los
pertinentes cálculos para cada plano de nuestra malla.

VARIABLES:

sharedMesh:

var sharedMesh : Mesh

Hace referencia al objeto malla que nuestro gameobject está usando como detector de colisiones, si lo hay. Nos permite
consultar sus características y/o asignar uno. La variable es de tipo mesh (malla) y la estudiaremos más adelante.

convex:

var convex : boolean

Usa un collider convexo para la malla, esto es, con esta variable establecida en true nuestro collider de malla
automáticamente pasa a ser convexo y todas las entradas y agujeros que pudiera tener nuestra malla desaparecen,
permitiendo una detección de colisiones mucho más completa. Las mallas convexas pueden colisionar con otros
colliders convexos y no convexos. Por lo tanto los colliders de malla convexos son apropiados para rigidbodies en el
caso de que necesitáramos colliders con formas más detalladas que los colliders primitivos.
transform.collider.convex = true;

smoothSphereCollisions:
var smoothSphereCollisions : boolean

Usa normales interpolados para colisiones de esferas en lugar de normales planos poligonales. Esto suaviza los baches
para rodamientos de esferas sobre superficies suaves.

28. CLASE CHARACTERCONTROLLER (I)

Un character Controller nos permite hacer fácilmente movimientos limitados por colisiones sin tener que lidiar con un
rigidbody.

Comprobémoslo empíricamente, siguiendo estos pasos:


1.- Le borramos a la esfera el script que tiene vinculado.
2.- Le eliminamos a la esfera su componente rigidbody (botón derecho del ratón
situado sobre el nombre de dicho componente=> remove component)
3.- Con la esfera seleccionada, vamos al menú Component=>Physics=>Character
Controller. Nos saldrá un aviso preguntándonos si realmente queremos sustituir
el collider primitivo que por defecto trae la esfera por un Character
collider. Presionamos Replace.
4.- En Jerarquía seleccionamos suelo, y en el inspector desmarcamos el checkbox
situado junto a Mesh Collider.
5.- Play.

El cubo, afectado por las leyes físicas, atraviesa el suelo al que le hemos deshabillitado la malla. Sin embargo el cubo,
que ahora está controlado por el character controller, no se ve afectado por las leyes físicas.

Antes de seguir, volvamos a habilitar la malla del suelo en su correspondiente checkbox.

Un character Controller no se ve afectado tampoco por fuerzas y se moverá sólo cuando llamemos a la función Move,
específica de esta clase.
VARIABLES:

isGrounded:

var isGrounded : boolean

Booleano que devuelve true si nuestro character controller (controlador de personaje, podríamos traducirlo) estaba
tocando el suelo durante el último movimiento.

velocity:

var velocity : Vector3

La velocidad relativa actual del character. La velocidad así retornada es la diferencia durante la última actualización
entre la distancia antes y después de llamar a la función Move o SimpleMove, esto es, dónde estaba el último frame, o
la última porción de frame o el último segundo el character antes de llamar a una de esas funciones y dónde estába
despues, para que distancia partida por (tiempo, frame o fracción dada) sea igual a la velocidad por tiempo, frame o
fracción dada.

Decimos que la velocidad es relativa porque no puede seguir movimientos del transform que suceden fuera del
character controller (por ej un character emparentado bajo otro transform en movimiento, como por ejemplo un vehículo
moviéndose)

collisionFlags:

var collisionFlags : CollisionFlags

Esta variable nos indicá qué parte de la cápsula (que es la forma que tiene un character controller) colisionó con el
entorno durante la última llamada a CharacterController.Move.

Comprobamos que la variable es de tipo CollisionFlags, que tal como podemos sospechar es de tipo enumeración,
permitiéndonos usar los siguientes valores:

None: No hay colisión.


Sides: Colisión en los lados.
Above: Colisión en la parte superior.
Bellow: Colisión en la parte inferior.

Así, un sistema para determinar si nuestro character controller ha topado con algo, y en qué parte de la cápsula, sería
como sigue (no hace falta que tecleéis esto, es sólo a modo indicativo):
function Update () {

var controller : CharacterController = GetComponent(CharacterController);


controller.Move(Vector3(1,0,0));

if (controller.collisionFlags == CollisionFlags.None)
print("No hay colisiones");

if (controller.collisionFlags & CollisionFlags.Sides)


print("Colisión lateral, al menos");

if (controller.collisionFlags == CollisionFlags.Sides)
print("Sólo colisión lateral, ninguna de otro tipo");
if (controller.collisionFlags & CollisionFlags.Above)
print("Colisión superior, al menos");

if (controller.collisionFlags == CollisionFlags.Above)
print("Sólo colisión superior, ninguna de otro tipo");

if (controller.collisionFlags & CollisionFlags.Below)


print("Tocando tierra");

if (controller.collisionFlags == CollisionFlags.Below)
print("Sólo tocando tierra, nada más");
}

Le añadimos el script a la esfera (si no lo tenía ya vinculado) y le damos al play.

La esfera se mueve hacia la derecha, hasta que impacta con el cubo (démonos cuenta que la esfera ya no tiene un
ridigbody, pero gracias al character controller sigue colisionando con otros colliders. Debajo de la ventana game aparece
impresa la última colisión, pero no es la única. Si le hacemos click al mensaje impreso nos saldrá una lista de todas las
colisiones que ha sufrido nuestra esfera hasta topar con el cubo.

La función GetComponent que hemos tecleado al inicio del script aún no la hemos estudiado, pero su cometido es
bastante obvio: buscar el componente del gameobject que hace la llamada y que sea del tipo que le pasamos como
parámetro, retornándolo. En este caso, nos sirve para inicializar la variable controller, con la que trabajaremos para
montar un primitivo sistema de identificación de colisiones.

la función Print es similar a Debug.Log, y la función Move la estudiaremos durante el próximo capítulo.

29. CLASE CHARACTERCONTROLLER (II)

radius:

var radius : float

El radio de la cápsula del character controller.

height:

var height : float

La altura de la cápsula del character controller.

center:

var center : Vector3


El centro de la cápsula del character relativo a la posición del transform.

slopeLimit:

var slopeLimit : float

El límite de pendiente en grados por el que puede ascender nuestro character controller.

Vamos a probar esta variable. Sigue estos pasos:

1.- Seleccionamos el cubo y lo desplazamos a la derecha (transform.x = 3, por


ejemplo)
2.- Vamos al menú=>GameObject=>create other=>Cube.
3.- En el apartado transform, le damos al nuevo cube estos valores:
position = 0,2,0
rotation = 0,0,315
scale = 0.2,7,1

Nos debería haber quedado algo así:

Vale, ahora tenemos una pasarela con una inclinación de 45 grados (360-315), por lo tanto, que nuestra esfera la pueda
subir o no depende del valor que le demos a slopeLimit. Así, si tecleamos:
function Update () {
var controller : CharacterController = GetComponent(CharacterController);
controller.slopeLimit = 46.0;
controller.Move(Vector3(1,0,0));
}

Nuestra esfera sí ascenderá por la rampa, ya que el grado de inclinación de esta es inferior al que puede asumir nuestro
character controller. Pero le asignamos a slopeLimit el valor 45.0. observaremos que ya la esfera se niega a subir.

stepOffset:

var stepOffset : float

Los límites de altura que el character controller podrá superar, fijados en metros.(no podrá por ejemplo subir una
escalera si cada escalón mide más de ese límite)

Para trabajar con un ejemplo, vamos a seleccionar el gameobject que nos sirvió como rampa en el ejemplo anterior. Le
establecemos estos valores:
position: 0,0,0
rotation: 0,0,90
scale: 0.6,3,1

Por otro lado, a nuestro cubo le vamos a aumentar momentáneamente la altura (scale.y = 4).

Y tecleamos el siguiente código para nuestra esfera:


function Update () {
var controller : CharacterController = GetComponent(CharacterController);
controller.stepOffset = 0.3;
controller.Move(Vector3(1,0,0));
}

Tomemos nota de que la antigua rampa tiene una altura de 0.6. En cambio, a nuestro character controller le hemos
limitado la capacidad de superar alturas mayores de 0.3, por lo que cuando le demos al play...la esfera topará con la
rampa. En cambio, si variamos el valor de stepOffset y lo colocamos, por ejemplo, a 0.9, la esfera no tendrá problemas
en superar la rampa, aunque tras hacerlo topará con el cubo.

Por experiencia personal, las mediciones de esta variable no siempre resulta exactas, ya que no todos los gameobjects
se asientan completamente y de manera exacta sobre el suelo, o éste presenta desniveles. Para evitar este tipo de
inexactitudes es necesario probar el comportamiento de los objetos y modificar stepOffsset en consecuencia, hasta dar
con el comportamiento adecuado.

Borramos la rampa y devolvemos al cubo a sus dimensiones habituales y a su posición 0,0,0.

detectCollisions:

var detectCollisions : boolean

Determina si otros rigidbodies o character controllers colisionan con nuestro character Controller (por defecto esto está
siempre habilitado).

Esta variable no afecta a las colisiones que nuestro character controller sufre cuando se mueve, sino las que sufre a raíz
del movimiento de otros character controllers o colliders, y en concreto a si esos colliders o character controllers
entrantes deben ser bloqueados por nuestro controller.

Para este ejemplo tenemos que escribir dos scripts. Abrimos MiPrimerScript, que deberíamos tener vinculado a nuestra
esfera, y tecleamos:
function Update () {
var controller : CharacterController = GetComponent(CharacterController);
controller.detectCollisions = false;
}

Aquí meramente le decimos a nuestro character controller que no queremos que bloquee los colliders entrantes. Y
ahora abrimos MiSegundoScript y escribimos:
function FixedUpdate() {
rigidbody.AddForce(-10,0,0);
}

Le asignamos este segundo script al cubo, de tal forma que se desplazará hacia la izquierda, hasta colisionar con la
esfera. Es preferible colocar la escena en vista superior (eje Y del gizmo). Le damos al play.

Y tal como era de esperar, nuestra esfera no bloquea al cubo. Eso sí, si lo hubiéramos hecho al revés (mover la esfera
hacia el cubo) aunque la variable detectCollisions estuviera en false, se produciría la colisión y el consiguiente bloqueo.
Y en la próxima lección vamos a por las funciones de esta clase.

30. CLASE CHARACTERCONTROLLER (y III)

FUNCIONES:

Move:

function Move (motion : Vector3) : CollisionFlags

Esta función mueve un character controller en la dirección y velocidad establecida por el parámetro de tipo vector tres.
Dicho movimiento sólo será restringido por las colisiones que sufra el character.

Empecemos por un ejemplo básico. Eliminamos el script vinculado al cubo, y acto seguido modificamos el script
vinculado a la esfera, para que quede así:
private var miCharCol : CharacterController;
miCharCol = GetComponent(CharacterController);
function Update() {
miCharCol.Move(Vector3(1 * Time.deltaTime ,0,0));
}

No tiene mucho misterio. Almacenamos el componente character controller de nuestro gameobject en una variable (que
declaramos como privada para que no sea accesible desde el inspector), y acto seguido le damos un movimiento de
una unidad por segundo a la derecha. Pulsando el play efectivamente la esfera se desplaza hasta que topa con el
cubo.

El collider del cubo en este caso detiene el avance de la esfera, porque lo intercepta de pleno. Pero si al cubo lo
ubicamos en position.Z= 0.8 y volvemos a darle al play, observamos que la esfera no detiene su avance. Choca y se
desplaza de manera acorde a la colisión, pero acto seguido continúa avanzando hacia la derecha.

La función devuelve una variable de tipo collisionFlags, que estudiamos hace un par de capítulos, y que aporta
información sobre la ubicación de las colisiones que ha sufrido el character controller. Con esa información, podemos
alterar el comportamiento de nuestro character controller cuando sufra una colisión en una zona determinada de su
cápsula.

Ampliamos el script anterior, para que quede así:


private var miCharCol : CharacterController;
miCharCol = GetComponent(CharacterController);
private var direccion : Vector3 = Vector3.right;
function Update() {
var misGolpes : CollisionFlags = miCharCol.Move(direccion * Time.deltaTime);
if(misGolpes == CollisionFlags.Sides)
{
direccion = Vector3(0,0,-1);
}
}

La esfera empieza su desplazamiento hacia la derecha (que es la dirección inicial que se le asigna a la función Move),
hasta que colisiona con el cubo. Al hacerlo, nuestro condicional "if" detecta si la colisión se ha producido en uno de los
laterales de la cápsula de nuestro character controller (CollisionFlags.Sides) y, para ese caso, se le pasa un nuevo
vector3 a la dirección de la esfera.

SimpleMove:

function SimpleMove (speed : Vector3) : boolean

Podríamos deducir que esta función es similar a la anterior, y así es, pero con algunas peculiaridades. Por ejemplo, si a
nuestra esfera le asignamos el siguiente script...
function Update() {
var miCharCol : CharacterController = GetComponent(CharacterController);
miCharCol.SimpleMove(Vector3(1 * Time.deltaTime, 0,0));
}

...podríamos esperar que la esfera se moviera hacia la derecha, pero se da el caso de que a la que presionamos play la
esfera atraviesa el suelo y cae.

Esto es porque esta función automáticamente le asigna gravedad al character controller que la llama. Por otro lado, esta
función no devuelve información sobre el lugar de contacto en que se ha producido las colisiones.

31. CLASE RENDERER (I)

Un render vendría a ser el proceso por el que un ordenador muestra una imagen. De ahí que sea incuestionable la
importancia de esta clase dentro de Unity. Tanto los gameobjects como algunos componentes tiene una propiedad
renderer a la que podemos acceder/modificar, o que incluso se puede deshabilitar para hacer dicho gameobject o
componente invisible.

VARIABLES:

enabled:

var enabled : boolean

Hace el objeto visible (true) o invisible (false).


castShadows:

var castShadows : boolean

¿Proyecta sombras este objeto?

receiveShadows:

var receiveShadows : boolean

¿Recibe sombras este objeto?

material:

var material : Material

El material de nuestro objeto. Modificar esta variable sólo cambiará el material para este objeto. Si el material que
asignamos a nuestro objeto está siendo usado para otros renders, se clonará el material compartido y se asignará una
copia de dicho material para nuestro objeto.

La variable es de tipo Material,que estudiaremos en su momento, y que nos permite cambiar el color del objeto, su
textura y la manera en que reacciona a la luz, entre otras propiedades.

Veamos un ejemplo sencillo. Le asignamos este script a la esfera:


renderer.material.color = Color.green;
renderer.material.shader = Shader.Find( "Transparent/Diffuse" );
renderer.receiveShadows = false;
Al darle al play, podemos observar el cambio de apariencia.

sharedMaterial:

var sharedMaterial : Material

Hace referencia al material que nuestro objeto comparte con otros. Modificar esta variabla cambiará la apariencia de
todos los objetos que usen este material y las propiedades del mismo que estén almacenadas en el proyecto también,
razón por la que no es recomendable modificar materiales retornados por sharedMaterial. Si quieres modificar el
material de un renderer usa la variable “material” en su lugar.

sharedMaterials:

var sharedMaterials : Material[]

Devuelve un array con todos los materials compartidos de este objeto, a diferencia de sharedMaterial y material, que
sólo devuelven el primer material usado si el objeto tiene más de uno. Unity soporta que un objeto use múltiples
materiales.

Al igual que la variable anterior y por la misma razón, no es aconsejable modificar materiales devueltos por esta variable

materials:

var materials : Material[]

Devuelve un array con todos los materiales usados por el renderer.

bounds:

var bounds : Bounds

Es una variable de sólo lectura que indica los límites del volumen del renderer. A cada renderer Unity le asigna una caja
invisible que contiene (que envuelve) al objeto y cuyos bordes están alineados con los ejes globales. De esta manera a
Unity le resulta más sencillo hacer cálculos de desplazamiento y situación. Por ejemplo, renderer.bounds.center
normalmente es más preciso para indicar el centro de un objeto que transform.position, especialmente si el objeto no es
simétrico. Pensemos, para aproximarnos intuitivamente al concepto, en esas rosas que vienen dentro de cajas
transparentes de plástico.

En cualquier caso, la estructura Bounds la estudiaremos más adelante.

lightmapIndex:

var lightmapIndex : int

El índice del lightmap aplicado a este renderer. El índice se refiere al array de lightmaps que está en la clase
LightmapSettings. Un valor de 255 significa que no se ha asignado ningún lightmap, por lo que se usa el que está por
defecto.

Una escena puede tener varias lightmaps almacenados en ella, y el renderer puede usar uno o varios. Esto hace
posible tener el mismo material en varios objetos, mientras cada objeto puede referirse a un ligthmap diferente o
diferente porción de un lightmap.

isVisible:

ar isVisible : boolean

Variable de sólo lectura que indica si el renderer es visible en alguna cámara.

Hay que tener presente que un objeto es considerado visible para Unity cuando necesita ser renderizado en la escena.
Podría por lo tanto no ser visible por ninguna cámara, pero todavía necesitar ser renderizado (para proyectar sombras,
por ejemplo).

32. CLASE RENDERER (y II)

FUNCIONES:

OnBecameVisible:

function OnBecameVisible () : void

Esta función (del tipo mensaje enviado) es llamada cuando el objeto se vuelve visible para alguna cámara. Este
mensaje es enviado a todos los scripts vinculados al renderer. Es útil para evitar cálculos que son sólo necesarios
cuando el objeto es visible.

Recordemos que la sintaxis de este tipo de funciones es distinta, así que si quisiéramos por ejemplo que un gameobject
fuera visible cuando fuera a salir en una cámara, escribiríamos esto:

function OnBecameVisible() {
enabled = true;
}

OnBecameInvisible:

function OnBecameInvisible () : void

Es llamada cuando el objeto ya no es visible por ninguna cámara.

33. CLASE MESHFILTER


Un mesh filter (o filtro de malla) toma una malla de la carpeta de assets y se la pasa a mesh renderer para renderizarla
en la pantalla. Es, para entendernos, la estructura de alambre de la malla.

VARIABLES:

mesh:

var mesh : Mesh

Devuelve la instancia de malla asignada al mesh filter. Si no se ha asignado ninguna malla al mesh filter se creará y
asignará una nueva.

Si la malla asignada al mesh filter es compartida por otros objetos, automáticamente se duplicará y la malla instanciada
será retornada.

Usando las propiedades de malla puedes modificar la malla para un solo objeto solamente. Los otros objetos que usan
la misma malla no serán modificados.

Cuando estudiemos la clase Mesh aprenderemos las diferentes posibilidades de alteración y deformación de mallas que
esto nos permite.

sharedMesh:

var sharedMesh : Mesh

Devuelve la malla compartida del mesh filter.

Es recomendable usar esta función sólo para leer datos de malla y no para escribirlos, dado que podrías modificar los
assets importados y todos los objetos que usan esta malla podrían ser afectados. Tengamos en cuenta también que no
será posible deshacer los cambios hechos a esta malla.

34. CLASE JOINT (I)


Es la clase base para todos los tipos de joints. Por clase base queremos decir que no podemos instanciarla como tal,
sino que sirve para ser heredada por diferentes tipos de joint (hingejoint, springjoint, characterjoint) que estudiaremos a
continuación.

Podríamos traducir joint por juntura o articulación, pero en principio usaremos la palabra en inglés. No obstante, tal
como denota el significado en castellano de joint, podemos deducir que esta clase sirve para unir varios objetos de una
manera u otra (pensemos en una puerta unida por una bisagra, en las articulaciones que mantienen unidos los huesos,
o meramente en una pared de objetos-ladrillo.)

VARIABLES:

connectedBody:

var connectedBody : Rigidbody

Esta variable referencia a otro rigidbody con el que este nuestra variante de joint conecta. Si la variable es null, el joint
conecta el objeto con el mundo, esto es, en lugar de estar vinculado a otro rigidbody, nuestro objeto quedará "clavado"
en su lugar en la escena.
axis:

var axis : Vector3

El eje altededor del cual el movimiento del rigidbody estará restringido, indicado en coordenadas locales.

anchor:

var anchor : Vector3


La posición del ancla alrededor de la cual el movimiento de los joints está restringido. La posición es definida en espacio
local.

En la próxima lección realizaremos una serie de ejemplos que nos permitirá comprender con mayor claridad la
funcionalidad de estas variables.

35. CLASE JOINT (y II)

breakForce:

var breakForce : float

La fuerza que es necesario aplicarle a este joint para romperlo. La fuerza podría venir por colisiones con otros objetos,
fuerzas aplicadas con rigidbody.AddForce o por otros joints.

Vamos a realizar una serie de ejemplos que incluirán tanto las variables vistas en la lección anterior como en ésta. Para
ello vamos a seguir los siguientes pasos:

1.- Eliminamos en el inspector el hingejoint de la esfera, y a ésta le añadimos un


sphere collider. La ubicamos en la posición (-1,0,-5). Le eliminamos el script que
tenía vinculado.
2.-Vamos al menú gameobject=>Create other=>Cube. Ubicamos este cube en (-1,0,0) y
le añadimos un hingejoint.
3.-Editamos el script MiPrimerScript como sigue:
var pegadoATi : Rigidbody;
hingeJoint.connectedBody = pegadoATi;
hingeJoint.breakForce = 20;

4.-Asignamos MiPrimerScript al recién creado gameobject Cube, y luego arrastramos


nuestro cubo a la variable pegadoATi.
5.-Editamos ahora MiSegundo script, tecleando:
function FixedUpdate() {
rigidbody.AddForce(0,0,9);
}

6.-Este script se lo asignamos a la esfera.

Bueno, pues vamos ahora a jugar a los bolos más rupestres de la historia. Tenemos por un lado dos cubos unidos por
un joint (el cubo que tiene el hinge joint y el cubo unido a éste primero a través de la variable connectedBody), con una
fuerza de ruptura de 20, y una esfera dispuesta a chocar contra el cubo situado a nuestra izquierda con una fuerza de 9.
Démosle al play y probemos qué sucede.

Está claro que es mayor la resistencia del cubo que la fuerza de la esfera. Veamos si la variable breakForce ha tenido
algo que ver en dicha resistencia cambiando su valor de 20 a 4. Play.

Observamos que -aunque con algo de esfuerzo- ahora sí la esfera rompe el joint y consigue arrastrar el cubo de la
izquierda (el portador del hingejoint).

Hagamos ahora unas pequeñas modificaciones: Ubicamos la esfera en (0,0,-5) para que nos vaya a impactar al cubo
que no tiene asignado el hingejoint.

Para hacer un acercamiento intuitivo al ejercicio, podemos pensar que el cubo situado a nuestra izquierda (cube) sería
el marco y el de nuestra derecha (nuestro cubo) la puerta. Visto así, ahora vamos a intentar impactar nuestra esfera con
la puerta.

Restablecemos la variable breakForce a 20. Le damos al play. Veremos que la esfera arrastra al cubo (abre la puerta),
pero no rompe su unión con el otro cubo. Cambiemos de nuevo la variable breakForce de valor, pongámmosle un 1. De
nuevo play. Observad cómo la esfera no tiene problemas en romper la juntura.

Pero vamos a seguir jugando con las variables que hemos ido viendo en estos dos capítulos. Antes que nada
desplazaremos un poco nuestra esfera para que sólo golpee en el cubo que no tiene el hinge joint, así que ubicamos la
esfera en (0.5,0,-5)

Vamos a añadir luego la variable axis a MiPrimerScript, de la siguiente manera:


var pegadoATi : Rigidbody;
hingeJoint.connectedBody = pegadoATi;
hingeJoint.axis = Vector3.up;
hingeJoint.breakForce = 20;

Recordemos que axis indica el eje alrededor del cual el movimiento de nuestro rigidbody portador del hingejoint
permanecerá bloqueado. Por lo tanto, si bloqueamos el eje up/down a través de la variable axis, teóricamente el cubo
de nuestra izquierda no debería girar sobre dicho eje. Como truco os recomendaría que antes de darle al play
seleccionárais en el inspector el Cube, para que veáis la flecha naranja saliendo del gameobject. Al darle al play, la
flecha señalará en la dirección del eje indicado por la variable axis.

Pulsamos el play, decimos, y tal como esperábamos, la esfera pasa a través del cubo de nuestra derecha, pero el que
tiene el hinge joint no gira sobre su eje vertical. La diferencia resulta muy clara si cambiamos en lugar de Vector3.up
colocamos Vector3.forward como valor de axis. probadlo sin miedo.

Probemos ahora la variable anchor, que tal como indicábamos indica la posición del ancla alrededor de la cual se
restringe el movimiento de los joints. Probemos a dejar MiPrimerScript así:
var pegadoATi : Rigidbody;
hingeJoint.connectedBody = pegadoATi;
hingeJoint.axis = Vector3.right;
hingeJoint.anchor = Vector3(0,0,0);
hingeJoint.breakForce = 20;

Hemos cambiado, como vemos, el eje que restringirá la variable axis, pasando del eje arriba/abajo al derecha/izquierda.
Ubicamos la variable anchor en las coordenadas locales de nuestro hingejoint (0,0,0). Démosle al play y observemos
por un lado la ubicación de la flecha naranja (recordemos previo pulsar play dejar seleccionado Cube) y por otro el
comportamiento de ambos cubos al ser impactados por la esfera.

Vale. Ahora vamos a modificar la ubicación del ancla. Dejémosla así:


hingeJoint.anchor = Vector3(0,-0.5,0.5);

Eso significa (recordemos que estamos en coordenadas locales del cube) que el ancla estará situada en el centro del
cube respecto del eje X (0), en la parte inferior del cube respecto del eje Y (-0.5) y en la parte trasera del cube respecto
al eje Z (0.5). Démosle al play y observemos la situación del ancla y la diferencia de comportamiento.

breakTorque:

var breakTorque : float

La torsión que es necesario aplicar a este joint para romperlo.

FUNCIONES:

OnJointBreak:

function OnJointBreak (breakForce : float) : void

Función de tipo "mensaje enviado" que es llamada cuando un joint vinculado al mismo game object se rompe. Cuando
una fuerza que es mayor que el breakForce del joint es aplicada, el joint se romperá. En ese momento, esta función
será llamada y el la fuerza de ruptura aplicada al joint le será pasada a la misma. Después de OnJointBreak el joint
automáticamente será borrado del game object.

Adaptemos MiPrimerScript para explicar esta función:


var pegadoATi : Rigidbody;
hingeJoint.connectedBody = pegadoATi;
hingeJoint.breakForce = 1.0;
function OnJointBreak(resistencia : float) {
Debug.Log("Se nos rompió el joint con resistencia " + resistencia);
}

El script es bastante sencillo. Le aplicamos a nuestro hingejoint una fuerza de ruptura baja, para asegurarnos de que el
impacto de la esfera lo romperá. Cuando esto suceda será llamada la función OnJointBreak, que en este caso imprimirá
un mensaje en el que, además de un texto, mostrará el valor que tendrá tras la ruptura la variable que le pasamos como
parámetro, y que coincidirá con la fuerza de ruptura previamente asignada.

Bueno, pues explicada la clase base, iremos a por las diferentes clases que heredan de joint.

36. CLASE HINGEJOINT (I)


Esta clase agrupa juntos 2 rigidbodies, constreñiéndolos a moverse como si estuvieran conectados por una bisagra.

Este tipo de joints es perfecto para puertas, pero también puede usarse para modelar cadenas, por ejemplo.

VARIABLES:

motor:

var motor : JointMotor

La variable motor aplicará una fuerza en aumento hasta conseguir la velocidad deseada en grados por segundo. Esta
variable es de tipo JointMotor, que es una estructura que a su vez tiene las siguientes variables:

targetVelocity: Es la velocidad de desplazamiento o rotación que se pretende


conseguir.
force: Es la fuerza que aplicará el motor para conseguir la velocidad fijada en
targetVelocity.
freeSpin: Es un booleano. Si está habilitado, el motor sólo acelerará y nunca
reducirá.

Si unimos intuitivamente los conceptos junturas y motor, posiblemente se nos vengan a la cabeza engranajes, vehículos
o maquinarias. Esa intuición no va muy desencaminada. Hagamos las siguientes modificaciones para prepararnos el
terreno.

Eliminamos el script vinculado a la esfera. Luego vamos a colocar a nuestros cubos siameses (cubo y cube) dos
unidades sobre tierra, lo que obviamente conseguiremos colocando su posición (la de los dos) en el eje X en 2. En
ambos casos, además y para evitar que se nos caigan al suelo por la gravedad, en sus respectivos constrainst
marcaremos Freeze Position Y.

Ahora editamos el script MiPrimerScript (que si hemos seguido las lecciones al pie de la letra debemos tener vinculado
a Cube), para que luzca así:
var pegadoATi : Rigidbody;
hingeJoint.connectedBody = pegadoATi;
hingeJoint.anchor = Vector3(0,0,0);
hingeJoint.motor.force =100;
hingeJoint.motor.targetVelocity = 90;
hingeJoint.motor.freeSpin = true;
Resumamos lo que hemos hecho: El rigidbody del segundo cubo (el que no tiene asignado un hingeJoint) es asignado a
la variable pegadoATi, que posteriormente es conectada al hingeJoint. El ancla entre ambos cubos (que por defecto se
ubica en el eje X) la colocamos en el centro geométrico del cubo, para que el giro que pretendemos sea más natural.
Luego aplicamos un motor al hingejoint con una fuerza de 100 hasta conseguir alcanzar una velocidad de 90 grados por
segundo, y sin que el motor pueda reducir.

Démosle al play para comprobar que funciona.

El motor trata de alcanzar la velocidad angular de motor.targetVelocity en grados por segundo. El motor sólo será capaz
de alcanzar motor.targetVelocity si motor.force es suficientemente grande. Si el joint está girando más rápido que el
motor.targetVelocity el motor se romperá. Un velor negativo para motor.targetVelocity hará que el motor gire en dirección
contraria.

El motor.force es la torsión máxima que el motor puede ejercer. Si es cero el motor se deshabilita. El motor frenará
cuando gire más rápido que motor.targetVelocity sólo si motor.freeSpin es false. Si motor.freeSpin es true el motor no
frenará.

37. CLASE HINGEJOINT (y II)

limits:

var limits : JointLimits

Variable que establece/indica los límites de nuestro hingejoint (recordemos, para que tengamos más o menos claro a
qué nos referimos con lo de límites, que hinge lo podemos traducir por "bisagra").

La variable es del tipo JointLimits, que es una estructura que a su vez tiene las siguientes cuatro variables:

min: Es el límite inferior del joint. Cuando el ángulo o posición del joint se
halla por debajo de este valor, el joint recibe la fuerza necesaria para
restablecerlo a ese mínimo.
max: El límite superior del joint. Si el ángulo o posición de éste está por
encima de este valor, el joint recibe fuerzas pra restablecerlo a dicho
máximo.
minBounce:La capacidad de retorno del joint a su límite inferior cuando es golpeado
por debajo de éste (pensemos en las puertas del Saloon.
maxBounce:Lo mismo que la anterior aplicado al límite superior.

El joint será limitado para que el angulo esté siempre entre limits.min y limits.max. Dicho ángulo se calcula en términos
relativos al ángulo existente al principio de la simulación, esto es, si entre dos rigidbodys hay ya al inicio un ángulo de
20º, si su variable limits.min la fijamos en 10, el ángulo real será de 30º

Vamos a trastear un rato, que es la mejor manera de quedarse con los conceptos. De entrada, vamos a devolver al
suelo a nuestros cubos, así que devolvemos sus transform.position.y respectivas a cero, y no nos olvidemos de
desmarcar la casilla de sus constraints que marcamos en el ejemplo anterior.
Por otro lado, el script MiSegundoScript, que en el anterior capítulo le habíamos sustraido a la esfera, se lo volvemos a
asignar, para que de nuevo impacte contra nuestros cubos. Le daremos algo más de fuerza (AddForce(0,0,20)).
Ubicaremos a la esfera en la posición 0.3,0,-5.

Ahora vamos a intentar convertir los cubos en lo más parecido a una puerta, y le vamos a dar unos ángulos máximo y
mínimo de apertura. Para evitar que el cubo que tiene asignado el hinge joint (cube) se nos mueva demasiado le
asignaremos un valor a su variable mass en el rigidbody (en el inspector ) de 6.

Y tecleamos en MiPrimerScript:
var pegadoATi : Rigidbody;
hingeJoint.connectedBody = pegadoATi;
hingeJoint.axis = Vector3.up;
hingeJoint.anchor = Vector3(0.5,-0.5,0.5);
hingeJoint.limits.min = 0;
hingeJoint.limits.max = 70;
hingeJoint.limits.minBounce = 0;
hingeJoint.limits.maxBounce = 0;

Si lo probamos, veremos que al impacto de la esfera nuestro joint alcanza un grado máximo de 70 grados, sin rebote. Si
quisiéramos obtener un rebote de nuestra puerta precaria, podríamos asignarle a maxBounce un valor de 4, por
ejemplo.

spring:

var spring : JointSpring

Esta variable intenta alcanzar un ángulo que es su objetivo a base de añadir fuerzas de resorte y amortiguación.
Podemos traducir en este contexto spring como muelle, lo que nos da una idea del tipo de impulso del que estamos
hablando.

El tipo de esta variable es JointSpring, que tal como podemos suponer es de nuevo una estructura, que en este caso
consta de tres variables:

spring: La fuerza tipo resorte para alcanzar la targetPosition, el


ángulo deseado. Un valor más grande hace que alcancemos la
posición más rápido.
damper: La fuerza del amortiguador usada para amortiguar la velocidad
angular. Un valor más grande hace que alcance el objetivo más
lento.
targetPosition: La posición que el joint intenta alcanzar. El spring alcanza la
targetPosition en ángulos relativos al ángulo inicial.

Pongámoslo con un ejemplo. Para ello previamente eliminamos el script vinculado a la esfera. Y luego, MiPrimerScript
debería quedar como sigue:
var pegadoATi : Rigidbody;
hingeJoint.connectedBody = pegadoATi;
hingeJoint.axis = Vector3.up;
hingeJoint.anchor = Vector3(0.5,-0.5,0.5);
hingeJoint.spring.spring = 20;
hingeJoint.spring.damper = 6;
hingeJoint.spring.targetPosition = 90;

Aquí abriremos nuestra puerta precaria con un impulso inicial de 20 y una amortiguación de 6, tendiendo a alcanzar los
90 grados. Si le damos al play notaremos el evidente efecto catapulta.
useMotor:

var useMotor : boolean

Habilita/deshabilita el motor del joint.

useLimits:

var useLimits : boolean

Habilita/deshabilita los límites del joint.

useSpring:

var useSpring : boolean

Habilita/deshabilita el resorte del joint.

velocity:

var velocity : float

Devuelve la velocidad actual del joint en grados por segundo.

angle:

var angle : float

Devuelve el ángulo actual en grados del joint con respecto a la posición inicial. Es una variable de solo lectura.

38. CLASE SPRINGJOINT


El spring joint ata juntos dos rigid bodies que se encuentran a una cierta distancia. Las fuerzas spring (resorte) se
aplicarán automáticamente para mantener el objeto a la distancia dada, intentando mantener la distancia que había
cuando empezó. Las propiedades minDistante y maxDistance añaden unos límites de implícita distancia.

VARIABLES:

spring:

var spring : float

La fuerza del spring usada para mantener los dos objetos juntos.

damper:

var damper : float

La fuerza de amortiguación usada para amortiguar la fuerza del spring.

minDistance:

var minDistance : float

La distancia mínima entre los rigidbodies relativa a su distancia inicial. La distancia que habrá de mantenerse se
conservará entre minDistance y maxDistance. Ambos valores son relativos a la distancia entre los respectivos centros
de masa cuando la escena fue cargada al principio.

maxDistance:

var maxDistance : float

La distancia máxima entre los rigidbodies relativa a su distancia inicial.


Veamos todo esto con un ejemplo rápido, para lo cual previamente haremos las pertinentes modificaciones en la interfaz
de Unity.

Asignamos (una vez más) el script MiSegundoScript a la esfera.

Seleccionamos "Cube" en la jerarquía, y lo ubicamos en position.x = -3. Asimismo le eliminamos el componente


hingejoint y en components=>Physics=>spring joints le añadimos un spring. Observaremos que ahora el cube en lugar
de una flecha tiene un minicubo.

Y -ahora sí- remozamos MiPrimerScript:


var pegadoATi : Rigidbody;
var springJoint : SpringJoint;
springJoint = GetComponent(SpringJoint);
springJoint.connectedBody = pegadoATi;
springJoint.spring = 25;
springJoint.damper = 4;
springJoint.minDistance = 0;
springJoint.maxDistance = 0;

Veamos lo que hemos querido hacer. En primer lugar declaramos una variable (que la llamamos springJoint por claridad
didáctica, pero que hubiéramos podido darle otro nombre legal) de tipo SpringJoint y le asignamos el componente
SpringJoint de nuestro cube. Luego vinculamos el cubo a nuestro joint. Y a partir de ahí le damos al sprint/resorte una
fuerza de recuperación de 25, una de amortiguación de 4, y establecemos la distancia mínima y máxima la que ya
ambos cubos tenían al darle al play, esto es, 3 metros. Si quisiéramos que los cubos tras el impacto de la esfera
quedaran a más o menos distancia de la inicial retocaríamos respectivamente minDistance o maxDistance.

Dadle al play. Nuestro muelle retiene al cubo a la distancia estipulada del portador del spring joint. Podéis jugar con
spring y damper para que esa fuerza de tipo resorte sea más o menos acusada.

39. CLASE CHARACTERJOINT


Esta clase, como su nombre indica, es principalmente usada con personajes. En concreto, la utilidad básica de la misma
consiste en simular lo que se vienen llamando efectos Ragdoll (proceso de animación para simular muertes, valga la
aparente paradoja). Ragdoll podría traducirse como "muñeca de trapo", para que nos hagamos una idea del efecto que
se pretende conseguir.Esta especialidad de joint nos permite limitar el joint de cada eje.

VARIABLES:

swingAxis:

var swingAxis : Vector3

El eje secundario alrededor del cual el joint puede rotar. Los límites de rotación permitida alrededor de este eje vendrán
establecidos por CharacterJoint.swing1Limit.

lowTwistLimit:

var lowTwistLimit : SoftJointLimit

El límite menor del ángulo permitido alrededor del eje primario del characterjoint. El límite es relativo al ángulo de los
dos rigidbody con el que comenzó la simulación.

La variable es de tipo SoftJointLimit, que es una estructura con estos valores:

limit: El límite de posición u ángulo del joint.


spring: Si es mayor que cero, el límite es soft. El spring retornará
el joint a su posición inicial.
damper: Si spring es mayor que cero, el límite es soft.
bounciness: Cuando el joint golpea el límite, esta variable puede establecerse
para impedir un rebote.

highTwistLimit:
var highTwistLimit : SoftJointLimit

El límite superior altededor del eje primario del characterjoint. El límite es relativo al ángulo de los dos rigidbody con el
que comenzó la simulación.

swing1Limit:

var swing1Limit : SoftJointLimit

El límite alrededor del eje primario del characterjoint. El límite es simétrico, así que un valor de 30 limitaría la rotación
entre -30 y 30. El límite es relativo al ángulo de los dos rigidbodys con que empezó la simulación.

swing2Limit:

var swing2Limit : SoftJointLimit

El límite alrededor el eje primario del joint del character.

Lamentablemente, por la especificidad de esta clase en estos momentos no podemos utilizar ejemplos, pero cuando
estemos a un nivel más avanzado me comprometo volver a esta clase para verla con más profundidad y claridad (y si
yo me olvido, que alguien me lo recuerde, please)

40. CLASE BEHAVIOUR

Para comprender el sentido de la clase Behaviour he considerado que lo mejor era traer a la vista de nuevo el
organigrama general que estuvimos comentando en las primeras lecciones. Si os fijáis ya hemos estudiado todas las
clases que derivaban de Component, a excepción de ésta, de la que a su vez deriva otra serie de clases (que son las
que estudiaremos a continuación).

Explico todo esto porque la clase Behaviour, como tal, sólo cuenta con una variable (con independencia, obviamente, de
las que hereda de Object y Component). Dicha variable es

enabled:

var enabled : boolean

Como podemos imaginar, esta variable solamente habilita o deshabilita el objeto Behaviour (y/o los objetos derivados de
esta clase) de un gameobject. Dicho de otra forma, tenemos por un lado las clases que derivan directamente de
Component (las que ya hemos estudiado) y por otro las que derivan de Behaviour, que heredan lo mismo que las
anteriores, más una variable que les permite deshabilitarse. Esto es, la diferencia entre, por ejemplo, transform y
camera es que la segunda se puede desactivar. Esto lo podemos comprobar en el inspector, con la main camera
seleccionada: veremos que Camera cuenta con un checkbox (el equivalente a la variable enabled) y Transform no.

Os suelto este rollo para que entendáis que las clases que explicaremos a continuación son en esencia de raíz idéntica
a las últimas que hemos estudiado, con la peculiaridad indicada.

41. CLASE MONOBEHAVIOUR (I)

Como podréis deducir de un mero vistazo a las funciones contenidas en esta clase, estudiarla nos va a llevar un buen
rato. La clase Monobehaviour es uno de los miembros destacados del top ten de la API de Unity, así que es vital
dominarla con una cierta solvencia.

MonoBehaviour es la clase base de la que derivan todos los scripts. Ni más ni menos. Al ser clase base, salvo en C# no
hace falta escribirla expresamente cuando llamemos a alguna de sus funciones, pues Unity la da por sobreentendida.
Dicho de otra manera, para Unity es lo mismo esto
MonoBehaviour.Invoke()
que esto
Invoke()

Vamos ya a empezar con las funciones de MonoBehaviour:

FUNCIONES:

Invoke:

function Invoke (methodName : String, time : float) : void

Llama a la función que le pasemos como primer parámetro en el tiempo contado en segundos que le indiquemos como
segundo parámetro.

Aunque creo que el funcionamiento de esta función es bastante evidente, nada mejor que verlo con un ejemplo. Y
previamente a empezar a diseñar el mismo, vamos a reorganizar nuestra escena en la interfaz de Unity siguiendo estos
pasos:

1.- Eliminamos el Gameobject Cube.


2.- Ubicamos el Gameobject Cubo en -2,0,0.
3.- Eliminamos el script vinculado a la esfera.
4.- Colocamos la esfera en 2,0,0.
5.- Asignamos por la vía de arrastre MiPrimerScript en Project al GameObject
PortaScripts. Si por lo que sea no has seguido todas las lecciones y en la
jerarquía no tienes un gameobject con ese nombre, crea un Gameobject vacío y lo
bautizas como PortaScripts.
6.- Doble click en MiPrimerScript para abrir el editor y poder trabajar en él.

Tecleamos esto:
var original : GameObject;
function OtroDeLoMismo() {
Instantiate(original, original.transform.position + Vector3.right,
original.transform.rotation);
}
Invoke("OtroDeLoMismo", 5);

Seleccionamos PortaScripts y en el inspector arrastramos el cubo hasta el valor de la variable Original. Pulsamos el
play.

Transcurridos cinco segundos, observaremos que la función Invoke llama a la función OtroDeLoMismo, que a su vez
crea una instancia clonada de nuestro cubo una unidad a la derecha. Si queremos, podemos arrastrar la esfera a la
variable Original, para que sea ella la clonada.

Un problema que me he encontrado a la hora de hacer este ejemplo, es que no sé si Invoke funciona para llamar a una
función que tenga uno o más parámetros, y en caso afirmativo cómo se ha de añadir dicho parámetro al string. Si
alguien conoce la respuesta a esto agradecería lo reportara.

42. CLASE MONOBEHAVIOUR (II)


InvokeRepeating:

function InvokeRepeating (methodName : String, time : float, repeatRate : float) : void

Invoca la función que le pasamos como string para dentro del número de segundos que le pasamos como segundo
parámetro, y una vez se ejecuta, lo vuelve a hacer cada repeatRate segundos (el tercer parámetro.

Veremos claramente su funcionamiento rehaciendo ligeramente el ejemplo anterior (previamente es mejor que
coloquemos la esfera en position.Z = 3):
var original : GameObject;
function OtroDeLoMismo() {
var copia : GameObject = Instantiate(original, original.transform.position +
Vector3.right, original.transform.rotation);
copia.transform.position += Vector3(Time.time - 5, 0,0);
}
InvokeRepeating("OtroDeLoMismo", 5,1);

Arrastramos nuestro cubo para inicializar la variable "original" en el inspector, y le damos al play. Nuestro cubo, pasados
los 5 primeros segundos, empezará a clonarse cada segundo.

Vamos por partes: Lo que hemos hecho es primero declarar una función que -al igual que en el ejemplo anterior- clona
el objeto que le hayamos arrastrado a "original" y lo coloca una unidad a la derecha del objeto clonado. Asimismo, para
que no se nos amontonen los cubos en el mismo punto, aprovechamos que la función Instantiate devuelve el objeto
clonado, para almacenarlo en una variable -que llamamos copia- y lo que hacemos es que cada nuevo objeto
instanciado se mueva una unidad más a la derecha que el anterior, cosa que obtenemos aprovechando que Time.time
hace precisamente eso, avanzar una unidad cada segundo. Al resultado le restamos 5, que es el número de segundos
que transcurren hasta que empiezan a clonarse los cubos, por obra y gracia del segundo parámetro de
InvokeRepeating. Si no le restáramos ese 5, el primer muro se clonaría 5 unidades a la derecha del cubo clonado.

Observamos que, centrándonos en la función estudiada, que InvokeRepeating llama a la función transcurridos 5
segundos -en este caso- y pasados los mismos, se repite de forma infinita cada segundo (3er parámetro).

CancelInvoke:

function CancelInvoke () : void

function CancelInvoke (methodName : String) : void

Tal como seguro que estaréis suponiendo, CancelInvoke cancela las funciones que hayan sido invocadas por las dos
funciones anteriores para un determinado script. La primera variación de esta función no tiene parámetros, y al llamarse
cancela todas las funciones Invoke e InvokeRepeating del script. La segunda variación de la función sólo cancelará la
función de dicho tipo que coincida con el nombre que se le pasa como parámetro.

Arreglemos un poco más nuestro script dándole al usuario la posibilidad de cancelar la clonación de cubos.
var original : GameObject;
function OtroDeLoMismo() {
var copia : GameObject = Instantiate(original, original.transform.position +
Vector3.right, original.transform.rotation);
copia.transform.position += Vector3(Time.time - 5, 0,0);
}
InvokeRepeating("OtroDeLoMismo", 5,1);
function Update() {
if (Input.GetButton ("Fire1"))
CancelInvoke();
}

Es la misma función anterior, sólo que con el añadido que le hemos puesto, la función update comprobará cada frame si
el usuario ha pulsado la tecla definida en input como Fire1. Si tenemos la configuración por defecto, debería ser el ctrl
izquierdo o el botón izquierdo del ratón.

IsInvoking:

function IsInvoking (methodName : String) : boolean

Este primer prototipo de la función devuelve true si hay algún invoke pendiente de la función que se introduce como
parámetro.

function IsInvoking () : boolean

En esta segunda modalidad devuelve true si algún invoke pendiente en este script.

43. CLASE MONOBEHAVIOUR (III)

StartCoroutine:

function StartCoroutine (routine : IEnumerator) : Coroutine

Antes de entrar a analizar esta función quisiera explicar aunque fuera de forma superficial lo que es una corutina y cómo
son tratadas por Unity.

Una corutina es una función -que individualizamos con la palabra clave yield- que puede suspender su ejecución hasta
que una instruction dada -que también contendrá la palabra clave yield- termine. Veámoslo con un ejemplo sacado del
manual de referencia de Unity:
//Imprime Starting 0.0
print ("Starting " + Time.time);
/* Se llama a continuación a la función WaitAndPrint como coroutine, cosa que se
consigue anteponiendo la palabra clave yield. Al hacer esto permitimos que la
función se pueda suspender por una instrucción yield. Por lo tanto, el flujo del
script se va a la función waitandprint, donde se encuentra con una instrucción yield
que le dice que espere cinco segundos, luego imprimirá “WaitandPrint 5.0 (que será
el tiempo transcurrido desde el inicio del cómputo) y volverá arriba para imprimir
“done 5.0” (o un número cercano).*/
yield WaitAndPrint();
print ("Done " + Time.time);
function WaitAndPrint () {
// suspende la ejecución por 5 segundos
yield WaitForSeconds (5);
print ("WaitAndPrint "+ Time.time);
}

Si no veis clara la diferencia con el proceso secuencial habitual, no tenéis más que borrar la palabra yield que está
antes de la llamada a la función. Si hacéis eso, el programa empezará escribiendo "starting 0", bajará a la siguiente
instrucción, donde se encuentra con la función WaitAndPrint, que le indica que no va a hacer nada durante cinco
segundos, así que pasará inmediatamente a la siguiente declaración e imprimirá "Done 0", para luego, cinco segundos
después, imprimir por fin "Waitandprint 5".

Bien. Con estas nociones vamos ya a la función que nos ocupa, StartCoroutine. Como su nombre indica, inicia una
corrutina cuya ejecución puede ser pausada en cualquier momento usando una instrucción yield.

Programando en javascript, no es necesario escribir StartCoroutine, ya que el compilador la dará por supuesta.

Veamos un segundo ejemplo sacado igualmente del manual de referencia de Unity. En este ejemplo, a diferencia del
anterior, vamos a invocar una corrutina que no suspenderá la ejecución de la función que se desarrolla en paralelo:
function Start() {
// - Después de 0 segundos imprimirá "Starting 0.0"
// - Después de 0 segundos, imprimirá "Before WaitAndPrint Finishes 0.0"
// - Después de 2 segundos imprimirá "WaitAndPrint 2.0"

//Empezamos (función Start) imprimiendo “Starting 0.0”


print ("Starting " + Time.time);
/* Ahora vamos a iniciar la función WaitAndPrint como coroutine (en paralelo), pero
sin suspender el flujo de llamadas a funciones que se puedan seguir dando en el hilo
principal. Si hubiéramos querido suspenderlo, hubiéramos utilizado la fórmula del
ejemplo anterior, y hubiéramos antepuesto la palabra clave yield a la llamada a la
función.
Lo que sigue podría también haberse escrito (en javascript) así:
WaitAndPrint(2.0)
y el compilador añadiría el StartCoroutine por ti automáticamente.*/
StartCoroutine(WaitAndPrint(2.0));
/*Como no hemos interrumpido el flujo principal con yield, la declaración que viene
ahora se ejecutará antes que la la función anterior, puesto que la anterior ha de
esperar dos segundos y el print que viene ahora no.*/
print ("Before WaitAndPrint Finishes " + Time.time);
}
function WaitAndPrint (waitTime : float) {
// suspende la ejecución por los segundos que le fijemos en waitTime
yield WaitForSeconds (waitTime);
print ("WaitAndPrint "+ Time.time);
}

La función StartCoroutine tiene una segunda modalidad:

function StartCoroutine (methodName : String, value : object = null) : Coroutine

Es como la función anterior, pero usando un string. Esta variante tiene una mayor sobrecarga de procesamiento, pero
es la única que se puede detener con StopCoroutine.
Vamos a realizar otro ejemplo en el que , aparte de usar la variante con string de la función, aprovecharemos para
ilustrar de nuevo otra posibilidad que nos dan este tipo de instrucciones Yield. El script ejecuta la función DoSomething,
que dispara un bucle. Al siguiente frame el script se detendrá un segundo al toparse con la instrucción yield, pasará al
yielf waitforseconds, esperará un segundo y luego se detendrá.

function Start () {

StartCoroutine("DoSomething", 2.0);
yield WaitForSeconds(1);
StopCoroutine("DoSomething");
}
function DoSomething (someParameter : float) {
while (true) {
print("DoSomething Loop");
// Detiene la ejecución de esta corutina y vuelve al loop principal hasta el
//siguiente frame.
yield;
}
}

StopCoroutine:

function StopCoroutine (methodName : String) : void

Como hemos visto en el ejemplo anterior, detiene las corrutinas con el nombre que le suministramos como parámetro
iniciadas en este script. Dicho nombre de función lo hemos de pasar como un string, y no hemos de olvidar que sólo las
corrutinas con el nombre indicado que se hayan iniciado mediante la variante string de StartCoroutine (la segunda
modalidad de las anteriormente vistas) podrán ser detenidas con StopCoroutine.

StopAllCoroutines:

function StopAllCoroutines () : void

Detiene todas las corrutinas que estén corriendo en ese script.

44. CLASE MONOBEHAVIOUR (IV)


El resto de funciones que nos quedan por estudiar de la clase MonoBehaviour (que son unas cuantas) son comúnmente
conocidas como "funciones sobreescribibles". El nombre les viene dado porque estas funciones tienen, con respecto a
las "standard", la peculiaridad de que nos permiten diseñar nosotros su contenido. Por explicarlo de alguna manera, la
firma de estas funciones, que es lo que nos brinda Unity, supondrían el cuándo y lo que nosotros escribiremos será el
qué y el cómo.

Incidentalmente, y para entender un poco mas la pléyade de funciones que vienen a continuación, vamos a explicar el
orden en que Unity inicializa los distintos elementos que lo componen cada vez que se carga una escena en el juego:

-Primero se cargan los objects (game objects y components).


-Acto seguido se cargan los scripts que van vinculados a estos objects, y
una serie de funciones (las que veremos a continuación) son llamadas en un
orden específico:

1.- Awake.
2.- Start.
3.-Update/FixedUpdate.
4.-LateUpdate.

Update:

function Update () : void

Esta función, con la que ya hemos lidiado en alguna ocasión, es llamada cada frame, si el Monobehaviour (script que la
invoca) está activo.

Es la función más usada en los scripts, si bien arrastra el inconveniente de que cada ordenador puede tener un
framerate distinto, por lo que la misma instrucción de movimiento, por ejemplo, daría pie a diferentes velocidades
dependiendo del framerate de cada uno (con resultados catastróficos, sin ir más lejos, en juegos online para varios
jugadores). Para transformar las unidades de actualización de la función de frames a segundos se utiliza
Time.deltaTime.

LateUpdate:

function LateUpdate () : void

LateUpdate es llamado una vez todas las funciones Update han sido llamadas. Esto nos permite ordenar la ejecución de
scripts. Por ejemplo, una cámara que sigue a un objeto debería implementarse en un lateUpdate, pues cabe que el
objeto al que sigue se inicialice con un determinado movimiento en update, movimiento que debería tener en cuenta la
cámara.

FixedUpdate:

function FixedUpdate () : void

Esta función se ejecuta cada cierto número preestablecido y fijo de frames, lo que hace que no presente los problemas
de update. Se debe utilizar en aquellos scripts que impliquen un componente de físicas, y sobre todo, siempre se ha de
utilizar cuando haya que añadir una fuerza a un Rigidbody.

Awake:

function Awake () : void


Awake es llamada cuando se inicia el script, osea, cuando se carga la escena, y justo después de que se carguen los
objects (gameobjects y components) a que el script hace referencia. De tal manera, es útil para inicializar variables o
estados del juego antes de que el juego empiece, referenciando si es preciso a los objects a que hacen mención (y que
ya habrán sido como decimos inicializados previamente). Por ejemplo, dentro de un awake puedes tranquilamente usar
Gameobject.FindWithTag.

Cada función awake para gameobjects es llamada de forma aleatoria, razón por la cual se utiliza Awake para colocar
referencias entre scripts (pej, el script X llamará al script Y) pero se ha de utiliza Start para pasar información de un lado
a otro (pej: El script x le pasa el valor 5 al script Y), ya que Awake es llamada siempre antes que Start y así al mandar
valores vía Start nos aseguramos de que las relaciones entre scripts estén ya establecidas vía Awake.

Por último, indicar que Awake no puede formar parte de una corrutina, y que será llamada aunque en el inspector la
instancia del script esté deshabilitada/desmarcada.

Start:

function Start () : void

Es llamada después de Awake y antes de Update. Al igual que awake, sólo es llamada una vez a lo largo de toda la vida
del script.

Aparte del momento en que son llamadas, Start se diferencia de Awake en que Start sólo es llamada si la instancia del
script está habilitada, esto es, tiene su checkbox marcado en el inspector. Así, cuando un gameobject es inicialmente
usado en una escena esta función es llamada automáticamente.

Reset:

function Reset () : void

Resetea a los valores por defecto, restaurando los valores originales. Esta función es llamada cuando se pulsa reset en
el inspector o cuando se añade un componente por primera vez.

45. CLASE MONOBEHAVIOUR (V)

OnMouseEnter:

function OnMouseEnter () : void

Esta función es llamada cuando el cursor entra en un collider o un GUIElement.

Veámoslo con un ejemplo muy sencillo. Tecleamos:


function OnMouseOver(){
Debug.Log("El mouse está sobre el objeto " + gameObject.name);
}

Arrastramos el script al cubo. Pulsamos play y observaremos que cuando el cursor está sobre el cubo, se imprime la
frase debajo de la ventana game.

Esta función no será llamada en objetos que pertenezcan a Ignore Raycast layer.

OnMouseEnter puede ser una corrutina, siempre que utilicemos una instrucción Yield en la función, y el evento será
enviado a todos los scripts vinculados con el collider o GUIElement

OnMouseOver:

function OnMouseOver () : void

Esta función es llamada cada frame en la que el mouse permanezca sobre el Collider o GUIElement, a diferencia de
onMouseEnter, que sólo se dispara cuando entra el mouse.

Como esta función se actualiza cada frame, podemos hacer cosas como la del ejemplo:
function OnMouseOver () {
renderer.material.color += Color(0.1, 0, 0) * Time.deltaTime;
}

Si pulsamos play y mantenemos el cursor sobre el cubo, lentamente éste se irá tornando de color rojizo, debido al
paulatino aumento del componente R (red) de su RGB.

OnMouseOver puede ser, como OnMouseEnter, una corrutina, simplemente utilizando la declaración yield en la función

OnMouseExit:

function OnMouseExit () : void

Esta función es llamada cuando el ratón ya no esta sobre un GUIElement o un Collider.

Podemos completar con ella el script anterior:


function OnMouseOver () {
renderer.material.color += Color(0.1, 0, 0) * Time.deltaTime;
}
function OnMouseExit () {
renderer.material.color = Color.white;
}

Cuando se retira el mouse del cubo, éste retorna a su color blanco inicial.

Como las precedentes, la función no es llamada en objetos que tienen el Ignore Raycast Layer y puede formar parte de
una coroutine.

OnMouseDown:

function OnMouseDown () : void


Es llamada cuando el usuario pulsa el botón del mouse sobre un GUIElement o Collider.

Por seguir con el ejemplo anterior, podríamos añadir al script:


function OnMouseOver () {
renderer.material.color += Color(0.1, 0, 0) * Time.deltaTime;
}
function OnMouseExit () {
renderer.material.color = Color.white;
}
function OnMouseDown () {
renderer.material.color = Color.blue;
}

Al presionar el botón del ratón, el cubo se torna de color azul.

A esta función le es de aplicación lo indicado para las precedentes en relación con Ignore Raycast y la posibilidad de ser
una corrutina.

46. CLASE MONOBEHAVIOUR (VI)

Seguimos con las funciones sobreescribibles.

OnMouseUp:

function OnMouseUp () : void

Esta función es llamada cuando el usuario libera/suelta el botón del ratón. Es llamada incluso si el mouse no está al
soltar el botón sobre el mismo GUIElement o Collider en que estaba cuando el botón fue presionado. (Para que tuviera
ese comportamiento habría que usar OnMouseUpAsButton, que vamos a examinar a continuación)

OnMouseUpAsButton:

function OnMouseUpAsButton () : void

Como anticipábamos en la función anterior, ésta es llamada sólo cuando el mouse es liberado estando sobre el mismo
GUIElement o Collider en el que fue presionado.

OnMouseDrag:

function OnMouseDrag () : void

Es llamada esta función cuando el usuario presiona el botón del mouse sobre un GUIElement o un Collider y todavía lo
mantiene presionado. Es llamada cada frame mientras el botón siga presionado.
OnTriggerEnter, OnTriggerExit, OnTriggerStay, OnCollisionEnter, On CollisionExit,
OnCollisionStay:

Todas estas funciones ya fueron explicadas en la clase Collider, así que a la misma me remito.

OnControllerColliderHit:

function OnControllerColliderHit (hit : ControllerColliderHit) : void

Es llamada cuando nuestro character controller golpea un collider mientras realiza un movimiento, de tal manera que
muchas veces esta función sirve para empujar objetos cuando colisionan con el personaje.

Hagamos unas modificaciones previas al pertinente ejemplo en la interfaz de Unity.

1.- Eliminamos el script vinculado a PortaScripts, para que no nos lance errores.
2.- Añadimos un CharacterController al cubo, reemplazando cuando nos lo pida
Unity el antiguo boxCollider.
3.- Desconectamos provisionalmente el boxCollider del gameobject Suelo, dado que
en caso contrario será el suelo el primer collider con el que se tope nuestra
función.
4.- Escribimos este script, que le vincularemos al cubo:

var miCharCon : CharacterController;


miCharCon = GetComponent(CharacterController);
function Update() {
miCharCon.Move(Vector3(1 * Time.deltaTime ,0,0));
}
function OnControllerColliderHit(teToco) {
teToco.rigidbody.AddForce(Vector3(0,0,50));
}

Play. Nuestro character controller (también conocido como cubo) avanza hacia la izquierda a razón de un metro por
segundo, y cuando se topa con un controller (la esfera), algunos datos de la colisión y sus intervinientes son pasador al
único parámetro de la función, que es de la clase ControllerColliderHit.

La clase ControllerColliderHit, aunque la veremos más en profundidad en otra lección, cuenta con una serie de
variables, entre las que destacan:
controller: El character controller que golpea el collider (nuestro cubo en el
ejemplo)
collider: El collider que es golpeado por el character controller (la esfera,
aquí)
rigidbody: El rigidbody que ha sido golpeado por el character controller, si es
que el collider que golpeamos tiene rigidbody (en el ejemplo lo usamos
porque la esfera tiene rigidbody).
gameObject: El game object que ha sido golpeado por el character controller.
transform: El transform que ha sido golpeado por el controller.
point: El punto de impacto en coordenadas globales.
normal: El normal de la superficie que ha sido colisionado en coordenadas
globales.
moveDirection: Aproximadamente la dirección desde el centro de la cápsula del
character controller al punto que tocamos.
moveLength: La distancia que el character controller ha recorrido hasta golpear
con el collider.
Entonces, volviendo al ejemplo, a través del parámetro de tipo ControllerColliderHit que hemos dado en llamar teToco,
accedemos a la variable rigidbody, que obviamente se corresponde con el rigidbody de la esfera, y a partir de ahí le
asignamos un comportamiento, que en este caso es meramente desplazarse en el eje Z.

OnJointBreak:

function OnJointBreak (breakForce : float) : void

Esta función ya la explicamos en la lección correspondiente a la clase Joint.

OnParticleCollision:

function OnParticleCollision (other : GameObject) : void

Esta función es llamada cuando una partícula choca con un collider. Puede por tanto usarse para computar el daño
recibido por un gameobject cuando choca con partículas. Este mensaje es enviado a todos los scripts vinculados con el
WorldParticleCollider y al collider que fue colisionado. El mensaje es sólo enviado si habilitamos SendCollisionMessage
en el inspector del WroldParticleCollider.

Quedémonos de momento con esta sucinta información, que ya trabajaremos cuando nos toque lidiar con la clase
ParticleEmitter.

47. CLASE MONOBEHAVIOUR (VII)


OnBecameVisible:

function OnBecameVisible () : void

Se llama a esta función cuando el renderer se convierte en visible por alguna cámara. Este mensaje se envía a todos
los scripts relacionados con el renderer. Esta función y su opuesta –OnBecameInvisible- son útiles para evitar cómputos
que son sólo necesarios cuando el objeto es visible.

OnBecameInvisible:

function OnBecameInvisible () : void

Es llamada cuando el renderer ya no es visible por ninguna cámara.

OnLevelWasLoaded:

function OnLevelWasLoaded (level : int) : void

Esta función es llamada después de que un nuevo level ha sido cargado. El parámetro level de la función es el level que
ha sido cargado. Si quiere ver el índice de levels de su juego, use el menú File=>Build settings.

OnEnable:

function OnEnable () : void

Es llamada cuando el objeto pasa a estar habilitado y activo.

OnDisable:

function OnDisable () : void

Es llamada cuando el objeto se convierte en deshabilitado o inactivo. También cuando el objeto es destruido y puede ser
usada la función para procesos de limpieza. Cuando los scripts son recargados después de que la compilación haya
acabado, OnDisable se llamará seguida por OnEnable después de que el script haya sido cargado.

OnDestroy:

function OnDestroy () : void

Es llamada cuando el MonoBehaviour (script) es destruido. Sólo puede ser llamada para gameobjects que previamente
hayan estado activos.

OnPreCull:

function OnPreCull () : void


Es llamada antes de que la cámara deseche la escena. Culling es un proceso que determina qué objetos son visibles en
la cámara, y OnPreCull es llamada justo antes de dicho proceso.

Esta function es llamada sólo si el script está vinculado con la cámara y está activado. Si queremos cambiar los
parámetros de visión de la cámara (tal como fieldOfView) lo tendremos que hacer aquí. La visibilidad de los objetos de
la escena se determinará en base a los parámetros de la cámara después de la función OnPreCull.

OnPreRender:

function OnPreRender () : void

Es llamada antes de que la cámara empiece a renderizar la escena. Sólo se llama si el script está vinculado a la cámara
y activo.

Es importante comprender que si cambiamos con esta función los parámetros de visión de la cámara, como por ejemplo
fieldOfView, sólo tendrán efecto el siguiente frame, por lo que hay que hacerlo mejor en la función OnPreCull, como
hemos dicho antes.

OnPostRender:

function OnPostRender () : void

Es llamada después de que la cámara acaba de renderizar la escena, siempre que el script esté vinculado con la
cámara y activo.

OnRenderObject:

function OnRenderObject () : void

Es llamada después de que la cámara renderiza la escena. Se diferencia de OnPostRender en que OnRenderObject es
llamada para cada objeto que tenga un script con la función, sin importar si está vinculado a una cámara o no.

OnWillRenderObject:

function OnWillRenderObject () : void

Esta función es llamada una vez por cada cámara si el objeto es visible. Es llamada durante el proceso de culling (que
podríamos traducir por elección o desechamiento), justo antes de renderizar todos los objetos seleccionados. Podría
usarse por tanto esta función para crear texturas que pudieran actualizarse sólo si el objeto a renderizar es en realidad
visible.

48. CLASE MONOBEHAVIOUR (VIII)


OnGUI:

function OnGUI () : void

Es llamada para renderizar y manejar eventos GUI. Esto significa que nuestra implementación de OnGUI podría ser
llamada varias veces por frame, a razón de una llamada por evento. Esto lo trabajaremos más cuando estudiemos las
clases Event y GUI.

OnRenderImage:

function OnRenderImage (source : RenderTexture, destination : RenderTexture) : void

Es llamada cuando se tiene toda la información de renderizado de una imagen, permitiendo modificar la imagen final
procesándola con filtros.

La imagen entrante (source) es de tipo renderTexture y el resultado lo almacenamos en otro parámetro del mismo tipo
(destination en el prototipo de la función). Cuando hay múltiples filtros de imagen vinculados a la cámara, estos
procesan la imagen secuencialmente, pasando el primer filter destination como source del siguiente filtro.

Este mensaje será enviado a todos los scripts vinculados a la cámara.

OnDrawGizmosSelected:

function OnDrawGizmosSelected () : void

Implementa esta function si quieres dibujar gizmos sólo si el objeto está seleccionado.

Vamos a verlo con un ejemplo simple. Es necesario para ver el efecto que previo a darle al play el cubo no esté
seleccionado.
function OnDrawGizmosSelected () {
Gizmos.color = Color.white;
Gizmos.DrawCube (transform.position, Vector3 (2,2,2));
}

No nos fijemos demasiado en los elementos de la clase Gizmos, que ya veremos en su momento. De momento sólo
necesitamos saber que , cuando se seleccione el cubo -bien en la jerarquía, bien en la escena, bien con el juego
activado o incluso sin que el juego esté activado- nos aparecerá un gizmo que se corresponderá a un cubo blanco de 2
unidades de lado.

Probémoslo.

OnDrawGizmos:

function OnDrawGizmos () : void

Implementa esta función si quieres dibujar gizmos que aparezcan siempre dibujados. Esto te permite rápidamente
seleccionar objetos importantes en tu escena, por ejemplo. Notad que esta función usa una posición del ratón que es
relativa a la vista de la escena.

Para mostrar esto en un ejemplo necesitamos unos pasos previos:


1.- Buscad por la red la imagen de una bombilla (no demasiado grande).
2.- La guardáis y le asignáis el nombre "Bombilla" (Fijaros en la extensión de la
imagen. Si es distinta de la que voy a utilizar yo, cambiadla en el script)
3.- Buscáis la carpeta de nuestro proyecto Unity, y arrastráis la imagen dentro de
la carpeta assets.
4.- En la interfaz, hacemos click derecho sobre Proyecto =>Create =>Folder.
Llamamos a la nueva carpeta Gizmos (es donde por defecto buscará Unity)
5.- En Proyecto, arrastramos la imagen dentro de esta carpeta.
6.- Doble click sobre MiPrimerScript. Tecleamos:

function Update(){
transform.Translate(-Vector3.right * Time.deltaTime);
}
function OnDrawGizmos() {
Gizmos.DrawIcon (transform.position + Vector3(0,5,0) , "Bombilla.jpeg");
}

Si hemos seguido correctamente estos pasos, cubo e imagen deberían desplazarse al unísono hacia la izquierda, tal
que así:

50. CLASE CAMERA (I)

Bienvenid@s a otra de las clases importantes de Unity. La clase cámera le permite al jugador interactuar con tu juego.
La cámara en Unity es un dispositivo a través del cual el jugador ve el mundo.

Hemos de diferenciar desde el primer momento, y ahí residen no pocas confusiones de los recién llegados a Unity, entre
pantalla y cámara. Ambas son cosas diferentes y tienen una unidad de medida distinta:

La unidad de medida de la pantalla está definida en píxeles. La esquina izquierda-inferior de la pantalla es (0,0), la
derecha-superior está contenida en las variables (pixelWidth,pixelHeight), que representan la anchura y altura de la
pantalla respectivamente. La posición Z estaría en unidades globales contadas desde la cámara.
Si la unidad de medida es relativa a la cámara, la esquina izquierda-inferior de la cámara es (0,0) y la derecha-superior
(1,1). La posición Z estaría en unidades globales desde la cámara.

Con esto en mente, empecemos:

VARIABLES:

fieldOfView:

var fieldOfView : float

Variable que contiene el campo de visión de la cámara en grados. Se referiría al campo de visión vertical, ya que el
horizontal varía dependiendo del viewport’s aspect ratio. El campo de visión es ignorado cuando la cámara está en
modo ortográfico.

Antes de empezar con los ejemplos, vamos a retocar un par de cosillas en la interfaz de Unity. En primer lugar, en
Proyecto le damos al botón derecho del ratón sobre la carpeta Gizmos=>Delete, para deshacernos de la bombilla. Nos
aseguramos de que el cubo está en posición -2,0,0. Eliminamos el script vinculado al cubo. Nos aseguramos de que la
cámara no esté en modo ortográfico, sino en perspectiva. Doble click en MiPrimer Script:
Camera.main.fieldOfView = 20;

Arrastramos el script a PortaScripts en la Jerarquía. Notemos antes que nada que en el script nos estamos refiriendo a
la cámara principal. Si quisiéramos que el script afectara a otra cámara distinta, por un lado deberíamos quitarle el
"main" del script y por otro lo tendríamos que arrastrar a la cámara que queremos afectar por éste. Vamos a probarlo,
para que no haya dudas, pero antes dadle al play y comprobar cómo el campo de visión, que por defecto es 60, pasa a
ser 20 y -aunque parezca al principio poco intuitivo- al tener un ángulo menor de visión, dicho campo reducido es
ampliado para que ocupe toda la pantalla. Esto a muchos les recordará las fotos en las bodas: cuanta más gente
queremos abarcar (más campo de visión) más lejos tenemos que tirar la foto.

Pero habíamos prometido que aplicaríamos el ejemplo a una cámara no principal, así que ahí vamos.
Gameobject=>Create other=>Camera. Llamamos a la Camera "Camara 2". Ahora la ubicaremos en la siguiente
posición/rotación:
Posicion:-0.6,15,1
Rotacion:85,0,0

Con la cámara 2 seleccionada, le damos al play y observamos nuestro pequeño mundo desde otra perspectiva. Nos
daremos cuenta de que Unity nos lanza un mensaje, avisándonos de que tenemos dos receptores (listeners) de audio
en la escena. Esto es porque por defecto cada cámara tiene un Audio Listener para recoger el sonido además de la
imagen. Para evitar problemas, eliminamos el AudioListener de la cámara 2 en el inspector.

Vale, y ahora montemos otro miniscript para la segunda cámara. Hacemos doble click en MiSegundoScript (ya que el
primero lo tenemos ocupado) y tecleamos:
camera.fieldOfView = 20;

Arrastramos este script a la Camara 2 (si lo arrastráramos al PortaScripts no funcionaría, ya que no sabría a qué cámara
ha de afectar, salvo que creáramos una variable "expuesta" de tipo Camera a la que arrastrar esta y etc, etc, aunque
para lo que queremos hacer es más cómodo el método primero)

Le damos al play y observaremos que funciona.

Aprovecho para comentaros una cosilla que seguro que la mayoría ya sabe. Cada cámara al ser creada tiene una
variable (depth, que luego veremos) que establece la prioridad de cada una. Cuanto más alto el valor de depth, más
prioridad tiene la cámara. Si os fijáis en el inspector, la cámara principal debería tener la variable depth en -1, y camara
2 en 0. Es por ello que cuando le damos al play, la vista que nos aparece es la que recoge la cámara 2. Si quisiéramos
retomar a vista grabada por la cámara principal, no tendríamos más que darle un valor a depth superior al del resto de
cámaras.
51. CLASE CAMERA (II)

nearClipPlane:

var nearClipPlane : float

El plano de recorte de cerca. Cualquier cosa que se halle más cerca de la cámara de la distancia establecida en esta
variable no se mostrará en la cámara.

Veámoslo con un ejemplo un poco extremo. Si a MiSegundoScript lo dejamos como sigue...


camera.nearClipPlane = 13;

... observaremos que todo lo que se halle a menos de 13 metros de la cámara será recortado.

farClipPlane:

var farClipPlane : float

El plano de recorte de lejos. Cualquier cosa que se halle más lejos de la cámara de la distancia establecida en esta
variable no se mostrará en la cámara.

Probad cambiar en nuestro ejemplo el "near" por el "far", para obtener una especie de negativo de la toma anterior.

renderingPath:

var renderingPath : RenderingPath

Indica el tipo de renderizado de entre los que contempla la enum RenderingPath (UsePlayerSettings, VertexLit, Forward
y DeferredLighting). Desde el inspector, con la cámara en uso seleccionada, podemos intercambiar entre las cuatro
posibilidades para captar sus matices.

actualRenderingPath:

var actualRenderingPath : RenderingPath

Variable de sólo lectura que contiene el rendering path que se está usando.

orthographicSize:
var orthographicSize : float

El campo de visión de la cámara cuando está en modo ortográfico. En el próximo ejemplo colocamos a la camara 2
primero en modo ortográfico, y le fijamos luego un tamaño de 3, de tal forma que al tener poco campo de visión amplíe
los objetos enfocados.
camera.orthographic = true;
camera.orthographicSize = 3;

Podríamos hacer lo mismo con la cámara principal modificando MiPrimerScript y añadiendo "Main". Recordemos que
con la cámara principal no es preciso que el script se incluya en el gameobject mainCamera.
Camera.main.orthographic = true;
Camera.main.orthographicSize = 3;

orthographic:

var orthographic : boolean

Variable de tipo booleano que indica si la cámara está en modo ortográfico (true) o en perspectiva (false) y que permite
pasar de uno a otro.

Como ya hemos visto, cuando esta variable esta en true, el campo de visión de la cámara se define por
orthographicSize, y cuando está en false por fieldOfView.

depth:

Var depth : float

La profundidad de la cámara en el orden de renderizado de las cámaras. Las cámaras con profundidad más baja son
renderizadas antes de las cámaras con profundidad más alta.

Si tienes varias cámaras, usa este control para decidir el orden en el que las cámaras mostrarán la escena si algunas de
ellas no cubren la totalidad de la pantalla.

aspect:

var aspect : float

Variable en la que se guarda/coloca la proporción de aspecto (aspect ratio), que es el nombre con que se conoce a la
anchura dividida por la altura de la cámara. Por defecto, el aspect ratio es calculado automáticamente tomando como
base la el aspect ratio de la pantalla. Esto es así incluso si la cámara no está renderizando el área entera. Si modificas
el aspect ratio de la cámara, el valor permanecerá hasta que llames a la función camera.ResetAspect().

cullingMask:

var cullingMask : int

Es usada para renderizar de manera selectiva partes de la escena. Si el layerMask del gameobject (que veremos a no
mucho tardar) y el cullingMask de la cámara son cero, entonces el game object será invisible para esa cámara. El
layerMask es parecido al sistema de capas de Blender, que brinda la posibilidad de arrastrar objetos a una capa
diferente. Con esto se consigue, por ejemplo, que objetos pertenecientes a una fase un poco más avanzada del juego
no sean mostrados hasta que permitamos a la cámara renderizar los relativos a esa capa.

backgroundColor:

var backgroundColor : Color

Variable que indica el color con el cual la pantalla será completada.

52. CLASE CAMERA (III)

rect:

var rect : Rect

Establece qué parte de la pantalla esta la cámara renderizando. Dicha parte de la pantalla es fijada a través de una
instancia de la estructura Rect, en coordenadas normalizadas. Por coordenadas normalizadas nos referimos a que los
valores en el rango del rect irán de 0,0 (izquierda,abajo) a 1,1 (derecha,arriba)

Así, si queremos que la cámara a la que vinculamos el script renderice toda la pantalla, meramente haremos lo
siguiente. Editamos MiSegundoScript (que es el que tenemos vinculado a Camara 2) y escribimos:
camera.rect = Rect (0, 0, 1, 1);

Tratándose de coordenadas normalizadas, lo que estamos pidiéndole aquí a Unity es que lo que esté captando la
cámara 2 se nos vea en el rectángulo que le pasamos a la variable rect, y que en este caso ocupa toda la pantalla (de
0,0 a 1,1)

Si queremos que lo que capta la cámara dos se nos vea en la esquina superior derecha de la pantalla, calcularíamos
estas coordenadas:
0,5: Corresponde al eje horizontal del primer punto, donde 0 sería izquierda y 1
derecha.
0,5: Corresponde al eje vertical del primer punto, donde 0 sería abajo y 1 arriba.
El primer punto de nuestro rectángulo, pues, estaría justo en el centro de la
pantalla.
1: Corresponde al eje horizontal del segundo punto.
1: Y este es el eje vertical del segundo punto. El segundo punto del rectántulo,
por lo tanto, está justo en el vértice derecho superior.

Así que podemos rectificar el script anterior, de la manera indicada:


camera.rect = Rect (0.5, 0.5, 1, 1);
De esta manera, lo que esté grabando cámara dos queda relegado al recuadro superior derecho y, para el resto de la
pantalla, entra en acción la cámara principal, que ya vimos que por profundidad (depth) es la siguiente en la lista de
prioridad. Debería quedar algo parecido a esto:

Pero, ¿qué sucedería si no hubiera ninguna cámara más?. Probémoslo. Seleccionamos Main Camera y en el inspector
desmarcamos el check box que aparece junto al componente camera. Volvemos a darle al play, y observaremos que la
parte de pantalla no incluida en el rectángulo aparece en negro, tal como se muestra a continuación:

pixelRect:

var pixelRect : Rect

Indica/establece qué parte de la pantalla esta la cámara renderizando, pero a diferencia de rect, no lo indica en
coordenadas normalizadas, sino en píxeles.

Por lo tanto, este script sería el equivalente al anterior:


camera.pixelRect = Rect (Screen.width/2, Screen.height/2, Screen.width,
Screen.height);

pixelWidth:

var pixelWidth : float

Indica la anchura de la cámara en píxeles. (Sólo lectura).

pixelHeight:

var pixelHeight : float

Y esta variable indica la altura de la cámara en píxeles(sólo lectura)

velocity:

var velocity : Vector3

Variable de sólo lectura que indica la velocidad de la cámara en el espacio global.

53. CLASE CAMERA (IV)

clearFlags:

var clearFlags : CameraClearFlags

Indica cómo la cámara completa el background. Admite los valores CameraClearFlags.Skybox (rellena el bacground con
el skybox habilitado), CameraClearFlags.SolidColor (con el color que le indiquemos), CameraClearFlags.Depth (que
mantiene el color que tuviera el background el frame anterior o en cualquier estado previo)o CameraClearFlags.Nothing
(no se renderiza background alguno).

FUNCIONES:

ResetAspect:

function ResetAspect () : void


Revierte el aspect ratio de la cámara al aspect ratio de la pantalla, acabando con el efecto de modificar la variable
aspect.

Lo veremos mejor con un ejemplo (por cierto, si al término del ejemplo anterior no lo hicísteis, aprovechad ahora para
volver a marcar el checkbox del componente Camera de Main camera)

Editamos MiSegundoScript para que luzca como sigue:


camera.aspect = 2;
yield WaitForSeconds (5);
camera.ResetAspect();

Lo que estamos haciendo es lo siguiente: primero establecemos la variable aspect en 2. Recordemos que aspect es la
anchura de la cámara dividida por la altura, esto es, en este caso la cámara será el doble de ancha que alta (y en
consonancia lo grabado será el doble de estrecho de lo normal). Para ver la diferencia, intercalamos una instrucción
yield que -como ya sabemos- suspende la ejecución de cualquier otra instrucción (en este caso durante cinco
segundos) y acto seguido reseteamos el aspect de la cámara para devolverlo a sus parámetros normales.

WorldToScreenPoint:

function WorldToScreenPoint (position : Vector3) : Vector3

Transforma la posición de un transform desde el espacio global al espacio de la pantalla.

Recordemos que el espacio de la pantalla está definido en píxeles (izquierda-abajo es (0,0) y derecha-arriba es
(pixelWidth,pixelHeight). La posición z se calcula en unidades globales desde la cámara.

Un ejemplo. Editamos MiSegundoScript tecleando esto:


var eseQueTuVesAhi : Transform;
var posicionGlobal : String;
var posicionEnPixeles : String;
posicionGlobal = eseQueTuVesAhi.position.ToString();
posicionEnPixeles = camera.WorldToScreenPoint(eseQueTuVesAhi.position).ToString();
Debug.Log("La posicion global es " + posicionGlobal + " y la posicion en pixeles "
+posicionEnPixeles);

Guardamos y arrastramos desde la Jerarquía nuestro Cubo hasta la variable expuesta eseQueTuVesAhi. El script
devuelve primero la posición global del cubo en la escena, en un string generado por la función ToString. Acto seguido
convertimos ese vector3 que contiene las coordenadas globales en otro Vector3 que contiene la ubicación en píxeles
del transform del cubo respecto de la pantalla. Al devolver un Vector3, podemos también aprovechar la función ToString
para convertir dichas coordenadas en un String e imprimirlas. Recordemos que el tercer parámetro del Vector3 con la
ubicación en píxeles viene referido (eje Z) a la distancia en unidades globales (metros) entre la cámara y el transform.

WorldToViewportPoint:

function WorldToViewportPoint (position : Vector3) : Vector3

Convierte la posición de un transform desde las coordenadas globales al espacio de punto de vista (viewport space en
inglés). El viewport space es el relativo a la cámara, donde izquierda-abajo es (0,0) y derecha-arriba (1,1). La posición z
se mediría en unidades globales desde la cámara.

Lo vemos con más claridad modificando ligeramente el script anterior:


var eseQueTuVesAhi : Transform;
var posicionGlobal : String;
var posicionEnEspacioCamara : String;
posicionGlobal = eseQueTuVesAhi.position.ToString();
posicionEnEspacioCamara = camera.WorldToViewportPoint(eseQueTuVesAhi.position).ToString();
Debug.Log("La posicion global es " + posicionGlobal + " y la posicion en espacio
normalizado de camara " +posicionEnEspacioCamara);

Como vemos al darle al play, ahora se nos muestra en coordenadas relativas a la cámara la ubicación del transform del
cubo. Continúa inalterada, eso sí, la distancia respecto del eje Z (profundidad), que ya dijimos que va medido en
unidades globales respecto de la posición de la cámara.

54. CLASE CAMERA (V)

ViewportToWorldPoint:

function ViewportToWorldPoint (position : Vector3) : Vector3

Es la función inversa a WorldToViewportPoint, que estudiamos en la lección anterior. Convierte por tanto la posición de
un transform medida en el viewport space relativo de la cámara (0,0 a 1,1) a coordenadas globales.

Se suministra a la función un vector donde los componentes X e Y son las coordenadas de pantalla, y el componente Z
es la distancia del plano resultante desde la cámara en unidades globales, y la función los transforma en coordenadas
globales.

Así, podemos adaptar el ejemplo que nos propone el manual de referencia y dibujar una esfera amarilla en la esquina
superior derecha de la pantalla, y traducir dichas coordenadas de ubicación a las generales de la escena.
function OnDrawGizmos () {
var deLaCamaraAlMundo : Vector3 = camera.ViewportToWorldPoint (Vector3 (1,1,0.5));

Gizmos.color = Color.yellow;
Gizmos.DrawSphere (deLaCamaraAlMundo, 0.1);
}

Le hemos dado una profundidad a la ubicación de la esfera de 0.5. Hemos de tener presente que esta distancia no debe
ser inferior a la que esté establecida en nearClipPlane, o no se verá.

ScreenToWorldPoint:

function ScreenToWorldPoint (position : Vector3) : Vector3


Convierte la posición de un transform desde el espacio de pantalla en píxeles (0,0 a pixelWidth,pixelHeight) a
coordenadas globales, con la posición z (como en el resto de casos) medida en unidades globales desde la cámara.

ScreenToViewportPoint:

function ScreenToViewportPoint (position : Vector3) : Vector3

Convierte la posición de un transform de espacio de pantalla en píxeles (0,0 a pixelWidth,pixelHeight) a viewport space
relativo al espacio de cámara (0,0 a 1,1).

ViewportToScreenPoint:

function ViewportToScreenPoint (position : Vector3) : Vector3

Convierte la posición del transform de viewport space relativo al espacio de cámara (0,0 a 1,1) en espacio de pantalla
en píxeles (0,0 a pixelWidth,pixelHeight). La posición z en ambos tipo de medida es la misma, medida en unidades
globales desde la cámara.

ViewportPointToRay:

function ViewportPointToRay (position : Vector3) : Ray

Devuelve un rayo que sale de la cámara en coordenadas relativas a ésta (0,0 a 1,1). El rayo comienza en el plano más
cercano a la cámara, razón por la que la posición Z es ignorada.

ScreenPointToRay:

function ScreenPointToRay (position : Vector3) : Ray

Devuelve un rayo que va de la cámara a través de un punto de la pantalla. Estando el rayo en las coordenadas
globales, empieza en el plano cercano a la cámara y va a través de la posición x e y en las coordenadas de píxeles (0,0
a pixelWidth,pixelHeight) en la pantalla (la posición z es ignorada.)

55. CLASE CAMERA (VI)


Render:

function Render () : void

Renderiza la cámara manualmente, usando el clear flags de la cámara, target texture y otras propiedades. La cámara
puede enviar mensajes como OnPreCull, OnPreRender o OnPostRender a cualquier script que esté vinculado, y
renderizar algunos filtros de imagen.

Esto es usado para tener control preciso sobre el orden de renderizado. Para hacer uso de esta característica, crea una
cámara y deshabilitala, y entonces llama a la función Render para ella.

RenderWithShader:

function RenderWithShader (shader : Shader, replacementTag : String) : void

Hace que la cámara renderice con reemplazo de sombreado (shader) . Esto renderizará la cámara, usando los clear
flags de la cámara, target texture y otras propiedades. A direrencia de la función anterior, la cámara no enviará
OnPreCull, OnPreRender or OnPostRender a scripts vinculados. Los filtros de la imagen tampoco serán renderizados.

Esto es usado para efectos especiales, como por ejemplo renderizar una visión de calor y cosas así. Para usar estas
características, habitualmente crearás una cámara y la deshabilitarás y luego llamarás RenderWithShader en ella.

SetReplacementShader:

function SetReplacementShader (shader : Shader, replacementTag : String) : void

Hace que la cámara renderice con shader replacement. Después de llamar a esta función, la cámara renderizará su
vista con shader replacement. Para volver al renderizado normal hay que llamar a ResetReplacementShader.

ResetReplacementShader:

function ResetReplacementShader () : void

Borra el shader replacement de la cámara provocado por la función anterior.

RenderToCubemap:

function RenderToCubemap (cubemap : Cubemap, faceMask : int = 63) : boolean

Renderiza un cubemap desde la cámara que llama a esta función. Esto es muy útil en el editor para bakear cubemaps
estáticos de tu escena.

La posición de la cámara, clear flags y clipping plane distances son usados para renderizar dentro de las caras de un
cubemap. faceMask es un mapa de bits que indica qué cara del cubemap debe ser renderizada. Cada bit se
corresponde a una cara y está representado por un int dentro de una enum de tipo cubemapface. Por defecto las seis
caras del cube map se renderizarán, que es lo que viene representado en la firma por el 63, que corresponde a lo que
ocupan los bits de las seis caras.

La función devuelve false si el renderizado del cubemap falla.


Esta función tiene un segundo prototipo:

function RenderToCubemap (cubemap : RenderTexture, faceMask : int = 63) : boolean

Esta segunda modalidad es usada para reflexiones en tiempo real dentro de render textures de cubemap. Puede ser
bastante caro en términos de rendimiento, eso sí, especialmente si las seiz caras del cubemap son renderizadas cada
frame.

CopyFrom:

function CopyFrom (other : Camera) : void

Permite copiar para una cámara todas las variables de otra cámara (campo de vision, clear flags, culling mask…) Puede
ser útil si queremos que una cámara coincida con la configuración de otra, para conseguir efectos personalizados de
rendering, como por ejemplo los objetidos usando RenderWithShader.

VARIABLES DE CLASE:

main:

static var main : Camera

Se refiere a la cámara que esté habilitada y con el tag “Main Camera”(Read Only). Devuelve nulo si no hay una cámara
con esas características en la escena.

allCameras:

static var allCameras : Camera[]

Devuelve todas las cámaras habilitadas en la escena.

56. CLASE LIGHT (I)

Esta clase se usa para controlar todos los aspectos de las luces en Unity. Las propiedades que aquí veremos son
exactamente las mismas que los valores que podemos encontrarnos en el inspector.

Normalmente las luces son creadas en el editor, pero a veces puede ser que queramos crear o manipular una luz desde
un script.

Podemos, para tener una primera idea de lo que podemos hacer con esta clase, aplicar el ejemplo que nos viene en el
manual de referencia. Antes de nada, modifiquemos el escenario para los ejemplos que vendrán:

1.- Eliminamos en Jerarquía Camara 2.


2.- Eliminamos el script vinculado a PortaScripts.
Doble click en MiPrimerScript. Tecleamos:
function Start () {
var unaNuevaLuz : GameObject = new GameObject("La luz");
unaNuevaLuz.AddComponent(Light);
unaNuevaLuz.light.color = Color.blue;
unaNuevaLuz.transform.position = Vector3(0, 5, 0);
}

Salvamos y arrastramos el script a PortaScripts. Pulsamos play. Debería aparecer una nueva luz en la escena, tal que
así:

La dinámica del script es sencilla: Creamos primero un gameobject (que sería el equivalente a crear en el menú de la
interface de Unity un gameobject vacío. A dicho gameobject vacío (que damos en llamar "la luz")le añadimos un
componente de tipo luz (por defecto se crea una luz de tipo Point). Le damos por último a esa luz recién creada un color
y una ubicación en la escena.

VARIABLES:

type:

var type : LightType

El tipo de luz. Puede ser

LightType.Spot: Consiste en una luz tipo tipo foco.


LightType.Directional: Una luz direccional, parecida a la del sol.
LightType.Point: Un punto de luz.

Para mostrar la diferencia entre las tres, he apagado la luz principal de la escena deshabilitando su checkbox, y he
rediseñado el script anterior, y a su vez he hecho las pertinentes probaturas con los tres tipos de luz.
function Start () {
var unaNuevaLuz : GameObject = new GameObject("La luz");
unaNuevaLuz.AddComponent(Light);
unaNuevaLuz.transform.position = Vector3(0, 3, 0);
unaNuevaLuz.light.type = LightType.Spot;
}

Aquí os dejo las capturas de pantalla de este script con los tres tipos de luz. Primero con luz tipo Spot:
Luz directional:

Y luz tipo point:

57. CLASE LIGHT (II)

color:

Var color : Color

El color de la luz. Para modificar la intensidad de la luz podemos cambiar el color de la luz. Por ejemplo, una luz de color
negro es lo mismo que no tener ninguna luz.

intensity:

var intensity : float

La intensidad de la luz es multiplicada con el color de la luz. El valor puede estar entre 0 y 8. Esto nos permite crear
luces muy brillantes.
Realicemos un pequeño ejemplo. Previamente eliminamos el script vinculado a PortaScripts y volvemos a habilitar (si lo
habíamos desmarcado) el checkbox de nuestra luz principal.

Doble click en MiPrimer Scipt. Tecleamos:


function Update(){
var opaco : float = 0.0;
var brillante : float = 8.0;
light.color = Color.green;
light.intensity = Random.Range(opaco, brillante);
}

Guardamos y vinculamos el script a nuestra Luz. Al darle al play deberíamos asistir a una escena iluminada por una
parpadeante luz de neón. Meramente lo que hemos hecho es utilizar una función de la clase Random (de próximo
estudio)de nombre Range, que genera un número aleatorio entre un máximo y un mínimo. Dado que la intensidad de la
luz va de 0 a 8, le damos ese amplio margen de actuación a Random.Range para que cada frame varíe la intensidad de
la luz de manera aleatoria entre 0 y 8. Ello, unido al color verde típicamente apocalíptico, nos da este bonito efecto.

shadows:

var shadows : LightShadows

Indica cómo proyecta sombras la luz. Esta variable es de tipo LightShadows, que es una enumeración que permite los
siguientes valores:

None: No proyecta sombras (por defecto)


Hard: Proyecta sombras duras (sin filtro de sombras)
Soft: Proyecta sombras suaves (con filtro)

Pongamos un ejemplo. Doble click sobre MiPrimerScript. Tecleamos:


function Start(){
light.color = Color.yellow;
light.intensity = 3.0;
light.shadows = LightShadows.Hard;
}
Observemos las sombras, y comprenderemos por qué se les denomina como duras:

Y si usamos el valor LightShadows.Soft:

shadowStrenght:

var shadowStrength : float

Establece/indica la fuerza de las sombras.

shadowBias:

var shadowBias : float

Vendría a ser la perpendicular de la sombra respecto del objeto que la emite.


shadowSoftness:

var shadowSoftness : float

Suavidad de las sombras de las luces direccionales.

shadowSoftnessFade:

var shadowSoftnessFade : float

Velocidad de fadeout de las sombras de las luces direccionales.

58. CLASE LIGHT (y III)

range:

var range : float

El diámetro del rango o haz de luz, en luces de tipo spot y point.

spotAngle:

var spotAngle : float

En ángulo en grados de las luces de tipo spotlight. Usado originalmente en luces de tipo spot, altera el tamaño del foco
de la galleta de luz en luces de tipo direccional. No tiene efectos para luces de tipo point.

cookie:

var cookie : Texture

Variable de tipo textura que establece la textura de la galleta proyectada por la luz.

flare:

var flare : Flare

Selecciona un halo para la luz. Este ha de ser previamente asignado en el inspector.

renderMode:
var renderMode : LightRenderMode

Establece cómo renderizar la luz, de entre las siguientes opciones que la enumeración LightRenderMode permite:

Auto: Elige el modo de renderizado automáticamente.


ForcePixel: Fuerza a la luz a ser una luz de píxeles.
ForceVertex: Fuerza a la luz a ser una luz de vértices.

cullingMask:

var cullingMask : int

Es usado para separar luces de la escena de manera selectiva. Si el layerMask del gameobject y el cullingMask de la
luz son cero, entonces el objeto no será iluminado con esta luz.

59. CLASE MATERIAL (I)

Como podéis comprobar en el esquema superior, hemos subido dos peldaños en la jerarquía respecto a las últimas
clases que estábamos estudiando, y vamos ahora a aproximarnos a una serie de clases que derivan directamente de la
clase base Object.

La clase material, tal como es de suponer, accede a todas las propiedades de un material, permitiéndonos
alterarlas/animarlas. Si pretendemos referirnos al material usado por un objeto, es preferible usar la propiedad
Renderer.material, tal que así:
renderer.material.color = Color.red;

VARIABLES:

shader:

var shader : Shader


Es el shader del material. En modelado 3d, un shader vendría a ser el algoritmo que indica cómo una superficie ha de
responder ante la luz.

Es una variable de la clase Shader (que estudiaremos en su momento). Nos permite utilizar/crear/importar diferentes
reacciones que tendrá nuestro objeto al darle la luz (diffuse, transparent, ect)

Por ejemplo, podemos comprobar qué tipo de shader tiene nuestra esfera. Para ello, antes que nada, eliminamos el
script que tenemos vinculado a la luz (si es que estamos siguiendo las lecciones por orden), y editamos MiPrimerScript
tal como sigue:
renderer.material.shader = Shader.Find( "Transparent/Diffuse" );
var miShader : String;
miShader = renderer.material.shader.ToString();
Debug.Log(miShader);

Como vemos, para obtener el tipo de material referido a un objeto recurrimos a la clase Renderer. Observamos que
nuestra esfera tiene el shader por defecto, que es de tipo Difusse.

Y si queremos cambiar el tipo de shader y convertirlo en especular, usamos la función Find (que estudiaremos en su
momento cuando demos la clase Shader) y se la asignamos a nuestra esfera.
var miShader : String;
renderer.material.shader = Shader.Find( "Specular" );
miShader = renderer.material.shader.ToString();
Debug.Log(miShader);

color:

var color : Color

El color principal del material. Es lo mismo que -como veremos en breve- usar GetColor o SetColor con el nombre
“_Color”.

Añadimos una declaración a nuestro script para cambiarle el color a la esfera:


var miShader : String;
renderer.material.shader = Shader.Find( "Specular" );
renderer.material.color = Color.cyan;
miShader = renderer.material.shader.ToString();
Debug.Log(miShader);

mainTexture:

var mainTexture : Texture

La textura principal del material. Es lo mismo que usar GetTexture o SetTexture con el nombre “_MainTex”.

Si por ejemplo escribiéramos un script así:


var texture : Texture;
renderer.material.mainTexture = texture;

tendríamos una variable "expuesta" donde podríamos arrastrar la textura principal que quisiéramos que tuviera nuestro
material.
mainTextureOffset:

var mainTextureOffset : Vector2

Nos permite desplazar la textura principal.

Hay un ejemplo muy indicativo en el manual de referencia de Unity, que he modificado ligeramente. Con carácter previo
necesitaría que os descargárais una textura, a poder ser alguna cuyo movimiento fuera perceptible. Yo por ejemplo voy
a utilizar esta:

http://www.blogger.com/img/blank.gif

Una vez descargada, la guardamos dentro de la carpeta "assets" de nuestro proyecto en Unity, de tal manera que en
Proyecto nos salga.

Acto seguido escribimos este script:


var miTextura : Texture;
renderer.material.mainTexture = miTextura;
var scrollSpeed : float = 0.5;
function Update() {
var offset : float = Time.time * scrollSpeed;
renderer.material.mainTextureOffset = Vector2 (offset, 0);
}

Salvamos y arrastramos la textura hasta la variable expuesta del script miTextura.

Le damos al play y observaremos que la textura de nuestra esfera comienza a girar (ojo, si vemos las variables rotate
de la esfera observaremos que no es el objeto el que gira, sino la textura la que se desplaza sobre el objeto).
Meramente estamos cambiando de sitio a nuestra textura con respecto a nuestro objeto (pensemos en aplicaciones
como anuncios de neón en una ciudad futurista).

60. CLASE MATERIAL (II)

mainTextureScale:

var mainTextureScale : Vector2

La escala de la textura principal.

FUNCIONES:

Constructor:

static function Material (contents : String) : Material

Crea un material temporal a partir de un shader descrito por un string.


static function Material (shader : Shader) : Material

O bien crea un material temporal directamente desde un shader que le proporcionemos de entre los que tengamos. A
grandes rasgos la creación de un nuevo material sería así:
var shader : Shader;
var texture : Texture;
var color : Color;
function Start () {
renderer.material = new Material (shader);
renderer.material.mainTexture = texture;
renderer.material.color = color;
}

Arrastrándole el shader, material y color, o cuanto menos el primero, creamos un material totalmente nuevo.

static function Material (source : Material) : Material

Crea un material temporal copiando el shader y todas las propiedades del material que le pasamos como parámetro.

SetColor:

function SetColor (propertyName : String, color : Color) : void

Nos permite indicar el nombre para un determinado color. Muchos shaders usan más de un color, por ejemplo:

"_Color" es el color principal del material, que es el que puede ser también accedido desde la propiedad "color" de la
clase.
"_SpecColor" es el color especular de un material (usado en shaders specular/glossy/vertexlit).
"_Emission" es el color que emite un material (usado en vertexlit shaders).
"_ReflectColor" es el color de reflexión del material (usado en reflective shaders).

En el ejemplo siguiente, sacado del manual de referencia, asignamos el shader glossy (brillante) a nuestra esfera, y
establecemos su color especular en rojo:
function Start () {
renderer.material.shader = Shader.Find ("Glossy");
renderer.material.SetColor ("_SpecColor", Color.red);
}

GetColor:

function GetColor (propertyName : String) : Color

Obtiene el valor de un color con nombre. Por ejemplo:


print (renderer.material.GetColor("_SpecColor"));

SetTexture:

function SetTexture (propertyName : String, texture : Texture) : void


Establece el nombre de una textura. Permite cambiar el propertyName de la textura.

Los nombres comunes de las texturas que encontramos ya creadas en Unity son:

"_MainTex" es la textura difusa principal. Puede ser accedida también a través de la propiedad mainTexture.
"_BumpMap" es el mapa de normales.
"_Cube" se refiere al cubemap.

GetTexture:

function GetTexture (propertyName : String) : Texture

Obtiene el nombre de una textura.

61. CLASE MATERIAL (y III)

SetTextureOffset:

function SetTextureOffset (propertyName : String, offset : Vector2) : void

Establece el lugar de desplazamiento de la textura pasada como primer parámetro.

GetTextureOffset:

function GetTextureOffset (propertyName : String) : Vector2

Obtiene la ubicación del desplazamiento de la textura pasada como parámetro.

SetTextureScale:

function SetTextureScale (propertyName : String, scale : Vector2) : void

Establece la escala de la textura pasada como primer parámetro.

GetTextureScale:

function GetTextureScale (propertyName : String) : Vector2

Obtiene la escala de la textura pasada como parámetro.

SetFloat:

function SetFloat (propertyName : String, value : float) : void


Establece un valor tipo float con nombre.

GetFloat:

function GetFloat (propertyName : String) : float

Obtiene un valor de tipo float con nombre.

HasProperty:

function HasProperty (propertyName : String) : boolean

Comprueba si el shader del material tiene una propiedad con un nombre determinado.

Por ejemplo:
if(renderer.material.HasProperty("_Color"))
renderer.material.SetColor("_Color",Color.red);

GetTag:

function GetTag (tag : String, searchFallbacks : boolean, defaultValue : String = "") : String

Obtiene el valor del tag del shader del material. Si el shader del material no tiene definido el tag, devuelve defaultValue.
Si el parámetro searchFallbacks es true, entonces esta función buscará el tag en todos los subshaders. Si
searchFallbacks es falso entonces sólo se hará la consulta para el actual subshader.

Lerp:

function Lerp (start : Material, end : Material, t : float) : void

Interpola propiedades entre dos materiales. Hace que todos los colores y valores del primer material sean convertidos
en los valores del segundo material en el tiempo t. Cuanto el tercer parámetro (t) es cero, se toman los valores de start;
cuando es 1, los valores se toman de end.

CopyPropertiesFromMaterial:

function CopyPropertiesFromMaterial (mat : Material) : void

Copia propiedades de otro material en este material.

62. CLASE CUBEMAP


Como vemos, esta clase deriva directamente de Material, que vimos en la lección pasada. No es quizás la clase que
más vayamos a utilizar, ya que por norma general utilizaremos tal cual los cubemaps con los que contemos, pero
tampoco está de más tener un conocimiento mínimo de la clase.

VARIABLES:

format:

var format : TextureFormat

El formato de los datos de los píxeles en la textura. Sólo lectura.

FUNCIONES:

Cubemap:

static function Cubemap (size : int, format : TextureFormat, mipmap : boolean) : Cubemap

Crea una nueva textura de cubemap. La textura puede tener tamaño en cada lado y con o sin mipmaps.

SetPixel:

function SetPixel (face : CubemapFace, x : int, y : int, color : Color) : void

Coloca el color del pixel en las coordenadas (face, x, y)

GetPixel:

function GetPixel (face : CubemapFace, x : int, y : int) : Color

devuelve el color del pixel en las coordenadas (face, x, y)


GetPixels:

function GetPixels (face : CubemapFace, miplevel : int = 0) : Color[]

Devuelve el color del pixel de una cara del cubemap. Devuelve un array de colores de píxeles de una cara del
cubemap.

SetPixels:

function SetPixels (colors : Color[], face : CubemapFace, miplevel : int = 0) : void

Establece colores de pixeles de una cara del cubemap. Esta función toma un array de color y cambia los colores de los
píxeles de la cara completa del cubemap.

Apply:

function Apply (updateMipmaps : boolean = true) : void

Aplica en realidad todos los previos cambios de SetPixel y Setpixels.

63. CLASE RENDERTEXTURE (I)

Al igual que Cubemap, la clase RenderTexture hereda directamente de Material. Hemos de advertir antes que nada que
esta clase sólo está disponible para Unity Pro, así que quien no tenga o confíe en tener la versión de pago de Unity
puede saltarse tranquilamente estas lecciones.

Render textures son texturas que pueden ser renderizadas. Pueden ser usadas para implementar imágenes basadas en
efectos de renderizado, sombras dinámicas, proyectores, reflexiones o cámaras de vigilancia.

Como es posible que -como a mí en su momento- esa definición no nos diga nada, vamos a acercarnos por la vía de los
hechos a las rendertextures. Hemos de seguir los siguientes pasos:

1.- Eliminamos el script vinculado a la esfera.


2.- Al cubo, en scale, le damos los siguientes valores: 5,5,0.1, y en position: -2,2,0.
3.- En el menú, nos vamos a Assets=>Create=>Render Texture.
4.- Asimismo en el menú, le damos a Gameobject=>Create other=>camera.
5.- A esta nueva cámara le quitamos el componente Audio Listener, para que no nos dé problemas de duplicidad.
6.- Con camera seleccionada, en el inspector veremos una variable denominada Target Texture. Arrastramos hasta ahí
la renderTexture creada antes y que está en Proyecto.
7.- Establecemos las siguientes coordenadas para camera: Position, 2,1,0, Rotation 90,0,0. (nos aseguramos de que la
esfera esté en 2,0,0.). Deberíamos estar viendo en la vista previa de la nueva cámara la esfera muy de cerca.
8.-Arrastramos la Render Texture de Proyecto al cubo.
9.- Pulsamos play.

Deberías estar viendo algo como esto:

Por lo tanto, observamos que render textures es un tipo especial de textura que se crea y actualiza en tiempo de
ejecución, y que convierte en textura el fruto del renderizado de una cámara. Para ello observamos que necesitamos
crear (en la interfaz o a través de un script) una nueva render textura y asignarla a una cámara para que la renderice.
Acto seguido creamos o designamos una superficie que reciba esa textura así creada.

Decimos que se crea y actualiza en tiempo de ejecución. Para demostrar tal aseveración, vamos a crear un pequeño
script. Doble click en MiPrimerScript:
var miTextura : Texture;
function Update() {
renderer.material.mainTexture = miTextura;
transform.Rotate(0,20,0);
}

Si todavía tenemos en Proyecto la textura que nos descargamos hace un par de lecciones, la arrastramos a miTextura.
Si no, hacemos lo propio con cualquier textura no uniforme (para que se note el efecto de la rotación de la esfera) que
tengamos a mano. Le damos al play.

Pensemos en las enormes posibilidades que nos abre esta clase, que nos permite convertir en textura o plasmar sobre
cualquier superficie algo que está siendo grabado en ese momento en otro lugar (esto nos sirve tanto para implementar
el contenido de una cámara de seguridad como para elaborar todo tipo de espejos o superficies reflectantes.)
VARIABLES:

width:

var width : int

La anchura de la textura renderizada en píxeles. A diferencia de Texture.width, esta variable no es de sólo lectura, y
permite establecer un valor para cambiar la anchura.

height:

var height : int

La altura de la textura renderizada en píxeles. Le es de aplicación lo dicho para width.

64. CLASE RENDERTEXTURE (II)

depth:

var depth : int

La precisión en bits de la profundidad del búfer de la render texture (son soportados los valores 0, 16 y 24)

format:

var format : RenderTextureFormat

El formato de la render texture. RenderTextureFormat es una enumeración que permite estos valores:

RenderTextureFormat es una enum con estos valores:


ARGB32: Formato de color de la render texture, 8 bits por canal.
Depth: Un formato de profundidad de la render texture.
ARGBHalf: Formato de color de la render texture, 16 bit en punto flotante por
canal.
RGB565: Formato de color de la render texture.
ARGB4444: Formato de color de la render textura, 4 bit por canal.
ARGB1555: Formato de color de la render texture, 1 bit para el canal Alpha, 5
bits para los canales del rojo, verde y azul.
Default: Formato de color por defecto de la render texture, dependiendo del
formato de bufer por frame y la plataforma.
useMipMap:

var useMipMap : boolean

Por defecto, las render textures no tienen mipmaps. Si establecemos esta variable en true, se generarán niveles de
mipmap asociados.

Este flag puede ser usado sólo en render textures que sean potencias de dos.

var isCubemap : boolean

var isCubemap : boolean

Si está habilitada, esta render texture será usada como Cubemap.

FUNCIONES:

RenderTextures:

static function RenderTexture (width : int, height : int, depth : int, format : RenderTextureFormat) : RenderTexture

Crea un nuevo objeto RenderTexture, que es creado con anchura y altura, con un buffer de profundidad y en un
determinado formato. Sería lo mismo que hicimos en el ejemplo de la lección anterior, pero a través de un script.

Cuando invocamos a través del constructor un nuevo objeto RenderTexture no estamos todavía creándolo en realidad .
El RenderTexture será creado o bien la primera vez que se usa o bien llamando de manera expresa a la función Create.
Así que después de construir la render texture, dado que aún no ha tomado forma la representación final de la misma,
es posible establecer variables adicionales, como format, isCubemap y similares.

static function RenderTexture (width : int, height : int, depth : int) : RenderTexture

Este segundo prototipo del constructor es idéntico al anterior, salvo que no se establece de manera expresa el formato
de la rendertexture. La render texture es colocada para estar en color format por defecto.

Create:

function Create () : boolean

Tal como acabamos de indicar, es esta función la que crea en realidad la RenderTexture.

Release:

function Release () : void

Esta función libera los recursos de hardware usados por la render texture. La texture en sí no es destruida, y será
automáticamente creada otra vez cuando se use.

IsCreated:
function IsCreated () : boolean

Indica si la render texture se ha creado realmente o no.

65. CLASE RENDERTEXTURE (y III)

DiscardContents:

function DiscardContents () : void

Descarta el contenido de la render texture.

SetGlobalShaderProperty:

function SetGlobalShaderProperty (propertyName : String) : void

Asigna esta render texture como la propiedad shader global llamada en el parámetro propertyName.

VARIABLES DE CLASE:

active:

static var active : RenderTexture

Se refiere a la render texture activa. Todos los renderings van dentro de la rendertexture activa. Si esta es null todo se
renderiza en la ventana principal. Cuando una RenderTexture se convierte en activa su contexto de hardware de
renderizado es automáticamente creado si no se había creado ya.

FUNCIONES DE CLASE:

GetTemporary:

static function GetTemporary (width : int, height : int, depthBuffer : int = 0, format : RenderTextureFormat =
RenderTextureFormat.Default) : RenderTexture

Asigna una render texture temporal.

Esta función es optimizada para cuando necesitas una RenderTexture rápida para hacer algunos cálculos temporales.
Libérala usando ReleaseTemporary tan pronto hayas hecho el trabajo, así podrás reusarla otra vez en una nueva
llamada si la necesitas.

ReleaseTemporary:
static function ReleaseTemporary (temp : RenderTexture) : void

Libera una textura temporal asignada con GetTemporary.

66. CLASE PARTICLEEMITTER (I)

Antes que nada, pediros disculpas porque esa clase se me había pasado. Como podéis ver en el gráfico, ParticleEmitter
deriva de Component (como Collider, Rigidbody, Transform...) y por lo tanto la debería haber explicado antes, pero
bueno, subsanamos el error ahora.

Vamos a preparar antes que nada nuestra escena para acoger los nuevos ejemplos de esta clase:

1.- Borramos la cámara que creamos en la clase RenderTexture


2.- Devolvemos el cubo a sus valores previos: position (-2,0,0) scale(1,1,1)
3.- Eliminamos el script vinculado a la esfera.
4.- Eliminamos la render texture en la carpeta Proyecto.
5.- Nos vamos al menú Gameobject=>Create other=>Particle System.
6.- Ubicamos nuestro nuevo gameobject en 0,0,0 y lo renombramos como "Particulas".
7.- Salvamos la escena.

Deberíamos tener ahora una escena parecida a esta:


Vale, ya estamos listos.

VARIABLES:

emit:

var emit : boolean

Booleano que indica si deben las partículas ser automáticamente emitidas cada frame o no.

Podemos usar esta variable para "encender o apagar" la emisión de partículas.

Editemos nuestro script de la siguiente manera:


yield WaitForSeconds(5);
particleEmitter.emit = false;
yield WaitForSeconds(5);
particleEmitter.emit = true;

Observaremos que tras los cinco segundos iniciales, nuestro sistema de partículas comienza a extinguirse hasta
desaparecer, y pasados otros cinco, vuelve a encenderse.

minSize:

var minSize : float

El tamaño mínimo que cada partícula puede tener cuando se genera.

maxSize:

var maxSize : float

El tamaño máximo que cada particular puede tener al tiempo de ser generada.
Si tenemos el gameobject Particulas seleccionado, podemos comprobar que por defecto el tamaño mínimo (y máximo)
es 0.1. Veamos qué pasa si alteramos estos valores:
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;

minEnergy:

var minEnergy : float

El mínimo tiempo de vida de cada particular, medido en segundos.

maxEnergy:

var maxEnergy : float

El máximo tiempo de vida de cada partícula, medido en segundos.

En el inspector esta variable y la anterior está colocada en 3 por defecto. Añadamos al script anterior unos valores
distintos.
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;
particleEmitter.minEnergy = 1;
particleEmitter.maxEnergy = 5;

Vemos que conseguimos así una mayor variedad en la persistencia de cada partícula, dándole mayor dinamismo a
nuestro sistema de partículas.

Dejamos aquí esta lección. Si aún te queda algo de tiempo no cierres el editor de scripts, ya que seguiremos en la
próxima lección añadiendo modificaciones a nuestro ejemplo.

67. CLASE PARTICLEEMITTER (II)

minEmission:

var minEmission : float

El mínimo número de partículas que serán generadas cada segundo.

maxEmission:

var maxEmission : float


El máximo número de partículas que serán generadas cada segundo.

Los valores por defecto en el inspector son 50. Editemos nuestro script para alterarlo.
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;
particleEmitter.minEnergy = 1;
particleEmitter.maxEnergy = 5;
particleEmitter.minEmission = 70;
particleEmitter.maxEmission = 110;

Tenemos ahora un sistema de partículas más denso y compacto, como vemos.

emitterVelocityScale:

var emitterVelocityScale : float

La cantidad de la velocidad de emisión que las partículas heredan. Por defecto 0.05.

worldVelocity:

var worldVelocity : Vector3

La velocidad inicial de las partículas en el mundo global, a lo largo de x,y,z.

Incorporemos estas dos últimas variables a nuestro ejemplo, dándole a nuestro sistema de partículas una mayor
velocidad de emisión y haciendo que nuestras partículas se desplacen hacia arriba (en coordenadas globales):
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;
particleEmitter.minEnergy = 1;
particleEmitter.maxEnergy = 5;
particleEmitter.minEmission = 70;
particleEmitter.maxEmission = 110;
particleEmitter.emitterVelocityScale = 0.4;
particleEmitter.worldVelocity = Vector3(0,3,0);

localVelocity:

var localVelocity : Vector3

Es como la anterior, pero el desplazamiento se efectúa en las coordenadas locales del propio sistema de partículas.

rndVelocity:

var rndVelocity : Vector3

Una velocidad aleatoria con relación a los ejes x,y,z que es añadida a la velocidad. En nuestro ejemplo:
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;
particleEmitter.minEnergy = 1;
particleEmitter.maxEnergy = 5;
particleEmitter.minEmission = 70;
particleEmitter.maxEmission = 110;
particleEmitter.emitterVelocityScale = 0.4;
particleEmitter.worldVelocity = Vector3(0,3,0);
particleEmitter.rndVelocity = Vector3(1,0.5,0.5);

useWorldSpace:

var useWorldSpace : boolean

Si está habilitado, las partículas no se mueven cuando el emitter se mueve. Si está en false, cuando muevas el emitter,
las partículas lo siguen.

rndRotation:

var rndRotation : boolean

Si está habilitado, las partículas serán generadas con rotaciones aleatorias. Por defecto está en false, así que
observemos qué ocurre si lo habilitamos:
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;
particleEmitter.minEnergy = 1;
particleEmitter.maxEnergy = 5;
particleEmitter.minEmission = 70;
particleEmitter.maxEmission = 110;
particleEmitter.emitterVelocityScale = 0.4;
particleEmitter.worldVelocity = Vector3(0,3,0);
particleEmitter.rndVelocity = Vector3(1,0.5,0.5);
particleEmitter.rndRotation = true;

angularVelocity:

var angularVelocity : float

La velocidad angular de las nuevas partículas en grados por segundo.

rndAngularVelocity:

var rndAngularVelocity : float

Un modificador aleatorio de velocidad angular para nuevas partículas. Un valor aleatorio en el rango de [-
rndAngularVelocity,rndAngularVelocity] será aplicado a todas las nuevas partículas, en adición a ParticleEmitter.
angularVelocity.
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;
particleEmitter.minEnergy = 1;
particleEmitter.maxEnergy = 5;
particleEmitter.minEmission = 70;
particleEmitter.maxEmission = 110;
particleEmitter.emitterVelocityScale = 0.4;
particleEmitter.worldVelocity = Vector3(0,3,0);
particleEmitter.rndVelocity = Vector3(1,0.5,0.5);
particleEmitter.rndRotation = true;
particleEmitter.angularVelocity = 1;
particleEmitter.rndAngularVelocity = 66;

68. CLASE PARTICLEEMITTER (y III)


particles:

var particles : Particle[]

Devuelve una copia de todas las partículas y asigna un array de todas las partículas. Hemos de tener en cuenta que
después de modificar el array de partículas debemos asignarlo de vuelta al particleEmitter para ver el cambio. Partículas
con energía de cero o menos serán elmiminadas cuando se asignen las partículas. Así cuando se crea un completo
nuevo array de partículas, necesitaremos colocar la energía de todas las partículas explicitamente.

particleCount:

var particleCount : int

El corriente número de partículas. Variable de sólo lectura.

enabled:

var enabled : boolean

Establece el ParticleEmitter en on o off. Un ParticleEmitter que no está habilitado no emitirá ninguna partícula, y las
partículas emitidas no se animarán. Así, este valor nos permite pausar un sistema de partículas.

Reutilicemos el ejemplo anterior para pausar nuestro sistema de partículas transcurridos cinco segundos.
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;
particleEmitter.minEnergy = 1;
particleEmitter.maxEnergy = 5;
particleEmitter.minEmission = 70;
particleEmitter.maxEmission = 110;
particleEmitter.emitterVelocityScale = 0.4;
particleEmitter.worldVelocity = Vector3(0,3,0);
particleEmitter.rndVelocity = Vector3(1,0.5,0.5);
particleEmitter.rndRotation = true;
particleEmitter.angularVelocity = 1;
particleEmitter.rndAngularVelocity = 66;
yield WaitForSeconds(5);
particleEmitter.enabled = false;

FUNCIONES:

ClearParticles:

function ClearParticles () : void


Borra todas las partículas del sistema de partículas.

Emit:

function Emit () : void

Emite un número de partículas. Hace que el emitter escupa un número aleatorio de partículas que se establecen entre
las propiedades minEmission y maxEmission. Probemos:
particleEmitter.minSize = 0.2;
particleEmitter.maxSize = 0.5;
particleEmitter.minEnergy = 1;
particleEmitter.maxEnergy = 5;
particleEmitter.minEmission = 20;
particleEmitter.maxEmission = 150;
particleEmitter.emitterVelocityScale = 0.4;
particleEmitter.angularVelocity = 1;
particleEmitter.rndAngularVelocity = 66;
particleEmitter.Emit();

function Emit (count : int) : void

Esta variante de la función emite el número count de partículas inmediatamente. Pasémosle el parámetro 300 a la
función Emit del ejemplo anterior, por poner un caso muy exagerado.

function Emit (pos : Vector3, velocity : Vector3, size : float, energy : float, color : Color) : void

Este tercer prototipo de la función Emit emite una partícula individual con unos parámetros dados. Expliquemos
brevemente qué representa cada parámetro:

pos: La posición de la partícula.


velocity: La velocidad de la partícula.
size: El tamaño de la partícula.
energy: El tiempo de vida restante de la partícula.
color: El color de la partícula.
particleEmitter.Emit(Vector3.zero, Vector3.up, 0.4, 4, Color.yellow);

Cuando le damos al play veremos una partícula en la posición central, con dirección hacia arriba, un tamaño de 0.4
unidades, que tardará 4 segundos en desvanecerse y tendrá color amarillo.

function Emit (pos : Vector3, velocity : Vector3, size : float, energy : float, color : Color, rotation : float, angularVelocity :
float) : void

Y aún tenemos una tercera variante de la función, que incluye la rotación inicial de la partícula en grados y la velocidad
angular por segundo.
particleEmitter.Emit(Vector3.zero, Vector3.up, 0.4, 4, Color.yellow, 75, 150);

Simulate:

function Simulate (deltaTime : float) : void

Avanzado sistema de simulación de partículas por un tiempo dado. Es útil para precalentar un sistema de partículas,
como si se estuviera gestando:
particleEmitter.Simulate(5);

69. CLASE MESH (I)


Esta clase permite crear o modificar mallas desde scripts.

Las mallas contienen vértices y múltiples arrays de triángulos. Los arrays de triángulos son meramente índices dentro
del array de vértices, tres índices para cada triángulo.

Para cada vértice puede haber un normal, dos coordenadas de texturas, color y tangentes. Toda la información de los
vértices es almacenada en arrays separados del mismo tamaño, así que si tu malla tiene 10 vértices, debe tener
tambíen arrays de 10 de tamaño para normales y otros atributos.

VARIABLES:

vertices:

var vertices : Vector3[]

Devuelve una copia de la posición del vértice o asigna un nuevo array de posiciones de vértices.

El número de vértices en la malla es cambiado asignando un array de vértices con un diferente número de vértices.
Debemos tener en cuenta que si redimensionamos el array de vértices entonces todos los otros atributos (normales,
colores, tangentes, Uvs) serán automáticamente redimensionados también. RecalculateBounds automáticamente será
invocado si no se han asignado vértices a la malla cuando se establecieron los vértices.

normals:

var normals : Vector3[]

Los normales de la malla. Si la malla no contiene normales un array vacío es retornado.

tangents:

var tangents : Vector4[]


Las tangentes de la malla.

Las tangentes son mayormente usadas en bump-mapped shaders. Una tangente es un vector de una unidad de
longitud que sigue la malla a lo largo de la dirección de textura horizontal. Las tangentes en Unity son representadas
como Vector4 con componentes x,y,z definiendo el vector, y w usada para dar la vuelta a la binormal si es necesario.

Unity calcula los otros vectores de supervicie (binormal) tomando un producto cruzado entre normal y tangente y
multiplicando el resultado por la tangente w. Así w debe ser siempre 1 o -1.

Debes calcular tangentes por ti mismo si planeas usar bump-mapped shaders en la malla. Asigna tangentes después de
asignar normals o usando RecalculateNormals.

uv:

var uv : Vector2[]

Las coordenadas de la textura base de la malla.

rv2:

var uv2 : Vector2[]

El segundo conjunto de coordenadadas de textura de la malla, si lo hubiere.

bounds:

var bounds : Bounds

El volumen de bordes de la malla. Esto es la caja de bordes alineadas en el eje de la malla en el espacio local (esto es,
no afectada por el transform). Existe por otro lado la propiedad Renderer.bounds que retorna los bordes en espacio
global.

colors:

var colors : Color[]

Devuelve los colores del vértice de la malla. Si no hay colores devuelve un array vacío.

triangles:

var triangles : int[]

Un array conteniendo todos los triángulos de la malla. El array es una lista de triángulos que contiene índices dentro del
array de vértices. El tamaño del array de triángulos debe siempre ser múltiplo de 3. Los vértices pueden ser compartidos
meramente indexando dentro del mismo vértice. Si la malla contiene múltiples sub mallas (materiales) la lista de
triángulos contendrá todos los triángulos de todas las submallas. Cuando asignas un array de triángulos, subMeshCount
es colocado a 1. Si quieres tener múltiples submallas, usa subMeshCount y SetTriangles.

Es recomendable asignar el array de triángulos después de asignar el array de vértices para evitar errores de fuera de
límite (out of bounds)
vertexCount:

var vertexCount : int

Variable de sólo lectura que devuelve el número de vértices en la malla.

subMeshCount:

var subMeshCount : int

El número de submallas. Cada material tiene una lista de triángulos separada.

boneWeights:

var boneWeights : BoneWeight[]

La pintura de pesos de cada vértice. El tamaño del array es o el mismo que vertexCount o vacío.

Cada vértice puede ser afectado por un máximo de 4 diferentes huesos. Las cuatro pinturas de peso deben sumar hasta
1.

70. CLASE MESH (y II)


FUNCIONES:

Mesh:

static function Mesh () : Mesh

Crea una malla vacía.

Clear:

function Clear () : void

Limpia todos los datos de los vértices y todos los índices de los triángulos. Debes llamar esta función antes de
reconstruir el array de triángulos.

RecalculateBounds:

function RecalculateBounds () : void

Recalcula el volumen de bordes de la malla desde los vértices. Después de modificar los vértices debes llamar a esta
función para asegurarte de que el volumen de bordes es correcto. Asignando triángulos automáticamente se recalcula el
volumen de bordes.

RecalculateNormals:

function RecalculateNormals () : void

Recalcula los normales de la malla desde los triángulos y vértices. Después de modificar los vértices es a menudo útil
actualizar los normales para reflejar el cambio. Los normales son calculados desde todos los vértices compartidos. Las
mallas importadas a veces no comparten todos los vértices. Por ejemplo un vértice en una costura UV se partirá en dos
vértices. En consecuencia la función RecalculateNormals creará normales que no son lisos en las costuras Uv. Notemos
también que RecalculateNormals no genera tangentes automáticamente, asi que los bumpmap shaders no trabajarán
con la malla después de llamar a esta función. Nosotros podemos sin embargo proveer nuestras propias tangentes.

Optimize:

function Optimize () : void

Optimiza la malla para mostrarla. Esta operación podría tomar un rato pero hace que la geometría se muestre más
rápido. Debes usarla si generas una malla desde cero procedimentalmente y quieres un mayo rendimiento en tiempo de
ejecución en lugar de un mayor tiempo de carga. Para modelos importados no debes nunca llamarla porque el import
pipeline ya lo hace por ti.

GetTriangles:

function GetTriangles (submesh : int) : int[]


Devuelve la lista de triángulos de la submalla. Una submalla es simplemente una lista de triángulos separada. Cuando
el mesh renderer usa múltiples materiales, debes asegurarte de que hay tantas submallas como materiales.

SetTriangles:

function SetTriangles (triangles : int[], submesh : int) : void

Establece la lista de triángulos para la submalla.

Es recomentable asignar el array de triángulos después de asignar el array de vértices para evitar el error de fuera de
bordes.

CombineMeshes:

function CombineMeshes (combine : CombineInstance[], mergeSubMeshes : boolean = true, useMatrices : boolean =


true) : void

Combina varias mallas dentro de la malla. Combinar mallas es útil para optimización de rendimiento. Si
mergeSubMeshes es true, todas las mallas serán combinadas en una única submalla. En caso contrario cada malla irá
dentro de una submalla diferente. Si todas las mallas comparten el mismo material, coloca esto a true. Si useMatrices es
false, el transform matrices en la estructura CombineInstance será ignorado.

71. CLASE GAMEOBJECT (I)


Estamos en la clase central, en la clase con mayúsculas de Unity. De hecho, muchas de las clases que hasta la fecha
hemos estudiado (y algunas que nos faltan) no son más que diferentes mimbres cuya función primordial era confluir en
esta clase.

No deberá sorprendernos, por tanto, que buena parte de las variables o propiedades de la clase GameObject no son
sino instancias de las clases que ya hemos estudiado, de tal forma que cualquier gameobject en la escena (recordemos
que todo lo que está en la escena son gameobjects) tenga la más amplia funcionalidad.

VARIABLES:

isStatic:

var isStatic : boolean

Variable sólo utilizable vía interface que especifica si un objeto es estático.

Esto es útil cuando estemos trabajando con occlusion culling, para determinar si un objeto puede ser considerado un
oclusor estático.

Para quien no sepa qué diantres es lo de occlusion culling, decir que es una característica que deshabilita el
renderizado de objetos cuando no están siendo actualmente vistos por la cámara porque son oclusionados/tapados por
otros objetos.

El proceso de occlusion culling va a travé de la escena usando una cámara virtual construyendo una jerarquía de
objetos potencialmente visibles. Estos datos serán usados en tiempo de ejecución por cada cámara para identificar qué
es visible y qué no.

Necesitamos etiquetar todos los objetos de la escena que queramos que sean parte de la occlusion como Static en el
inspector. La manera más rápida de hacerlo es convertir todos los objetos que queramos marcar como estáticos en hijos
de un GameObject vacío y establecer éste como Static, eligiendo luego en la opción de diálogo que la condición de
static afecte también a todos sus hijos. Después de eso, ya podemos tranquilamente desparentarlos del gameobject
vacío, y seguirán teniendo la consideración de static.

transform:

var transform : Transform


El transform del gameobject, si lo tiene. Null si no tiene ningún transform vinculado.

Tal como explicaba antes, ésta va a ser la tónica de la mayoría de propiedades de la clase Gameobject: integrar
objetos/instancias de diferentes clases para conformar la utilidad básica de nuestro juego: los gameobjects. Podemos
demostrar que todo lo que está en la escena es un gameobject:

Eliminamos antes que nada el gameobject particulas que creamos en la clase anterior. Si tenemos algún script
vinculado al cubo o la esfera, los eliminamos.

Vamos a editar ahora MiPrimerScript:


var unGameObject: GameObject;
function Update() {
unGameObject.transform.Rotate(0,5,0);
}

El script debería resultarnos fácil a estas alturas. Declaramos una variable expuesta de tipo Gameobject, de tal manera
que posteriormente podamos acceder al transform de la que arrastremos y rotar el gameobject sobre el eje Y.

Salvamos. Arrastramos el script a PortaScripts en la jerarquía. Con PortaScripts seleccionado, arrastramos el cubo a la
variable expuesta. Play.

Tal como era de esperar, el cubo comienza a girar. Pero vamos a ver qué otros elementos en la escena son
considerados por Unity Gameobjects, y por tanto susceptibles de tener un transform. Si con PortaScripts seleccionado
nos vamos al inspector, observaremos que a la derecha de la variable expuesta que hemos inicializado con el cubo hay
una pequeña flecha. Si hacemos click sobre ella, se nos abre un menú emergente con TODOS los gameobjects de la
escena. De hecho hasta nuestro PortaScripts -a pesar de no ser ni visible- es considerado un gameobject. Así, si en ese
mismo popup hacemos doble click en main camera y le damos al play, observaremos en la vista del juego que lo que
empieza a girar es la cámara.

rigidbody:

var rigidbody : Rigidbody

El rigidbody vinculado a nuestro gameobject, o null si éste no tiene rigidbody.

camera:

var camera : Camera

La cámara vinculada a nuestro gameobject, o null si éste no tiene una cámara. Por ejemplo, nuestra main camera es un
gameobject que tiene vinculado una cámara (y un transform).

light:

var light : Light

La luz vinculada al gameobject. Null si no tiene.

72. CLASE GAMEOBJECT (II)


animation:

var animation : Animation

La animación vinculada al gameobject. Null si no tiene.

No hemos tratado aún nada relacionado con animaciones, clips y sonidos, ya que he preferido reservarlo para más
adelante y tratarlo todo junto.

constantForce:

var constantForce : ConstantForce

la constantForce vinculada a este gameobject. Null si no tiene ninguna vinculada.

No habíamos tratado todavía la miniclase ConstantForce, pero vamos a subsanar ese pequeño lapsus ahora mismo:

La clase ConstantForce deriva de Behaviour (como camera, light, animation...) y cuenta sólo con cuatro
variables/propiedades (amén de las heredadas), que son:
force: var force : Vector3
La fuerza aplicada al rigidbody cada frame. Esto último es lo que la diferencia de
rigidbody.AddForce(). En AddForce se aplica la fuerza al rigidbody una vez por
frame, lo que obliga a llamar a la función varias veces (por ejemplo dentro de una
función fixedUpdate). constantForce.force aplicará la fuerza indicada cada frame
hasta que cambiemos el contenido de la variable force a un nuevo valor. Esto es
aplicable a las cuatro variables de esta clase.
relativeForce: var relativeForce : Vector3
La fuerza -relativa al sistema de coordenadas local del rigidbody- aplicada cada
frame.
torque: var torque : Vector3
La torsión aplicada al rigidbody cada frame.
relativeTorque: var relativeTorque : Vector3
La torsión -relativa al sistema de coordenadas local del rigidbody- aplicada cada
frame.

Pongamos un ejemplo global: Antes que nada seleccionamos el cubo, y nos vamos al
menú=>Component=>Physics=>Constant Force.

Editamos una vez más MiPrimerScript:


var unGameObject: GameObject;
unGameObject.constantForce.force = Vector3(3,10,0);
unGameObject.constantForce.torque = Vector3(0,8,0);

Salvamos. Recordemos que este script lo tenemos vinculado al gameobject PortaScripts, así que lo seleccionamos.
Arrastramos el cubo a la variable expuesta. Observemos que ambos atributos están fuera de toda función o bucle, esto
es, que tal como hemos dicho, tanto la fuerza (hacia la derecha y hacia arriba) como la torsión (sobre el eje Y) que le
aplicamos tendrán carácter periódico debido única y exclusivamente a las propias características de la clase
constantForce. Play.

renderer:

var renderer : Renderer

El renderer vinculado al gameobject. Null si no tiene.

audio:

var audio : AudioSource

el audiosource vinculado al gameobject. Null si no tiene.

73. CLASE GAMEOBJECT (III)


guiText:

var guiText : GUIText

El guiText (de próximo estudio) vinculado al gameobject. Nulo si no existe.

networkView:

var networkView : NetworkView

El networkView (a estudiar mucho más adelante) vinculado al gameobject. Nulo si no existe.

guiTexture:

var guiTexture : GUITexture

El guiTEXture (en breve) vinculado al gameobject. Null si carece de él.

collider:

var collider : Collider

El collider vinculado al gameobject, si lo tiene. Null en caso contrario.

hingeJoint:

var hingeJoint : HingeJoint

El hingeJoint vinculado al gameobject, o null.

particleEmitter:

var particleEmitter : ParticleEmitter

El particleEmitter vinculado al gameobject, null si no tiene.

layer:

var layer : int

Es una variable de tipo int comprendida entre el rango 0 y 31 que indica/coloca la capa en la que el gameobject se
encuentra.

El layer sirve entre otras cosas para realizar un renderizado selectivo de lo que una cámara debe mostrar o para
determinar si a un determinado gameobject les afectará o no un raycast.

active:
var active : boolean

¿Está el gameobject activo?. Podemos habilitarlo/deshabilitarlo cambiando a true o false este booleano.

tag:

var tag : String

El tag (etiqueta) de este game object. Puede ser usado para identificar a un gameobject. Hemos de recordar que antes
de poder usar esta variable debemos haber declarado el tag en el inspector

74. CLASE GAMEOBJECT (IV)

FUNCIONES:

GameObject:

static function GameObject (name : String) : GameObject

La función constructora de gameobject tiene tres prototipos diferentes. El primero, como vemos, nos permite crear un
nuevo gameobject y pasarle un parámetro que constituirá el nombre de dicho gameobject. Veamos un ejemplo sencillo.

Editamos MiPrimerScript:
var nuevoGameObject: GameObject;
nuevoGameObject = new GameObject("miNuevoObjeto");

Lo salvamos. Si no lo estaba, lo arrastramos a PortaScripts. Le damos al play.

Aparentemente no ocurre nada. No aparece ningún objeto en la escena ni en la ventana del juego. Esto es porque
nuestro nuevo gameobject está vacío, como podremos comprobar si -con el juego reproduciéndose- seleccionamos el
nuevo gameobject de nombre miNuevoObjeto que aparece en la jerarquía. Observamos que tan sólo tiene un elemento
transform (que se crea por defecto).

Pero, ya teniendo un gameobject creado en tiempo de ejecución (y volveremos sobre este concepto pasado no
demasiado tiempo), podemos de la misma forma añadirle componentes:
var nuevoGameObject: GameObject;
nuevoGameObject = new GameObject("miNuevoObjeto");
nuevoGameObject.AddComponent ("BoxCollider");
nuevoGameObject.AddComponent ("MeshRenderer");

En este caso le añadimos un collider de cubo y una meshrenderer. Seguirá sin verse nuestro nuevo gameobject -salvo
que lo seleccionemos en la jerarquía, en cuyo caso veremos la malla) porque para ello le deberíamos haber creado una
malla desde cero, pero a los efectos de este ejemplo con esto debería bastar.

static function GameObject () : GameObject


Crea un gameobject, pero sin nombre nombre, lo cual no obsta para que luego se le pueda asignar uno.

static function GameObject (name : String, params components : Type[]) : GameObject

Esta variante del constructor crea un gameobject con nombre y ya vinculado a una serie de componentes prefijados.

GetComponent:

function GetComponent (type : Type) : Component

Esta función devuelve el componente de tipo Type que tenga el gameobject. En caso de que el gameobject que lanza el
mensaje no tenga ningún componente de ese tipo, devuelve null.

Además de acceder a componentes Standard de unity, puedes acceder a scripts por esta vía. En este caso, el nombre
del script equivaldrá al Type del parámetro de búsqueda. Ojo, porque aunque pongamos el nombre del script, como en
realidad sustituye y equivale a un tipo de componente, lo escribiremos sin comillas (no es un string)

Es decir, si por ejemplo quisiéramos -no hace falta que realicéis este ejemplo, es sólo para aclarar conceptos- hacer
algo en caso de que un determinado gameobject tenga un determinado script, haríamos algo como esto:
var unObjetoCualquiera : GameObject;
if(unObjetoCualquiera.GetComponent(miPrimerScript) {
HazAlgunaCosa();
}

Arrastraríamos luego el gameobject que quisiéramos consultar, y si éste tuviera vinculado un script de nombre
miPrimerScript -en caso contrario devolvería null- se ejecuta la función HazAlgunaCosa.

Esta función nos permite además acceder a variables públicas (no private ni dentro de funciones) y funciones que se
hallen en otro script que esté vinculado al mismo gameobject. Pongamos que el script del ejemplo lo vinculamos
directamente a un gameobject (no como el anterior, en que arrastramos el gameobject a la variable expuesta) y dicho
gameobject a su vez tiene vinculado otro script llamado otroScript. Podemos capturarlo con esta función y asignarlo a
una variable de tipo ScriptName.
var unSegundoScript : ScriptName = gameObject.GetComponent(otroScript);

// Una vez ya hemos capturado ese script, como decimos, podemos acceder a sus
//funciones y variables públicas.
unSegundoScript.HazOtraCosa ();
unSegundoScript.tengoUnaVariable = 5;
}

function GetComponent (name : String) : Component

Es identica a la anterior, pero en lugar de acceder a un componente por su tipo, lo hacemos por su nombre a través de
un string (aquí sí colocaremos comillas). Es preferible la primera firma por motivos de rendimiento, pero si no
recordamos el tipo del componente, podemos buscar por el nombre.

Recordemos que el nombre de un script ajeno pero vinculado al gameobject que hace la consulta es también su tipo, lo
cual no obsta a que siga siendo su nombre y podamos consultarlo por él, aunque con comillas (“otroScript”, sería en
este caso la manera de escribir el parámetro de búsqueda)
75. CLASE GAMEOBJECT (V)

GetComponentInChildren:

function GetComponentInChildren (type : Type) : Component

Devuelve un componente activo de tipo que le pasamos como parámetro que pueda tener o bien el Gameobject que
lanza el mensaje o bien sus hijos. La búsqueda empieza por los hijos.

GetComponents:

function GetComponents (type : Type) : Component[]

Esta función devuelve todos los componentes del tipo Type y los devuelve en forma de array del tipo de componente
solicitado.

Esto nos permitiría hacer cosas como las del ejemplo que nos brinda el manual de referencia (no es preciso seguir el
ejemplo, es sólo a modo oritentativo):
//Vamos a desconectar la variable spring de todos los HingeJoints que tenga este
//gameobject. Primero crearemos un array de tipo HingeJoint y lo almacenamos
//en la variable bisagras.

var bisagras : HingeJoint[];

//Y colocamos todos los HingeJoint que encontremos en el array.

bisagras = gameObject.GetComponents (HingeJoint);

//Recorremos ahora en un bucle for in todos los componentes hallados y les


//desactivamos la variable spring.

for (var joint : HingeJoint in bisagras) {


joint.useSpring = false;
}

GetComponentsInChildren:

function GetComponentsInChildren (type : Type, includeInactive : boolean = false) : Component[]


Devuelve todos los componentes del tipo pasado como primer parámetro que tenga ese gameobjects y sus hijos. La
peculiaridad es que la función incluye un segundo parámetro opcional de tipo booleano que si establecemos en true nos
permite recuperar también los componentes inactivos de ese tipo.

SetActiveRecursively:

function SetActiveRecursively (state : boolean) : void

Función que activa o desactiva el estado del gameobject que la invoca y de todos sus hijos, en función de si el bolean
que tiene como parámetro está en true o false.

CompareTag:

function CompareTag (tag : String) : boolean

Esta función nos permite averiguar si un determinado gameobjet tiene como tag el string colocado como parámetro de
la función. Si lo tiene, la función devuelve true.

SendMessageUpwards:

function SendMessageUpwards (methodName : String, value : object = null, options : SendMessageOptions =


SendMessageOptions.RequireReceiver) : void

Esta función llama a una función cuyo nombre pasamos en el parámetro methodName y que se encuentre en cualquier
script que tengamos vinculado al gameobject que hace la llamada o en los "padres" de dicho gameobject. Le podemos,
si procede, pasar un valor a dicho método (value). El parámetro options es del tipo SendMessageOptions, que es un
enum con dos valores = requireReceiver (que necesita respuesta) y dontRequireReceiver, que no la requiere.

Por defecto la función requiere respuesta, lo cual quiere decir que si se le pasa un argumento a la función receptora y
ésta no precisa ninguno, imprimirá un mensaje de error. Si no lo requiriera (dontRequireReceiver), puede optar por
ignorar dicho argumento sin más.

Vamos a intentar aclararlo un poco más con un ejemplo. Previamente eliminemos el script que tenemos vinculado en
PortaScripts. Acto seguido, editamos miPrimerScript y lo dejamos como sigue:
function DameColor (tono : Color) {
renderer.material.color= tono;
}
DameColor(Color.black);

La función no requiere más explicación. La arrastramos al cubo y al darle al play éste se torna de color negro.

Ahora vamos a editar el script miSegundoScript, que si hemos seguido las lecciones debemos tener en Proyecto. (y si
no creamos uno, tampoco nos volvamos locos). Tecleamos:
function OnMouseEnter() {
gameObject.SendMessageUpwards ("DameColor", Color.cyan);
}

Arrastramos tras salvar este script también al cubo. De esta manera y a través de SendMessageUpwards podemos
acceder a la función DameColor de miPrimersScript y usarla a nuestro gusto. En este caso nos limitamos a cambiar el
color del cubo al pasar el ratón por éste, pero obviamente y en caso de gameobjects con un montón de scripts
vinculados (que a eso llegaremos) esta posibilidad de acceder desde un scripts a las funciones que hay en otros es toda
una ventaja.

SendMessage:

function SendMessage (methodName : String, value : object = null, options : SendMessageOptions =


SendMessageOptions.RequireReceiver) : void

Igual que la función anterior, con la diferencia de que en esta sólo podemos llamar a funciones que estén en scripts
vinculados al gameobject, pero no en sus ancestros.

BroadcastMessage:

function BroadcastMessage (methodName : String, parameter : object = null, options : SendMessageOptions =


SendMessageOptions.RequireReceiver) : void

Llama a un método con nombre methodName que esté en cualquier script vinculado a ese game object o a cualquiera
de sus hijos, a diferencia de las funciones anteriores.

76. CLASE GAMEOBJECT (y VI)

AddComponent:

function AddComponent (className : String) : Component

Añade un componente a nuestro gameobject. Ese componente ha de ser una instancia de la clase que ponemos en el
parámetro className, o bien puede ser el nombre de un script. Es útil para añadir componentes en tiempo de
ejecución.

Algunos componentes requieren de la presencia de otros para existir, así que al añadir aquéllos automáticamente se
nos añadirán estos. Pej, si añadimos un HingeJoint automáticamente se nos añadirá un Rigidbody.

function AddComponent (componentType : Type) : Component

La única variación es que en lugar de string, introducimos el tipo de componente o el nombre del script sin comillas.

Nótese que no existe la función RemoveComponent o similar. Para destruir un componente al vuelo, usad
Object.Destroy

FUNCIONES DE CLASE:

CreatePrimitive:

static function CreatePrimitive (type : PrimitiveType) : GameObject


Crea un gameobject con una malla de tipo primitivo y el apropiado collider.

Los tipos primitivos son sphere, capsule, cylinder, cube y plane.

Veamos algún ejemplo (recordemos que es una función de clase, no vinculada a un objeto concreto)

Eliminemos antes que nada los dos scripts vinculados al cubo.

Y luego editamos miPrimerScript:


function Start() {
var miCilindro : GameObject = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
miCilindro.transform.position = Vector3(0,0,-2);
}

Salvamos y arrastramos a PortaScripts. Vemos que hemos creado en ejecución un tipo primitivo (un cilindro en este
caso) de entre los que la enum PrimitiveType nos permite), con su malla y su collider. Este cilindro lo almacenamos en
una variable de tipo Gameobject que a partir de ahí podemos tratar como un gameobject más, cambiándole, por
ejemplo, de ubicación.

FindWithTag:

static function FindWithTag (tag : String) : GameObject

Devuelve un gameobject con el tag que le pasamos a la función como string en su único parámetro. Si no existe
ninguno. Devuelve null.

Dado que es una función de clase y no va vinculada a ningún objeto, ha de quedar claro que el gameobject con el tag
que buscamos no tiene por qué hacer referencia a ningún gameobject vinculado al script, sino a cualquier gameobject
del juego que esté activo.

El tag, eso sí, previamente debe estar declarado en el tag manager del inspector.

FindGameObjectWithTag:

static function FindGameObjectsWithTag (tag : String) : GameObject[]

Esta función de clase devuelve una lista de gameobjects activos que tengan el tag requerido.

Find:

static function Find (name : String) : GameObject

Busca un gameobject por nombre y lo devuelve. Aquí no buscamos por tag, sino por nombre. Hemos de reiterar que es
una función de clase, no vinculada a un determinado gameobject, o sea, podemos buscar con esta función cualquier
gameobject activo del juego.

Se puede usar el slash (/) para buscar a través de una jerarquía.

Por motivos de rendimiento es aconsejable no usar esta función cada frame. Es mejor o, una vez hallado el gameobject,
almacenarlo en una variable, o bien usar la función gameobject.findwithtag. Por poner un ejemplo sencillo, reeditemos
una vez más miPrimerScript:
var buscaLaEsfera : GameObject;
buscaLaEsfera = GameObject.Find("Esfera");
Debug.Log(buscaLaEsfera.name.ToString());
buscaLaEsfera.renderer.material.color = Color.green;

Tal como indicábamos en la definición, buscamos con Find un gameobject de nombre Esfera, lo almacenamos en una
variable de tipo gameobject, y ya a partir de ahí podemos, por ejemplo, imprimir su nombre o cambiarle el color.

77. CLASE SHADER

Abordamos esta clase pequeña y poco importante más por seguir un orden en la jerarquía de clases que por otra cosa.
No obstante, aunque será raro que nos topemos a menudo con la misma, no está de más tener una idea básica de su
funcionalidad.

Creo que ya indicamos en otro lugar que un shader es en 3d el algoritmo que especifica cómo una superficie
reaccionará ante la luz.

Muchos de los renders avanzados son controlados vía la clase Material. La clase Shader es más usada para comprobar
si un shader puede correr en el hardware del usuario (propiedad isSupported) y para encontrar shaders por el nombre
(método Find).

VARIABLES:

isSupported:

var isSupported : boolean

Bool de sólo lectura que indica si un determinado shader puede correr en la tarjeta gráfica del usuario. Se usa a veces
cuando se implementan efectos especiales. Por ejemplo, image effects en Unity Pro automáticamente se deshabilitan si
el shader no es soportado.

FUNCIONES DE CLASE:
Find:

static function Find (name : String) : Shader

Encuentra un shader con el nombre dado. Name es el nombre que vemos en el popup del shader de cualquier material.
Son nombres comunes: "Diffuse", "Bumped Diffuse", "VertexLit", Transparent/Diffuse" etc.

78. CLASE PHYSICMATERIAL

Esta clase describe cómo manejar los objetos que chocan (fricción, capacidad de rebotar, etc). Es posible que
recordemos cuando estudiábamos la clase Collider, que ésta tenía dos variables/atributos -material y sharedMaterial- de
tipo PhysicMaterial. Por norma general para crear/modificar las capacidades de fricción y rebote de un gameobject,
pues, lo haremos a través de la propiedad collider.material de dicho gameobject.

VARIABLES:

dynamicFriction:

var dynamicFriction : float

La fricción usada cuando ya hay movimiento. Este valor ha de estar entre 0 y 1, siendo 0 la fricción del hielo y 1 la del
caucho.

Probemos un ejemplo. Para ello empecemos por eliminar el script vinculado a PortaScripts. Luego editamos
miPrimerScript, de esta guisa:
collider.material.dynamicFriction = 0;
function FixedUpdate() {
rigidbody.AddForce(0,0,-9);
}

Salvamos y le asignamos el script al cubo. Como vemos, le hemos dado al material la mínima fricción. Le damos al play
y observamos que la fuerza arrastra el cubo sin problemas. Pero ahora probad a darle a dynamicFriction un valor de
0.4.

staticFriction:

var staticFriction : float

La fricción usada cuando un objeto está reposando en una superficie. Usualmente un valor de 0 a 1.

Siguiendo con nuestro ejemplo, podemos teclear:


collider.material.staticFriction = 0;
collider.material.dynamicFriction = 0.2;
function FixedUpdate() {
rigidbody.AddForce(0,0,-9);
}

Aquí nuestro cubo tendría una fricción en reposo nula y una en movimiento baja. Podemos tratar de invertir las tornas
entre ambos valores e ir experimentando.

bounciness:

var bounciness : float

Qué capacidad de rebote tiene la superficie. 0 es no rebote. Un valor de 1 implica rebote sin pérdida de energía.

Para verlo con un ejemplo, eliminamos el script vinculado a nuestro cubo, primero, y luego colocamos la esfera en la
posición (2,5,0). Editamos entonces miPrimerScript:
collider.material.bounciness = 0.7;

Lo vinculamos a la esfera. Observamos que ésta rebota un poco, sobre todo si comparamos el comportamiento de la
esfera si establecemos esta propiedad en cero. No obstante, nos puede sorprender que a un valor tan alto como 0.7 la
capacidad de rebote de nuestra esfera no sea superior. Dicha sorpresa se acrecienta si le asignamos el valor máximo
(1) a bounciness. Lo que sucede es que Unity establece una media entre los valores de rebote de las dos superficies en
contacto. Probad a arrastrar miPrimerScript también al Suelo y veréis la diferencia.

frictionDirection2:

var frictionDirection2 : Vector3

Se refiere a la dirección de anisotropía. La fricción anisotrópica está habilitada salvo que su vector esté a cero.

La anisotropía es aquella cualidad (forma, tamaño, temperatura, etc) que cambia en un objeto cuando se mueve.

DynamicFriction2 y staticFriction2, que estudiaremos a continuación, serán aplicadas a lo lardo de frictionDirection2. La


dirección de anisotropía es relativa al sistema de coordenadas local del collider.

Utilizaremos un pequeño ejemplo para entender cómo funciona esto. Previamente devolvemos a la esfera al suelo
(position.y=0) y luego eliminamos el script miPrimerScript que tenemos vinculado al suelo y la esfera. Editamos después
de nuevo miPrimerScript:
collider.material.dynamicFriction = 1;
collider.material.frictionDirection2 = Vector3.forward;
collider.material.dynamicFriction2 = 0;
function FixedUpdate() {
rigidbody.AddForce(5,0,0);
}

Arrastramos tras salvar el script al cubo.

Bien. Vamos a tratar de explicar qué tenemos aquí. En primer lugar establecemos la fricción dinámica del cubo al
máximo, esto es, 1. De esta manera debería resultar difícil poderlo arrastrar. Pero, a continuación, le damos a
frictionDirection2 un valor distinto a 0,0,0, esto es, lo activamos. Lo que le estamos diciendo aqui a Unity es que a lo
largo del eje Z (forward) la fricción del cubo será distinta que cuando se mueva a lo largo de los otros dos ejes (esto es
la anisotropía). Concretamente, en la tercera declaración le indicamos a Unity que la fricción dinámica en ese eje Z
dynamicFriction2 será nula (0).

En consecuencia, el resultado de este script debería hacer que el cubo se resistiera a moverse en los ejes X e Y pero en
cambio no tuviera problemas en hacerlo en el eje Z. Para abrir boca, le aplicamos al script acto seguido una fuerza en el
eje X. Pulsemos el play y comprobemos que el cubo si se mueve.

Pero ahora cambiemos los parámetros de AddForce a (5,0-5), esto es, aplicándole la misma fuerza en el eje Z (que no
debería tener resistencia) que al X. Si funciona como debiera, el cubo empezará a moverse hacia delante, pero seguirá
negándose a hacerlo hacia la derecha, aunque la presión en un eje y otro es la misma. Probad.

dynamicFriction2:

var dynamicFriction2 : float

Si la fricción anisotrópica está habilitada (frictionDirection2 no vale cero), dynamicFriction2 será aplicada a lo largo del
eje/s de frictionDirection2 que no valga cero.

staticFriction2:

var staticFriction2 : float

Si la fricción anisotrópica está habilitada, staticFriction2 será aplicada a lo largo de frictionDirection2.

frictionCombine:

var frictionCombine : PhysicMaterialCombine

Determina cómo se combina la fricción. Tal como explicaba hace unas líneas en relación con el rebote, asimismo las
propiedades de fricción son dependientes de la combinación de los dos materiales en contacto.

PhysicMaterialCombine (el tipo de este atributo) es una enumeración con las diferentes posibilidades de combinación de
fricciones y capacidades de rebote.
Average: La media de la fricción o rebote de los materiales de los dos colliders.
Multiply: Multiplica la fricción o rebote de los materiales de los dos colliders.
Minimum: Usa el valor más bajo de fricción o rebote de los dos colliders.
Maximum: El valor más alto de los dos.

Así, la sintaxis para establecer la fricción entre dos colliders en el valor más alto de los dos sería así (a modo indicativo,
nada más):

collider.material.frictionCombine = PhysicMaterialCombine.Maximum;

bounceCombine:

var bounceCombine : PhysicMaterialCombine

Determina cómo la capacidad de rebote es combinada. Al igual que la anterior, recurriremos al enum
PysicMaterialCombine.

FUNCIONES:

PhysicMaterial:

static function PhysicMaterial () : PhysicMaterial

La función constructora crea un nuevo material. Es normalmente más fácil, sin embargo, usar meramente un
collider.material y modificar el material vinculado directamente.

static function PhysicMaterial (name : String) : PhysicMaterial

Crea un nuevo material con un nombre dado.

79. CLASE COMPONENT

Si realizamos un vistazo más o menos fugaz a las variables y funciones contenidas en esta clase, más de uno llegará a
la conclusión de que guardan mucha similitud con las de la clase GameObject.

Tras compararlas más detenidamente, podemos hacer la siguiente afirmación: la clase Component no tiene ningún
atributo ni propiedad que no tenga la clase GameObject.

En aras de ser exactos, podemos decir que la clase Component es igual que la clase GameObject, sin las propiedades
isStatic, layer y active y sin los métodos SetActiveRecursively, AddComponent, y obviamente su constructor.

Esto es así porque Component no está pensada para ser usada directamente (para ello ya tenemos Gameobject) sino
para que sus métodos y atributos sean heredados por buena parte de clases (de rigidbody a camera y de joint a light
pasando por transform)

80. CLASE GUIELEMENT


Volvemos a bajar de nuevo en nuestra jerarquía de clases para buscar otra heredera de Behaviour. GUIElement es a su
vez una clase base, que establece la funcionalidad mínima para todos los elementos de la GUI (Interfaz Gráfica de
Usuario), entendiendo "elementos" como las imágenes y cadenas de texto mostradas en la GUI.

FUNCIONES:

HitTest:

function HitTest (screenPosition : Vector3, camera : Camera = null) : boolean

Es un punto en la pantalla dentro del elemento. Devuelve true si la posición pasada como parámetro screenPosition
está contenida en este GUIElement. La screenPosition es especificada en coordenadas de pantalla, a semejanza de los
valores retornados por Input.mousePosition property. Si no se proporciona a la función una cámara en concreto, se
asumirá por esta una cámara cubriendo la ventana del juego entera.

Hemos de tener en cuenta que si la posición está dentro del elemento, esta función devolverá true incluso si el
gameobject pertenece al layer Ignore Raycast.

GetScreenRect:

function GetScreenRect (camera : Camera = null) : Rect

Devuelve lo bordes del rectángulo del GUIElement en coordenadas de pantalla.

Si no se proporciona a la función una cámara en concreto, se asumirá por esta una cámara cubriendo la ventana del
juego entera.
81. CLASE GUITEXT (I)

La clase GUIText es una de las dos que derivan de GUIElement (vista en la lección anterior). En concreto, la clase
GUIText es la que se ocupa de los string de texto mostrados en un GUI (de las imágenes se ocupa GUITexture, que
veremos luego).

VARIABLES:

text:

var text : String

Variable que contiene el texto que se muestra en el GUI.

Empecemos con la habitual tanda de ejemplos. Para empezar, eliminamos el script que tenemos en el cubo. Luego
vamos a crear un gameobject de tipo GUIText, para lo que nos vamos al menú=>Gameobject=>Create other=>Gui Text.
Renombramos a nuestro nuevo gameobject como "miTexto".

Observaremos que en la ventana Game nos aparece un texto ("gui text") por defecto.

Editamos miPrimerScript:
guiText.text = "Bienvenidos a unityscripts";

Arrastramos tras salvar el script a miTexto. Play. Observamos que el texto que hemos introducido se nos muestra en la
ventana Game, tal como se ve en esta captura:

material:

var material : Material

El material usado para renderizar el texto. Esta variable nos permite acceder a él para modificarlo o crear uno. Si le
damos un valor null se mostrará la fuente por defecto.

Podemos por ejemplo completar un poco más nuestro anterior script:


guiText.text = "Bienvenidos a unityscripts";
guiText.material.color = Color.magenta;

Y habremos alterado el color de nuestras letras de bienvenida.

pixelOffset:

var pixelOffset : Vector2

El desplazamiento en píxeles del texto desde su posición inicial (marcada por su transform) en base a los valores
contenidos en un Vector2.
Un ejemplo sencillo que no merece más comentario:
guiText.text = "Bienvenidos a unityscripts";
guiText.material.color = Color.magenta;
guiText.pixelOffset = Vector2 (-150, 100);

82. CLASE GUITEXT (y II)

font:

var font : Font

La fuente usada para el texto. Podemos asignar una de entre las que tengamos disponibles.

alignment:

var alignment : TextAlignment

La alineación del texto. TextAlignment es una estructura con los valores Left, Center y Right.

Para probar esto hemos de añadir más texto a nuestro ejemplo del capítulo con algunos saltos de linea:
guiText.text = "Bienvenidos a unityscripts. \nAñadimos una \nsegunda y tercera linea";
guiText.material.color = Color.magenta;
guiText.alignment = TextAlignment.Center;

Podemos comprobar que el texto se alinea centrado.

anchor:

var anchor : TextAnchor

El ancla del texto. TextAnchor es una enumeración que permite indicar dónde se colocará el ancla o fijación del texto.
Tiene estas posibilidades:
UpperLeft: El texto se fija en la esquina superior izquierda. Para entendernos, la
parte superior de la B de nuestro "Bienvenidos" del ejemplo estará
ubicada en el punto marcado por las coordenadas de posición del
transform del GUIText.
UpperCenter: El texto se fija en el lado central superior, esto es, la mitad de
nuestra frase, medida horizontalmente, en su lado superior,
coincidirá con la ubicación marcada por el transform.
UpperRight: El texto se fija en la esquina superior derecha.
MiddleLeft: El texto se fija en el lado izquierdo, centrado verticalmente.
MiddleCenter:El texto se centra tanto vertical como horizontalmente respecto de su
transform.
MiddleRight: El texto se ancla en el lado derecho, centrado verticalmente.
LowerLeft: El texto se fija en la esquina inferior izquierda.
LowerCenter: El texto se ancla en la parte inferior, centrada horizontalmente.
LowerRight: El texto se fija en la esquina inferior derecha.

Vamos a centrar nuestra frase respecto la posición del transform del GUIText. Añadimos al script:
guiText.text = "Bienvenidos a unityscripts. \nAñadimos una \nsegunda y tercera linea";
guiText.material.color = Color.magenta;
guiText.alignment = TextAlignment.Center;
guiText.anchor = TextAnchor.MiddleCenter;

Ahi lo tenemos.

lineSpacing:

var lineSpacing : float

El multiplicador del espacio de interlineado. Esta cantidad será multiplicada por el espacio de línea definido en la
fuente.

tabSize:

var tabSize : float

El multiplicador de la anchura del tab. Esta cantidad se multiplicará con la anchura de tab definida en la fuente.

fontSize:

var fontSize : int

El tamaño de fuente a usar (para fuentes dinámicas). Si lo establecemos en una cantidad distinta de cero, el tamaño de
fuente especificado en la fuente importada es sobreescrito con un tamaño personalizado. Esto sólo es soportado para
fuentes que usen fuentes dinámicas de renderizado. Otras fuentes siempre usarán el tamaño de fuente por defecto.

fontStyle:

var fontStyle : FontStyle

Lo mismo que la anterior para el estilo de Fuentes dinámicas.

83. CLASE GUITEXTURE


Clase "hermana" de GUIText, GUITexture se encarga de manejar las imágenes que compondrán nuestra GUI en 2d.
Para una mejor comprensión, no tenéis más que hacer lo siguiente: eliminad el gameobject "miTexto" y acto seguido ir al
menú=>gameobject=>create other=>GUI texture. Aparecerá por defecto el icono de Unity en nuestra escena. Llamemos
a nuestro nuevo gameobject "logoUnity".

VARIABLES:

color:

var color : Color

El color de la textura de la GUI.

Un ejemplo muy sencillo. Editamos miPrimerScipt:


guiTexture.color = Color.blue;

Lo arrastramos a logoUnity, play, y nuestra imagen/textura pasa a ser de color azul.

texture:

var texture : Texture

La textura usada para dibujar.

Es posible que conservemos todavía en Proyecto la imagen que llamamos "multicolor" que usamos para que diera
vueltas alrededor de la esfera. Si la tenéis ahí, perfecto, y si no arrastrad hasta la carpeta assets donde tengáis
guardado vuestro proyecto cualquier imagen. Luego editamos miPrimerScript como sigue:
var unaTextura : Texture;
guiTexture.texture = unaTextura;

Salvamos y arrastramos la textura a la variable expuesta. Al darle al play observamos que la misma sustituye al logo de
Unity.
pixelInset:

var pixelInset : Rect

Inserción de pixels usada para ajustar píxeles para tamaño y posión. Pueder poner el transform.localScale a
Vector3.zero para hacer que la GUI texture siempre tenga el mismo tamaño de píxeles. Reeditemos una vez más
nuestro script:
transform.position = Vector3.zero;
transform.localScale = Vector3.zero;
guiTexture.pixelInset = Rect (100, 25, 180, 180);

Antes de darle al play expliquemos lo que hemos hecho. Primero colocamos el transform de nuestra GUITexture en el
centro de la escena. Acto seguido colocamos la escala del transform a cero para que tenga el tamaño que
posteriormente le indiquemos, sin ningún tipo de variación. Acto seguido encuadramos la textura dentro de un
rectángulo situado a 100 píxeles desde la izquierda y 25 desde arriba, con una anchura y altura de 180 píxeles. Dadle al
play.

Bye.

84. CLASE GUI (I)

Bueno. Hasta ahora nos hemos centrado en estudiar las clases que conformaban el árbol de herencia que ocupa buena
parte de la API de Unity. Hemos ido repasándolas (casi) todas, más o menos en orden. A partir de ahora, en cambio,
vamos a recorrer clases que no están vinculadas por relación de herencia alguna con las ya explicadas (salvo alguna
excepción, que anunciaremos como tal cuando corresponda).

Por tanto la cuestión del orden en su estudio obedecerá a criterios un poco más liviamos, como su mayor o menor
importancia o su vinculación funcional con la clase que se haya estudiado con anterioridad.

En base a ese último criterio, y dado que las tres últimas clases estaban relacionadas con la inferfaz gráfica de usuario,
vamos a dedicar las siguientes lecciones a darle unas vueltas a diferentes clases que de una manera u otra están
vinculadas con la GUI.

Y, como no podía ser de otra forma, empezamos por la clase GUI, que representa la interfaz de Unity:

VARIABLES DE CLASE:

skin:

static var skin : GUISkin

La skin (que podemos traducir por "piel" o "aspecto") en uso. Cambiando esta variable podemos cambiar el look de
nuestra GUI. Si su valor es null, se muestra la skin que está por defecto.

Es una instancia de la clase GUISkin, que estudiaremos a continuación de ésta.


color:

static var color : Color

Color del tintero global de la GUI. Afectará tanto a la parte trasera de los elementos (background) como a los colores del
texto que escribamos sobre cualquier superficie.

Vamos con el primer ejemplo. Empezamos por eliminar el objeto logoUnity. A continuación editamos miPrimerScript:
function OnGUI() {
GUI.color = Color.yellow;
GUI.Label (Rect (10, 10, 200, 20), "Esto es una etiqueta");
GUI.Box(Rect(10, 50, 100, 50), "Una caja");
GUI.Button(Rect(10,110,90,50), "Un botón");
}

Salvamos y la arrastramos a PortaScripts. Si pulsamos play observaremos algo como esto:

Aquí hemos de explicar varias cosas. Empezando por el final, comprobamos en la imagen que el texto de la GUI, sobre
las diferentes superficies, es del color que le hemos indicado a la variable color. Asimismo, los bordes de elementos
como el botón adquieren en su parte posterior (background) ese color amarillo.

Esa declaración y las anteriores están contenidas dentro de una función que vimos un poco de puntillas cuando
estudiamos la clase MonoBehaviour: la función OnGUI renderiza y maneja eventos GUI. Dicho de otra manera, al llamar
a esta función activamos un evento GUI, que en este caso asigna un color a la letra y luego crea una etiqueta, una caja
y un botón.

backgroundColor:

static var backgroundColor : Color

Color del tintero para todas las partes traseras (background) de los elementos renderizados para la GUI.

Dicho de otra manera, esta variable de clase hace parte del trabajo que efectuaba color, ya que colorea el background
pero no el texto.
Reeditamos miPrimerScript como sigue:
function OnGUI() {
GUI.backgroundColor = Color.red;
GUI.Button(Rect(10,10,70,30), "Mi botón");
}

Al darle al play podemos observar que los bordes del botón que hemos creado son de color rojo, color que se acrecienta
cuando colocamos el ratón encima. En cambio, el color del texto sigue siendo blanco (el color por defecto)

contentColor:

static var contentColor : Color

Color de tinta para todo el texto renderizado en la GUI. Esta función es la complementaria de la anterior con relación a
color, ya que no afecta al color del background, sino al del texto.

Por ejemplo:
function OnGUI() {
GUI.contentColor = Color.yellow;
GUI.backgroundColor = Color.red;
GUI.Button(Rect(10,10,70,30), "Mi botón");
}

Vemos aquí claramente las diferencias entre ContentColor (amarillo) y backgroundColor (rojo).

85. CLASE GUI ( II)

changed:

static var changed : boolean

Devuelve true si algún control cambia el valor de los datos de entrada de la GUI.

Podemos aprovechar el ejemplo que aparece en el manual de referencia para ilustrarnos. Editamos miPrimerScript
como sigue:
var miTexto : String = "Cambiame";
function OnGUI () {
miTexto = GUI.TextField (Rect (10, 10, 200, 20), miTexto, 25);
if (GUI.changed)
Debug.Log("El campo de texto se modificó");
}

El contenido del script es bastante intuitivo: creamos un campo de texto editable con la función TextField, que en breve
estudiaremos, y, si procedemos a cambiar su contenido inicial se nos imprimirá en pantalla un texto avisándonos de
dicha modificación.

enabled:

static var enabled : boolean

Habilita/deshabilita la GUI.

Si establecemos esta variable en false se deshabilitan todas las interacciones de la GUI. Todos los controles se
dibujarán semitransparentes, y no responerán a las entradas del usuario.

tooltip:

static var tooltip : String

Un tooltip es ese pequeña nota emergente que aparece a veces con determinada información cuando colocamos un
ratón sobre un control, o dicho control tiene el foco del teclado.

Vamos con el pertinente ejemplo. Abrimos nuestro script y:

function OnGUI () {
GUI.Button (Rect (10,10,100,20), GUIContent ("Pulsame", "Este es el tooltip"));
GUI.Label (Rect (10,40,100,40), GUI.tooltip);
}

El script funciona de la siguiente manera: Como viene siendo habitual, empezamos creando un evento GUI con la
función OnGUI. Acto seguido creamos un botón con una determinada ubicación y dimensiones, y le pasamos como
segundo parámetro la función constructora de la clase GUIContent (de cercano estudio), que a su vez admite como
parámetros el texto del botón, una imagen (en este caso no) y en su caso el texto del tooltip que se deba activar al
pasarle el ratón por encima.

Acto seguido hemos de crear propiamente la etiqueta del tooltip, indicando su ubicación y dimensiones.

Al darle al play y colocar el ratón sobre el botón, automáticamente nos aparecerá un tooltip con el texto indicado, que
desaparecerá al retirar el ratón de dicho control.

depth:

static var depth : int

El orden de profundidad que tendrá cada actividad GUI en ejecución. Quiere esto decir que cuando tengamos varios
scripts ejecutándose simultáneamente, los elementos GUI que tengan valores de profundidad más bajos aparecerán en
la pantalla encima de los que lo tengan más altos

86. CLASE GUI (III)


FUNCIONES DE CLASE:

label:

static function Label (position : Rect, text : String) : void


static function Label (position : Rect, image : Texture) : void
static function Label (position : Rect, content : GUIContent) : void
static function Label (position : Rect, text : String, style : GUIStyle) : void
static function Label (position : Rect, image : Texture, style : GUIStyle) : void
static function Label (position : Rect, content : GUIContent, style : GUIStyle) : void

Crea una etiqueta (label) de texto o de imagen en la pantalla. Como podemos observar, esta función tiene varios
prototipos, permitiéndonos pasarle distintos parámetros en base a la necesidad y datos que tengamos en cada
momento. Dichos parámetros serían:
position: Rectangulo en la pantalla a usar para la etiqueta.
text: Texto a mostrar en la etiqueta.
image: Textura/imagen a mostrar en la etiqueta.
content: Texto, imagen y tooltip para esta etiqueta.
style: El estilo a usar. Si no se indica, se aplicará el estilo para
etiquetas que tenga el GUISking que se esté usando.

En base al significado de estos parámetros, podemos fácilmente deducir la diferencia entre los distintos prototipos de
esta función.

Las etiquetas o labels en sí no implican ningún tipo de interacción con el usuario, no captan clicks del ratón y son
siempre renderizadas en un estilo normal (no por ejemplo como los botones, que aparte del estilo normal tienen otro
para cuando se pasa el ratón por encima, otro cuando se presionan, o cuando se activan, etc).

Pongamos el más sencillo de los ejemplos: mostremos en pantalla el famoso hello world.
function OnGUI () {
GUI.Label (Rect (10, 10, 100, 20), "Hello World");
}

Como vemos, hemos optado por el primer prototipo de la función de los que mostramos al inicio. Meramente el
rectángulo con la posición (10,10) y tamaño (100,20) del rectángulo donde ubicaremos la etiqueta, y como segundo
parámetro un string con el texto.

Si en lugar de mostrar un texto quisiéramos hacer lo propio con una imagen, modificaríamos la función como sigue:
var unaTextura : Texture2D;
function OnGUI () {
GUI.Label (Rect (10, 40, unaTextura.width, unaTextura.height), unaTextura);
}

Salvamos y arrastramos la textura que teníamos en la carpeta assets de ejemplos anteriores a la variable expuesta de
este script que tenemos vinculado a PortaScripts. En este prototipo de nuevo pasamos como parámetro primero un
rectángulo con la posición del rectángulo (10,40) y, en lugar de indicar directamente las dimensiones que ha de tener
dicho rectángulo, aprovechamos la anchura y altura originales de la imagen arrastrada para no recortarla (aunque por
supuesto podemos sentirnos libres de establecer unas dimensiones fijas). El segundo parámetro es donde difiere este
prototipo del anterior, ya que en lugar de suministrar un string pasamos una textura.

DrawTexture:
static function DrawTexture (position : Rect, image : Texture, scaleMode : ScaleMode = ScaleMode.StretchToFill,
alphaBlend : boolean = true, imageAspect : float = 0) : void

Dibuja una textura dentro de un rectángulo. Tiene los siguientes parámetros:

position: Rectángulo en la pantalla para dibujar la textura dentro.


image: Textura a mostrar.
scaleMode: Cómo escalar la imagen cuando la proporción hace que no encaje bien
dentro del rectángulo.
alphaBlend: Si el canal alfa es mezclado en la imagen (por defecto)
imageAspect: Proporción a usar para la imagen fuente. Si vale 0 (por defecto), es
usada la proporción de la imagen. Pasad un w/h para la deseada
proporción, lo cual permite cambiar la proporción de la imagen
original sin cambier la anchura y altura de píxeles.

El parámetro scaleMode es a su vez una enumeración que acepta los siguientes valores:
StretchToFill: Estira la textura para rellenar el rectangulo entero.
ScaleAndCrop: Escala la textura, manteniendo la proporción, hasta cubrir
completamente el rectángulo. Si la textura se dibuja en un
rectángulo con una proporción diferente, la imagen se recorta.
ScaleToFit: Escala la textura, manteniendo la proporción, hasta encajar
completamente dentro del rectángulo.

Vamos ahora a dibujar una textura en la esquina izquierda de la pantalla, textura que se dibujará en una ventana de 60 x
60 píxeles. A la textura original le daremos una proporción de 10 x 1 y la haremos encajar luego en el rectángulo anterior
con el Scalemode.ScaleToFit, de tal manera que la textura se escalará hasta encajar horizontalmente con el rectángulo,
manteniendo la proporción de 10/1.
function OnGUI() {
}
GUI.DrawTexture(Rect(10,10,60,60), aTexture, ScaleMode.ScaleToFit, true, 10.0f);
}

Box:

static function Box (position : Rect, text : String) : void


static function Box (position : Rect, image : Texture) : void
static function Box (position : Rect, content : GUIContent) : void
static function Box (position : Rect, text : String, style : GUIStyle) : void
static function Box (position : Rect, image : Texture, style : GUIStyle) : void
static function Box (position : Rect, content : GUIContent, style : GUIStyle) : void

Como su nombre indica, crea un cuadro o caja. Sus diferentes prototipos cuentan con estos parámetros:
position: Rectángulo en la pantalla a usar para la caja.
text: Texto a mostrar en la caja.
image: Textura a mostrar en la caja.
content: Texto, imagen y tooltip para la caja.
style: El estilo a usar. Si no se indica expresamente, el estilo de la caja
será el del GUISkin en uso.

Pongamos un ejemplo sencillo:


function OnGUI() {
GUI.Box(Rect(10,20,100,40),"Hola, mundo");
}

87. CLASE GUI (IV)


Button:

static function Button (position : Rect, text : String) : boolean


static function Button (position : Rect, image : Texture) : boolean
static function Button (position : Rect, content : GUIContent) : boolean
static function Button (position : Rect, text : String, style : GUIStyle) : boolean
static function Button (position : Rect, image : Texture, style : GUIStyle) : boolean
static function Button (position : Rect, content : GUIContent, style : GUIStyle) : boolean

Función que crea un botón que, al ser pulsado por un usuario, dispara algún tipo de evento.

Una cosa que puede llamar la atención inicialmente, y que tiene mucho que ver con la manera un poco atípica de usar
esta función, es que retorna un booleano que es establecido en true cuando el usuario hace click sobre el botón. De
esta suerte, la función devolverá false hasta que se pulse el botón, lo que implica en la práctica que esta función se
suele usar tras una declaración if, como enseguida veremos.

Antes detengámonos un momento en los parámetros, que ya nos deberían sonar:


position: Rectangulo en la pantalla a usar para el botón.
text: Texto a mostrar en el botón.
image: Textura/imagen a mostrar en el botón.
content: Texto, imagen y tooltip para este botón.
style: El estilo a usar. Si no se indica, se aplicará el estilo para
botones que tenga el GUISking que se esté usando.

Y ahora es el momento de entender con un ejemplo lo que os comentaba antes sobre la peculiar manera de usar la
función button:
var unaTextura : Texture;
function OnGUI() {
if (!unaTextura) {
Debug.LogError("Asigne por favor una textura en el inspector");
return;
}
if (GUI.Button(Rect(10,10,50,50),unaTextura))
Debug.Log("Has hecho click en el botón que tiene una imagen");
if (GUI.Button(Rect(10,70,50,30),"Click"))
Debug.Log("Has hecho click en el botón con texto");
}

Vamos por partes. Salvamos el script y, sin arrastrar ninguna textura a la variable expuesta que nos debería aparecer en
el inspector al tener PortaScripts seleccionado, pulsamos play. Nos aparecerá un mensaje de error avisándonos de que
debemos arrastrar dicha textura. Es esta una variande de Debug.Log llamada Debug.LogError, que hace que el
mensaje aparezca en rojo.

Detenemos el reproductor, arrastramos la textura y volvemos a pulsar play. Observaremos que nos aparecen dos
botones, uno con la textura arrastrada por nosotros, otros con el texto indicado, y que al pulsarlos aparecen en pantalla
sendos textos.

Lo que personalmente me parece más interesante de todo esto es la manera de utilizar la función:
if (GUI.Button(Rect(10,70,50,30),"Click"))
Pensemos que con esta declaración estamos haciendo dos cosas: primero, al pasarla como condición de if nos
aseguramos de que una vez el user clicke el botón y por ende la función Button devuelva true, se ejecutará las
declaraciones que se vean afectadas por ese if. Pero, además, previo a devolver true o false, Unity ejecuta la función, y
por consiguiente se renderiza el botón en la pantalla.

RepeatButton:

static function RepeatButton (position : Rect, text : String) : boolean


static function RepeatButton (position : Rect, image : Texture) : boolean
static function RepeatButton (position : Rect, content : GUIContent) : boolean
static function RepeatButton (position : Rect, text : String, style : GUIStyle) : boolean
static function RepeatButton (position : Rect, image : Texture, style : GUIStyle) : boolean
static function RepeatButton (position : Rect, content : GUIContent, style : GUIStyle) : boolean

Función que crea un botón que está activo mientras el usuario lo presiona.

TextField:

static function TextField (position : Rect, text : String) : String


static function TextField (position : Rect, text : String, maxLength : int) : String
static function TextField (position : Rect, text : String, style : GUIStyle) : String
static function TextField (position : Rect, text : String, maxLength : int, style : GUIStyle) : String

Crea un campo de texto de una línea donde el usuario puede editar un string. Devuelve un string con el texto editado.

Tiene estos parámetros:


position: Rectángulo en la pantalla a usar para el campo de texto.
text: Texto a editar. El valor de retorno debe ser reasignado de vuelta al
string tal como se enseña en el próximo ejemplo.
maxLength: La longitud máxima del string. Si no se indica, el usuario puede
escribir sin ningún tipo de límite.
style: El estilo a usar. Si no se indica, se aplicará el estilo para
textField que tenga el GUISkin en uso.

Esta función es importante porque permite una mayor interactividad con el usuario que meramente pulsar determinamos
botones. Por ello es importante que el texto que dicho usuario introduce se almacene debidamente. Tal como indica el
manual de referencia al hablar del parámetro text, la solución ideal es inicializar una variable string con el valor que le
damos por defecto, y reutilizar dicha variable para posteriormente contener el string ya modificado y devuelto por la
función. En definitiva:
var mensaje : String = "Escribe algo";
function OnGUI () {
mensaje = GUI.TextField (Rect (10, 10, 200, 20), mensaje, 25);
Debug.Log(mensaje);
}

Como veis, la idea es que "mensaje" sirva tanto para contener el string inicial como el modificado. Le he añadido a
continuación un Debug.Log para que comprobéis en tiempo real cómo va cambiando el contenido de la variable.

PasswordField:

static function PasswordField (position : Rect, password : String, maskChar : char) : String
static function PasswordField (position : Rect, password : String, maskChar : char, maxLength : int) : String
static function PasswordField (position : Rect, password : String, maskChar : char, style : GUIStyle) : String
static function PasswordField (position : Rect, password : String, maskChar : char, maxLength : int, style : GUIStyle) :
String

Crea un campo de texto donde el usuario puede introducir una contraseña. Devuelve un string con la contraseña
editada.

Cuenta con los siguientes parámetros:


position: Rectángulo en la pantalla a usar para el campo de texto.
password: Contraseña a editar. El valor de retorno de esta función debe ser
reasignado al string que contenía el valor original.
maskChar: El carácter con el que queremos enmascarar la contraseña.
maxLength: La longitud máxima del string. Si no se indica, el usuario no tendrá
límite para escribir.
style: El estilo a usar. Si no se indica, se usará el estilo para textfield
que tenga el GUISkin que se esté usando.

Podemos apreciar que en definitiva esta función es como la anterior, con la salvedad de que en la presente le añadimos
un carácter (tradicionalmente un asterisco) que queremos que aparezca en pantalla cuando el usuario teclee su
contraseña.
var miPin : String = "";
function OnGUI () {
miPin = GUI.PasswordField (Rect (10, 10, 200, 20), miPin, "*"[0], 25);
}

88. CLASE GUI (V)

TextArea:

static function TextArea (position : Rect, text : String) : String


static function TextArea (position : Rect, text : String, maxLength : int) : String
static function TextArea (position : Rect, text : String, style : GUIStyle) : String
static function TextArea (position : Rect, text : String, maxLength : int, style : GUIStyle) : String

Crea un área de texto de varias líneas donde el usuario puede editar un string. Devuelve el string editado.

SetNextControlName:

static function SetNextControlName (name : String) : void

Función que establece el nombre del siguiente control. Esto hace que el siguiente control sea registrado con un nombre
dado.

GetNameOfFocusedControl:

static function GetNameOfFocusedControl () : String


Obtiene el nombre del control que tiene el foco. El nombre de los controles es asignado usando SetNextControlName.
Cuando un control tiene el foco, esta función devuelve su nombre. GetNameOfFocusedControl funciona especialmente
bien cuando tratamos con ventanas para loguearse.

FocusControl:

static function FocusControl (name : String) : void

Mueve el foco del teclado a un control nombrado.

Toggle:

static function Toggle (position : Rect, value : boolean, text : String) : boolean
static function Toggle (position : Rect, value : boolean, image : Texture) : boolean
static function Toggle (position : Rect, value : boolean, content : GUIContent) : boolean
static function Toggle (position : Rect, value : boolean, text : String, style : GUIStyle) : boolean
static function Toggle (position : Rect, value : boolean, image : Texture, style : GUIStyle) : boolean
static function Toggle (position : Rect, value : boolean, content : GUIContent, style : GUIStyle) : boolean

Crea un botón de tipo interruptor (on/off). Devuelve el nuevo valor del botón(true/false).

Toolbar:

static function Toolbar (position : Rect, selected : int, texts : string[]) : int
static function Toolbar (position : Rect, selected : int, images : Texture[]) : int
static function Toolbar (position : Rect, selected : int, content : GUIContent[]) : int
static function Toolbar (position : Rect, selected : int, texts : string[], style : GUIStyle) : int
static function Toolbar (position : Rect, selected : int, images : Texture[], style : GUIStyle) : int
static function Toolbar (position : Rect, selected : int, contents : GUIContent[], style : GUIStyle) : int

Función que crea una barra de herramientas. Devuelve -un int- el índice de la toolbar seleccionado.

Tiene estos parámetros:


position: Rectángulo en la pantalla a usar para la barra de herramientas.
selected: El índice del botón seleccionado.
texts: Un array de strings a enseñar en los botones de la barra.
images: Un array de textras para los botones de la barra de herramientas.
contents: Un array de texto, imágenes y tooltips para los botones del toolbar.
style: El estilo a usar. Si no se indica, se usará el estilo para botones que
tenga la GUISkin que se esté usando.

Veámoslo con un ejemplo:


var toolbarIndice : int = 0;
var toolbarStrings : String[] = ["Toolbar1", "Toolbar2", "Toolbar3"];
function OnGUI () {
toolbarIndice = GUI.Toolbar (Rect (25, 25, 250, 30), toolbarIndice, toolbarStrings);
Debug.Log("El índice pulsado es " + toolbarIndice);
}
Observaremos que nos aparece en pantalla al pulsar play una barra con tres botones, estando por defecto activado el
primero, que corresponde con el índice cero que le hemos pasado como parámetro. Le hemos añadido la función
Debug.Log para acreditar que al presionar los distintos botones se le asigna a la variable el índice de cada botón.

89. CLASE GUI (VI)

SelectionGrid:

static function SelectionGrid (position : Rect, selected : int, texts : string[], xCount : int) : int
static function SelectionGrid (position : Rect, selected : int, images : Texture[], xCount : int) : int
static function SelectionGrid (position : Rect, selected : int, content : GUIContent[], xCount : int) : int
static function SelectionGrid (position : Rect, selected : int, texts : string[], xCount : int, style : GUIStyle) : int
static function SelectionGrid (position : Rect, selected : int, images : Texture[], xCount : int, style : GUIStyle) : int
static function SelectionGrid (position : Rect, selected : int, contents : GUIContent[], xCount : int, style : GUIStyle) : int

Hace una cuadrícula (grid) de botones. Devuelve un int con el índice del botón seleccionado.

Permite los siguientes parámetros:


position: Rectándulo de la pandalla a usar para la cuadrícula.
selected: El índice del botón seleccionado de la cuadrícula.
texts: Un array de strings que mostrar en los botones de la cuadrícula.
images: Un array de texturas en los botones de la cuadrícula.
contents Un array de texto, imágenes y tooltips para los botones de la cuadrícula
xCount: Cuántos elementos caben en la dirección horizontal. Los controles serán
escalados para encajar a meno que el estilo elegido para usar sea
fixedWidth.
style: El estilo a usar. Si no se indica, se usa el estilo de botón marcado
por el GUISkin que se esté usando.

Veamos un breve ejemplo:


var selGridInt : int = 0;
var selStrings : String[] = ["Grid 1", "Grid 2", "Grid 3", "Grid 4"];
function OnGUI () {
selGridInt = GUI.SelectionGrid (Rect (25, 25, 100, 30), selGridInt, selStrings, 2);
}

HorizontalSlider:

static function HorizontalSlider (position : Rect, value : float, leftValue : float, rightValue : float) : float
static function HorizontalSlider (position : Rect, value : float, leftValue : float, rightValue : float, slider : GUIStyle, thumb :
GUIStyle) : float

Crea una barra de desplazamiento horizontal que el usuario puede arrastrar para cambiar un valor entre un mínimo y un
máximo. Devuelve un float con el valor que ha sido elegido por el usuario.

Parámetros:
position: Rectángulo en la pantalla a usar para la barra.
value: El valor que muestra la barra. Esto determina la posición del
deslizable móvil.
leftValue: El valor del extremo izquierdo de la barra.
rightValue El valor del extremo derecho de la barra.
slider: El GUIStyle a usar para mostrar el área de arrastre. Si no se
utiliza, se usará el estilo de horizontalSlider que tenga por
defecto el GUISkin que se esté usando.
thumb: El GUIStyle a usar para mostrar el deslizable móvil. Si no se usa,
se usará el estilo de horizontalSliderThumb style que tenga por
defecto el GUISkin que se esté usando.

Y por último el ejemplo:


var valorDelSlider : float = 0.0;
function OnGUI () {
valorDelSlider = GUI.HorizontalSlider (Rect (25, 25, 100, 30), valorDelSlider,
0.0, 10.0);
}

VerticalSlider:

static function VerticalSlider (position : Rect, value : float, topValue : float, bottomValue : float) : float
static function VerticalSlider (position : Rect, value : float, topValue : float, bottomValue : float, slider : GUIStyle, thumb :
GUIStyle) : float

Crea una barra de deslizamiento vertical que el usuario puede arrastrar para cambiar un valor entre un mínimo y un
máximo. Devuelve un float con el valor que ha sido escogido por el usuario.

Tiene los siguientes parámetros:


position: Rectángulo en la pantalla a usar para la barra.
value: El valor que muestra la barra. Esto determina la posición del
deslizable móvil.
topValue: El valor en lo alto de la barra
bottomValue: El valor en lo bajo de la barra
slider: El GUIStyle a usar para mostrar el área de arrastre. Si no se
utiliza, se usará el estilo de verticalSider que tenga por
defecto el GUISkin que se esté usando.
thumb: El GUIStyle a usar para mostrar el deslizable móvil. Si no se usa,
se usará el estilo de verticalSliderThumb que tenga por
defecto el GUISkin que se esté usando.

HorizontalScrollbar:

static function HorizontalScrollbar (position : Rect, value : float, size : float, leftValue : float, rightValue : float) : float
static function HorizontalScrollbar (position : Rect, value : float, size : float, leftValue : float, rightValue : float, style :
GUIStyle) : float
Crea una barra de desplazamiento (scrollbar) horizontal. Un scrollbar es lo que usamos para desplazarnos por un
documento. Por norma general en lugar de scrollbar usaremos scrolliews.

Devuelve un float con el valor modificado. Este puede ser cambiado por el usuario arrastrando el scrollbar o clickando
en las flechas de los extremos.

Un breve ejemplo:
var valorDeBarra : float;
function OnGUI () {
valorDeBarra = GUI.HorizontalScrollbar (Rect (25, 25, 100, 30), valorDeBarra,
1.0, 0.0, 10.0);
}

VerticalScrollbar:

static function VerticalScrollbar (position : Rect, value : float, size : float, topValue : float, bottomValue : float, style :
GUIStyle) : float

Crea una barra de desplazamiento (scrollbar) vertical.

90. CLASE GUI ( VII)

BeginGroup:

static function BeginGroup (position : Rect) : void


static function BeginGroup (position : Rect, text : String) : void
static function BeginGroup (position : Rect, image : Texture) : void
static function BeginGroup (position : Rect, content : GUIContent) : void
static function BeginGroup (position : Rect, style : GUIStyle) : void
static function BeginGroup (position : Rect, text : String, style : GUIStyle) : void
static function BeginGroup (position : Rect, image : Texture, style : GUIStyle) : void
static function BeginGroup (position : Rect, content : GUIContent, style : GUIStyle) : void

Comienza un grupo. Esta función debe emparejarse con una llamada a EndGroup.

Cuando comenzamos un grupo, el sistema de coordenadas para los controles GUI será tal que coincidirá la coordenada
(0,0) con la esquna superior izquierda del grupo. Los grupos pueden ser anidados, estando lo hijos agrupados respecto
de sus padres.

Esto es muy útil cuando movemos un montón de elementos GUI a lo largo de la pantalla. Un caso de uso común es
diseñar nuestros menús para que encajen en un específico tamaño de pantalla, centrando la GUI en pantallas más
amplias.

Los distintos prototipos de función usan estos parámetros:


position: Rectángulo en la pantalla a usar para el grupo.
text: Texto a mostrar en el grupo.
image: Textura a mostrar en el grupo.
content: Texto, imagen y tooltip para este grupo. Si el parámetro es
proporcionado, cualquier click de ratón es capturado por el grupo y
si no se proporciona, no se renderiza ingún bacground y los clicks
del ratón son renderizados.
style: El estilo a usar para el background.

Veremos muy claramente la funcionalidad de este método con un ejemplo:


function OnGUI () {
GUI.BeginGroup (new Rect (Screen.width / 2 -200 , Screen.height / 2 - 150, 400,
300));
GUI.Box (new Rect (0,0,400,300), "Este cuadrado está ahora centrado, y dentro del
mismo podemos colocar nuestro menú");
GUI.EndGroup ();
}

Expliquemos páso a paso en qué consiste lo que hemos hecho. Para empezar usamos la función BeginGroup para
crear un grupo en un rectángulo que se iniciará en el centro de la pantalla. Si lo ubicáramos en width/2 el rectángulo
quedaría desplazado, pues no se estaría teniendo en cuenta en este caso la anchura del propio rectángulo. De esta
manera, le hemos de restar al centro de la pantalla la mitad de la anchura del rectángulo, asegurándonos así que queda
justo en el centro. Hacemos lo propio con la altura.

Una vez ya tenemos definido para el grupo un rectángulo centrado con unas dimensiones de 400 X 300, ahora para los
controles dentro de dicho grupo la esquina superior izquierda del rectángulo pasa a ser la coordenada 0,0. Así, cuando
a continuación invocamos una caja con un texto y la ubicamos en las coordenadas 0,0, ésta se nos viene a colocar al
inicio del rectángulo del grupo.

No hemos de olvidarnos, por último, que si usamos una función BeginGroup hemos de usar cuando acabemos de
diseñar el grupo una función EndGroup obligatoriamente, para indicarle a Unity que las instrucciones referidas al grupo
ya se han acabado.

EndGroup:

static function EndGroup () : void

Finaliza un grupo.

BeginScrollView:
static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect) : Vector2
static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect, alwaysShowHorizontal :
boolean, alwaysShowVertical : boolean) : Vector2
static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect, horizontalScrollbar : GUIStyle,
verticalScrollbar : GUIStyle) : Vector2
static function BeginScrollView (position : Rect, scrollPosition : Vector2, viewRect : Rect, alwaysShowHorizontal :
boolean, alwaysShowVertical : boolean, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle) : Vector2

Inicia una vista de desplazamiento (scrollview) dentro de la GUI. Un scrollview nos permite poner una área más
pequeña en la pantalla dentro de un área mucho mayor, usando barras de desplazamiento (scrollbars) a los lados.

Esta función devuelve la posición del scroll modificada. Al igual que con otras variables, se recomienda reintroducir en la
variable que se le pasa a la función los datos nuevos que ésta devuelve.

Tiene estos parámetros:


position: Rectángulo en la pantalla a usar para el ScrollView.
scrollPosition: La distancia en píxeles que la vista es desplazada en las
direcciones X e Y.
viewRect: El rectángulo usado dentro del scrollview.
alwayShowHorizontal: Parámetro opcional para enseñar siempre el scrollbar
horizontal. Si lo establecemos en falso o no lo aportamos a la
función, sólo se enseñará cuando clientRect sea más ancho que
la posición.
alwayShowVertical: Lo mismo para el scrollbar vertical.
horizontalScrollbar: GUIStyle opcional a usar por el scrollbar horizontal. Si no se
aporta, se usará el estilo horizontalScrollbar del GUISkin que
se esté usando.
verticalScrollbar: Lo mismo para el scrollbar vertical

EndScrollView:

static function EndScrollView () : void

Acaba un scrollview iniciado con una llamada a BeginScrollView.

ScrollTo:

static function ScrollTo (position : Rect) : void

Desplaza todos los scrollviews incluidos para tratar de hacer visible una determinada posición.

Window:

static function Window (id : int, clientRect : Rect, func : WindowFunction, text : String) : Rect
static function Window (id : int, clientRect : Rect, func : WindowFunction, image : Texture) : Rect
static function Window (id : int, clientRect : Rect, func : WindowFunction, content : GUIContent) : Rect
static function Window (id : int, clientRect : Rect, func : WindowFunction, text : String, style : GUIStyle) : Rect
static function Window (id : int, clientRect : Rect, func : WindowFunction, image : Texture, style : GUIStyle) : Rect
static function Window (id : int, clientRect : Rect, func : WindowFunction, title : GUIContent, style : GUIStyle) : Rect

Crea una ventana emergente y devuelve el rectángulo en el que dicha ventana se ubica.

Las ventanas flotan sobre los controles GUI normales,


Windows float above normal GUI controls, y pueden ser opcionalmente arrastrados por los usuarios finales. A diferencia
de otros controles, necesitamos pasarles una función separada para los controles GUI para colocarlos dentro de la
ventana.

Nota: Si usamos GUILayout (de próxima explicación) para colocar nuestros componentes dentro de la ventana,
debemos usar GUILayout.Window.

Parámetros:
id: Una ID única a usar para cada ventana.
clientRect: Rectángulo en la pantalla a usar por el grupo.
func: La función que crea el GUI dentro de la ventana. Esta función debe
tomar un parámetro, que será la ID de la ventana para la que se está
creando la GUI.
text: Texto a mostrar como título para la ventana.
image: Textura que muestra una imagen en la barra del título.
content: Texto, imagen y tooltip para esta ventana.
style: Un estilo opcional a usar por la ventana. Si no se aporta, se usará el
estilo de ventana del GUISkin corriente.

Y vamos con un ejemplo:


var windowRect : Rect = Rect (20, 20, 120, 50);
function OnGUI () {
windowRect = GUI.Window (0, windowRect, CreaMiVentana, "Mi ventana");
}
function CreaMiVentana (windowID : int) {
if (GUI.Button (Rect (10,20,100,20), "Hola mundo"))
print ("Recibí un click");
}

Creamos una ventana con ID 0, que ubicamos en un rectángulo cuyas coordenadas y dimensiones están almacenadas
en una variable, variable en la cual almacenaremos el rectángulo retornado por la función. Como tercer parámetro le
pasamos una función que es la que crea los controles que irán dentro de la ventana, y por último el título de dicha
ventana.

La función que crea los controles toma como parámetro a su vez el primer parámetro de GUI.Window, que es la id de la
ventana, y en este caso meramente creamos un botón con un texto, que al ser pulsado imprime un mensaje en
plantalla.

91. CLASE GUI (y VIII)

DragWindow:

static function DragWindow (position : Rect) : void

Crea una ventana que puede arrastrarse. Si llamamos a esta función dentro del código de la ventana, automáticamente
ésta podrá arrastrarse.

Le hemos de pasar a la función un parámetro que indica la parte de la ventana que puede ser arrastrada, dando un
rectángulo que recorta la ventana original.

Para constatar lo que estoy diciendo, sólo tenéis que añadir esta línea a la función CreaMiVentana del ejemplo anterior:
GUI.DragWindow (Rect (0,0, 100, 20));

Pensad que 0,0 viene referido a la ventana emergente, no a las coordenadas generales.

static function DragWindow () : void

Esta función tiene un segundo prototipo que no requiere parámetros. Si queremos que nuestra ventana pueda ser
arrastrada desde cualquier parte del background de la misma, es preferible utilizar esta forma de la función y colocarla
al final de las funciones de la ventana.

Así, si modificamos esta parte del script:


function CreaMiVentana (windowID : int) {
if (GUI.Button (Rect (10,20,100,20), "Hola mundo"))
print ("Recibí un click");
GUI.DragWindow ();
}
podremos arrastrar nuestra ventana emergente desde cualquier punto de ésta.

BringWindowToFront:

static function BringWindowToFront (windowID : int) : void

Trae una ventana determinada al frente del resto de ventanas flotantes. Tiene como único parámetro la ID de la ventana
que queremos poner en primer plano.

BringWindowToBack:

static function BringWindowToBack (windowID : int) : void

Coloca una ventana determinada al fondo de las ventanas flotantes.

FocusWindow:

static function FocusWindow (windowID : int) : void

Hace que una ventana se convierta en la ventana activa. Se le pasa como parámetro la ID de dicha ventana.

UnFocusWindow:

static function UnfocusWindow () : void

Quita el foco de todas las ventanas.

92. CLASE GUILAYOUT (I)


Esta clase es la interfaz para la gui de Unity con distribución automática. Me explico:

Hay dos maneras que podemos usar para organizar y distribuir nuestras interfaces gráficas de usuario: fija y automática.
Hasta ahora hemos trabajado con la clase GUI, que es la forma fija de distribución. Esto entraña que cada vez que se
crea un nuevo elemento o control, se le ubica en un punto concreto (casi siempre a través de un Rect). En cambio, con
la forma automática de distribución (que es la que permite la clase GUILayout) esto no es necesario.

Se pueden usar ambos modos en la misma función OnGUI().

El modo fijo de distribución se suele usar cuando tenemos una interfaz prediseñada con la que trabajamos. El modo
automático en cambio se suele usar cuando no sabemos cuántos elementos acabaremos necesitando, o no queremos
preocuparnos de colocar a mano cada control.

Hay dos diferencias a tener en cuenta cuando usamos distribución automática:

1.- Hemos de usar GUILayout en lugar de GUI.


2.- Para la distribución automática no se usa la función Rect().

FUNCIONES DE CLASE:

Label:

static function Label (image : Texture, params options : GUILayoutOption[]) : void


static function Label (text : String, params options : GUILayoutOption[]) : void
static function Label (content : GUIContent, params options : GUILayoutOption[]) : void
static function Label (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void
static function Label (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void
static function Label (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void

Hace una etiqueta de distribución automático. Las etiquetas no proveen interación con el usuario, no capturan clicks de
ratón y son siempre renderizadas en estilo normal. Si quieres tener un control que responda visualmente a las entradas
de usuario usa un box control.

Todos los parámetros menos uno son idénticos a los de la función homónima de la clase GUI, así que a ellos me remito.
Pero quiero detenerme en ese parámetro distinto, que de hecho es el que marca la diferencia entre la distribución fija
(mediante Rect() y la automática. Me estoy refiriendo al parámetro params option, que es de tipo GUILayoutOption.

GUILayoutOption es una clase internamente usada por Unity para pasar diferentes opciones de distribución en las
funciones de la clase GUILayout. No se usa directamente, sino a través de funciones de tipo GUILayout, como por
ejemplo: GUILayout.Width, GUILayout.Height, GUILayout.MinWidth, GUILayout.MaxWidth, GUILayout.MinHeight,
GUILayout.MaxHeight, GUILayout.ExpandWidth y GUILayout.ExpandHeight.

Más adelante en esta clase estudiaremos dichas funciones.

Box:

static function Box (image : Texture, params options : GUILayoutOption[]) : void


static function Box (text : String, params options : GUILayoutOption[]) : void
static function Box (content : GUIContent, params options : GUILayoutOption[]) : void
static function Box (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void
static function Box (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void
static function Box (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void

crea una caja de distribución automática. Si queremos crear una caja con algún contenido dentro, hemos de usar el
parámetro de estilo de uno de los subgrupos de funciones (BeginHorizontal, BeginVertical, etc...).
Button:

static function Button (image : Texture, params options : GUILayoutOption[]) : boolean


static function Button (text : String, params options : GUILayoutOption[]) : boolean
static function Button (content : GUIContent, params options : GUILayoutOption[]) : boolean
static function Button (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : boolean
static function Button (text : String, style : GUIStyle, params options : GUILayoutOption[]) : boolean
static function Button (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : boolean

Crea un botón con distribución automática qevuelve true cuando el usuario lo presiona.

RepeatButton:

static function RepeatButton (image : Texture, params options : GUILayoutOption[]) : boolean


static function RepeatButton (text : String, params options : GUILayoutOption[]) : boolean
static function RepeatButton (content : GUIContent, params options : GUILayoutOption[]) : boolean
static function RepeatButton (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : boolean
static function RepeatButton (text : String, style : GUIStyle, params options : GUILayoutOption[]) : boolean
static function RepeatButton (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : boolean

Crea un botón que devuelve true tanto tiempo como el usuario lo mantiene pulsado.

TextField:

static function TextField (text : String, params options : GUILayoutOption[]) : String


static function TextField (text : String, maxLength : int, params options : GUILayoutOption[]) : String
static function TextField (text : String, style : GUIStyle, params options : GUILayoutOption[]) : String
static function TextField (text : String, maxLength : int, style : GUIStyle, params options : GUILayoutOption[]) : String

Crea un campo de texto de una linea donde el usuario puede editar un string.

PasswordField:

static function PasswordField (password : String, maskChar : char, params options : GUILayoutOption[]) : String
static function PasswordField (password : String, maskChar : char, maxLength : int, params options : GUILayoutOption[])
: String
static function PasswordField (password : String, maskChar : char, style : GUIStyle, params options : GUILayoutOption[])
: String
static function PasswordField (password : String, maskChar : char, maxLength : int, style : GUIStyle, params options :
GUILayoutOption[]) : String

Crea un campo de texto donde el usuario puede entrar una contraseña. Devuelve la contraseña editada.

TextArea:

static function TextArea (text : String, params options : GUILayoutOption[]) : String


static function TextArea (text : String, maxLength : int, params options : GUILayoutOption[]) : String
static function TextArea (text : String, style : GUIStyle, params options : GUILayoutOption[]) : String
static function TextArea (text : String, maxLength : int, style : GUIStyle, params options : GUILayoutOption[]) : String
Crea un campo de texto multilínea donde el user puede editar un string, y devuelve dicho string

93. CLASE GUILAYOUT (II)


Toggle:

static function Toggle (value : boolean, image : Texture, params options : GUILayoutOption[]) : boolean
static function Toggle (value : boolean, text : String, params options : GUILayoutOption[]) : boolean
static function Toggle (value : boolean, content : GUIContent, params options : GUILayoutOption[]) : boolean
static function Toggle (value : boolean, image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : boolean
static function Toggle (value : boolean, text : String, style : GUIStyle, params options : GUILayoutOption[]) : boolean
static function Toggle (value : boolean, content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) :
boolean

Crea un botón que alterna on/off. Devuelve un booleano que indica el estado de dicho botón

Toolbar:

static function Toolbar (selected : int, images : Texture[], params options : GUILayoutOption[]) : int
static function Toolbar (selected : int, content : GUIContent[], params options : GUILayoutOption[]) : int
static function Toolbar (selected : int, texts : string[], style : GUIStyle, params options : GUILayoutOption[]) : int
static function Toolbar (selected : int, images : Texture[], style : GUIStyle, params options : GUILayoutOption[]) : int
static function Toolbar (selected : int, contents : GUIContent[], style : GUIStyle, params options : GUILayoutOption[]) : int

Crea un toolbar (barra de herramientas). Devuelve un int que contiene el índice del botón seleccionado.

SelectionGrid:

static function SelectionGrid (selected : int, texts : string[], xCount : int, params options : GUILayoutOption[]) : int
static function SelectionGrid (selected : int, images : Texture[], xCount : int, params options : GUILayoutOption[]) : int
static function SelectionGrid (selected : int, content : GUIContent[], xCount : int, params options : GUILayoutOption[]) :
int
static function SelectionGrid (selected : int, texts : string[], xCount : int, style : GUIStyle, params options :
GUILayoutOption[]) : int
static function SelectionGrid (selected : int, images : Texture[], xCount : int, style : GUIStyle, params options :
GUILayoutOption[]) : int
static function SelectionGrid (selected : int, contents : GUIContent[], xCount : int, style : GUIStyle, params options :
GUILayoutOption[]) : int

Crea una cuadrícula de selección, devolviendo el int con el índice del botón seleccionado.

HorizontalSlider:

static function HorizontalSlider (value : float, leftValue : float, rightValue : float, params options : GUILayoutOption[]) :
float
static function HorizontalSlider (value : float, leftValue : float, rightValue : float, slider : GUIStyle, thumb : GUIStyle,
params options : GUILayoutOption[]) : float

Crea una barra de desplazamiento horizontal que el usuario puede arrastrar desde un mínimo hasta un máximo.
Devuelve un float con el valor que ha sido escogido por el usuario.

VerticalSlider:

static function VerticalSlider (value : float, leftValue : float, rightValue : float, params options : GUILayoutOption[]) : float
static function VerticalSlider (value : float, leftValue : float, rightValue : float, slider : GUIStyle, thumb : GUIStyle, params
options : GUILayoutOption[]) : float

Crea una barra de desplazamiento vertical que el usuario puede arrastrar desde un mínimo hasta un máximo.
HorizontalScrollbar:

static function HorizontalScrollbar (value : float, size : float, leftValue : float, rightValue : float, params options :
GUILayoutOption[]) : float
static function HorizontalScrollbar (value : float, size : float, leftValue : float, rightValue : float, style : GUIStyle, params
options : GUILayoutOption[]) : float

Crea un scrollbar horizontal. Devuelve el valor modificado en float, que puede ser cambiado por el usuario arrastrando el
scrollbar o haciendo click en las flechas al final.

VerticalScrollbar:

static function VerticalScrollbar (value : float, size : float, topValue : float, bottomValue : float, params options :
GUILayoutOption[]) : float
static function VerticalScrollbar (value : float, size : float, topValue : float, bottomValue : float, style : GUIStyle, params
options : GUILayoutOption[]) : float

Crea un scrollbar vertical.

94. CLASE GUILAYOUT (III)

Space:

static function Space (pixels : float) : void

Inserta un espacio en el actual grupo de distribución. La dirección de dicho espacio dependerá del la dirección del grupo
de distribución en el que estemos trabajando. Si por ejemplo se trata de un grupo vertical, el espacio será vertical.

Observemos la diferencia con dos ejemplos:


function OnGUI () {
GUILayout.Button ("Primer botón");
GUILayout.Space (20);
GUILayout.Button ("Segundo botón");
}

Si ejecutamos este primer script, observaremos dos botones situados uno encima del otro, con un espacio (vertical)
entre ambos de 20 píxeles. En cambio, si estuviéramos trabajando con un grupo de distribución horizontal...
function OnGUI () {
GUILayout.BeginHorizontal();
GUILayout.Button ("Primer botón");
GUILayout.Space (20);
GUILayout.Button ("Segundo botón");
GUILayout.EndHorizontal();
}
... nos encontraríamos con un botón al lado del otro separados por un espacio (horizontal) de 20 píxeles. (En breve
estudiaremos las funciones BeginHorizontal y EndHorizontal, aunque supongo que se intuye su cometido)

FlexibleSpace:

static function FlexibleSpace () : void

Inserta un elemento de espacio flexible. Esta función utiliza cualquier espacio que sobra en un diseño.

BeginHorizontal:

static function BeginHorizontal (params options : GUILayoutOption[]) : void


static function BeginHorizontal (style : GUIStyle, params options : GUILayoutOption[]) : void
static function BeginHorizontal (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void
static function BeginHorizontal (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void
static function BeginHorizontal (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void

Empieza un grupo de controles horizontal. Todos los controles dentro de este elemento serán colocados
horizontalmente uno junto a otro. El grupo debe cerrarse con una llamada a EndHorizontal.

EndHorizontal:

static function EndHorizontal () : void

Cierra el grupo abierto por BeginHorizontal.

BeginVertical:

static function BeginVertical (params options : GUILayoutOption[]) : void


static function BeginVertical (style : GUIStyle, params options : GUILayoutOption[]) : void
static function BeginVertical (text : String, style : GUIStyle, params options : GUILayoutOption[]) : void
static function BeginVertical (image : Texture, style : GUIStyle, params options : GUILayoutOption[]) : void
static function BeginVertical (content : GUIContent, style : GUIStyle, params options : GUILayoutOption[]) : void

Empieza un grupo de controles vertical. Todos los controles dentro de este elemento serán situados verticalmente uno
debajo del otro. El grupo debe cerrarse con EndVertical

EndVertical:

static function EndVertical () : void

Cierra un grupo iniciado por BeginVertical.

BeginArea:

static function BeginArea (screenRect : Rect) : void


static function BeginArea (screenRect : Rect, text : String) : void
static function BeginArea (screenRect : Rect, image : Texture) : void
static function BeginArea (screenRect : Rect, content : GUIContent) : void
static function BeginArea (screenRect : Rect, style : GUIStyle) : void
static function BeginArea (screenRect : Rect, text : String, style : GUIStyle) : void
static function BeginArea (screenRect : Rect, image : Texture, style : GUIStyle) : void
static function BeginArea (screenRect : Rect, content : GUIContent, style : GUIStyle) : void

Comienza un bloque GUILayout de controles GUI en un determinado área de la pantalla.


Por defecto, cualquier control GUI hecho usando GUILayout es situado en la parte superior izquierda de la pantalla. Si
queremos colocar una serie de controles en una zona arbitraria, hemos de usar esta función para definir un nuevo area.

por ejemplo:
function OnGUI () {
GUILayout.BeginArea (Rect (10,10,100,100));
GUILayout.Button ("Un botón");
GUILayout.Button ("Otro botón");
GUILayout.EndArea ();
}

Aquí iniciamos un área de controles ubicada en las coordenadas 10,10 y con unas dimensiones de 100 X 100.

EndArea:

static function EndArea () : void

Cierra un área de controles abierto con BeginArea.

BeginScrollView:

static function BeginScrollView (scrollPosition : Vector2, params options : GUILayoutOption[]) : Vector2


static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean,
params options : GUILayoutOption[]) : Vector2
static function BeginScrollView (scrollPosition : Vector2, horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle,
params options : GUILayoutOption[]) : Vector2
static function BeginScrollView (scrollPosition : Vector2, style : GUIStyle) : Vector2
static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean,
horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle, params options : GUILayoutOption[]) : Vector2
static function BeginScrollView (scrollPosition : Vector2, alwaysShowHorizontal : boolean, alwaysShowVertical : boolean,
horizontalScrollbar : GUIStyle, verticalScrollbar : GUIStyle, background : GUIStyle, params options :
GUILayoutOption[]) : Vector2

Comienza un scrollview (o vista de desplazamiento) automáticamente distribuído. Este puede tomar cualquier contenido
que le metas dentro y lo muestra normalmente (sin que se vea el scroll). Si no cabe el contenido, aparece la barra. Una
llamada a esta función debe siempre acabar con una llamada a EndScrollView.

Esta función devuelve un Vector2 con la posición del scroll modificada.

Cuenta con los siguientes parámetros:


scrollPosition: La posición de uso de la pantalla.
alwayShowHorizontal: Parámetro opcional para enseñar siempre el scrollbar
horizontal. Si está en falso o no se aporta, sólo se mostrará
el scrollbar cuando el contenido dentro del scrollview sea más
ancho que éste.
alwayShowVertical: Lo mismo que el anterior para el scrollbar vertical.
horizontalScrollbar: GUIStyle opcional a usar para el scrollbar horizontal. Si no se
indica, se usará el estilo de horizontalScrollbar del GUISkin
que se esté usando.
verticalScrollbar Lo mismo pra el scrollbar vertical.

EndScrollView:

static function EndScrollView () : void

Finaliza un scroll view empezado con una llamada a BeginScrollView.

95. CLASE GUILAYOUT (y IV)

Window:
static function Window (id : int, screenRect : Rect, func : GUI.WindowFunction, text : String, : ) : Rect
static function Window (id : int, screenRect : Rect, func : GUI.WindowFunction, image : Texture, : ) : Rect
static function Window (id : int, screenRect : Rect, func : GUI.WindowFunction, content : GUIContent, : ) : Rect
static function Window (id : int, screenRect : Rect, func : GUI.WindowFunction, text : String, style : GUIStyle, : ) : Rect
static function Window (id : int, screenRect : Rect, func : GUI.WindowFunction, image : Texture, style : GUIStyle, : ) :
Rect
static function Window (id : int, screenRect : Rect, func : GUI.WindowFunction, content : GUIContent, style : GUIStyle, : )
: Rect

Crea una ventana emergente que distribuye su contenido automáticamente. Las ventanas flotan sobre los controles GUI
normales y pueden opcionalmente ser arrastradas por el usuario final. A diferencia de otros controles, necesitas
pasarles una función separada a los controles GUI para ponerlos dentro de la ventana.

Devuelve un rect, que es el rectángulo en el que está la ventana y que puede tener una posición y tamaño diferentes
que aquélla.

Width:

static function Width (width : float) : GUILayoutOption

Opción pasada a un control para darle una anchura absoluta.


function OnGUI() {
GUILayout.Button("Botón con una anchura fija", GUILayout.Width(300));
}
MinWidth:

static function MinWidth (minWidth : float) : GUILayoutOption

96. CLASE
Opción pasada TEXTURE
a un control para especificar una anchura mínima.

MaxWidth:

static function MaxWidth (maxWidth : float) : GUILayoutOption

Opción pasada a un control para especificar una anchura máxima.

Height:

static function Height (height : float) : GUILayoutOption

Opción pasada a un control para darle una altura absoluta.

MinHeight:

static function MinHeight (minHeight : float) : GUILayoutOption

Opción pasada a un control para especificar una altura mínima.

Clase base para el manejo de textures. Contiene funcionalidades que son comuntes tanto en la clase Texture2D como
MaxHeight:
en la clase RenderTexture.

VARIABLES:
static function MaxHeight (maxHeight : float) : GUILayoutOption

width:
Opción pasada a un control para especificar una altura máxima.
Variable de sólo lectura que indica la anchura de la textura en píxeles.

ExpandWidth:
height:
static function ExpandWidth (expand : boolean) : GUILayoutOption
var height : int

Opción pasada a un control para habilitar o deshabilitar la expansión horizontal.


Variable de sólo lectura que indica la alturade la textura en píxeles.

ExpandHeight:
filterMode:
tatic function ExpandHeight (expand : boolean) : GUILayoutOption
var filterMode : FilterMode

Modo
Opcióndepasada
filtradoade
unlacontrol
textura. La habilitar
para variable oFilterMode es la
deshabilitar una enumeración
expansión que permite estos valores:
vertical.
Point: Filtrado de puntos, los píxeles de la textura se convierten en bloques
de cerca.
Bilinear: Filtrado bilinear, las muestras de la textura se promedian.
Trilinear: Filtrado trilinear, los píxeles de la textura se promedian y también
son mezclados entre los niveles del mipmap.
anisoLevel:

var anisoLevel : int

Nivel de filtro anisotrópico de la textura (anisotropía recordemos que implica que algunas características del objeto,
como la luz, dependen del lugar desde donde éste es observado.)

El filtrado anisotrópico hace que las texturas luzcan mejor cuando son vistas en un ángulo bajo, pero a cambio de un
importante coste de rendimiento en la tarjeta gráfica. Usualmente se usa esto en texturas de suelo, tierra o carreteras
para que se vean mejor.

El rango de valor de esta variable va de 1 a 9, donde 1 equivale no filtro aplicado y 9 implica filtro totalmente aplicado. A
medida que el valor es más grande, la textura es más clara en los ángulos bajos.

wrapMode:

var wrapMode : TextureWrapMode

Modo de envoltura de la textura. Esta puede establecerse en clamp (que podemos traducir por fija o pinzada) o en
repear, que "azuleja" la textura en un número de repeticiones.
renderer.material.mainTexture.wrapMode = TextureWrapMode.Clamp;

TextureWrapMode como decimos es una enum con estos valores:


Repeat: Azuleja la textura, creando un patrón de repetición.
Clamp: Sujeta la textura al último pixel en el borde de ésta.

97. CLASE TEXTURE2D (I)


Es esta una clase para manejo de texturas. Se suele usar para crear texturas "al vuelo" (en tiempo de ejecución) o para
modificar los assets de texturas existentes.

VARIABLES:

mipmapCount:

var mipmapCount : int

Variable de sólo lectura que indica cuántos niveles de mipmap hay en la textura.

El valor retornado incluye el nivel base también, razón por la cual esta variable siempre vale 1 o más.

El conteo de mipmaps se usa en caso de que utilicemos las funciones GetPixels o SetPixels para obtener o modificar
respectibamente los diferentes niveles de mipmal. Por ejemplo, podemos querer cambiar una textura de tal manera que
cada nivel de mipmap se tinte de un color diferente, y así en el juego veríamos cuantos niveles de mipmap son en
realidad visibles.

textureFormat:

var format : TextureFormat

El formato de los datos de los píxeles en la textura (sólo lectura).

FUNCIONES:

Texture2D:

static function Texture2D (width : int, height : int) : Texture2D

Crea una nueva textura vacía. Esta tendrá un tamaño dado por anchura y altura, con un formato de textura ARGV32 y
con mipmaps.

Normalmente querremos poner los colores de la textura después de crearla, usando las funciones SetPixel, SetPixels y
Apply.

function Start () {
var nuevaTextura = new Texture2D (128, 128);
renderer.material.mainTexture = nuevaTextura;
}

Este sería el ejemplo básico. Creamos una nueva textura de dimensiones 128 X 128 y se la asignamos como principal
textura al gameobject al que le vinculemos el script. En este caso eliminamos MiPrimerScript del gameobject
PortaScripts y se lo vinculamos a la Esfera. Observaremos, si la tenemos seleccionada al darle al play, que ésta importa
a vuelo la nueva textura, que por defecto es de color negro.

static function Texture2D (width : int, height : int, format : TextureFormat, mipmap : boolean) : Texture2D

Este segundo prototipo se diferencia del primero en que se le pasa a la función como parámetro un determinado
formato para la textura y a la vez se indica si se crea con o sin mipmaps.

SetPixel:

function SetPixel (x : int, y : int, color : Color) : void

Función que nos permite indicar el color de los píxeles en las coordenadas que le pasamos. No obstante, para
actualizar en realidad el color de dichos píxeles en la tarjeta gráfica del usuario, deberemos acto seguido llamar a la
función Apply.

Dado que esta manera de actualizar las texturas consume bastantes recursos, así que sería deseable que cambiáramos
tantos píxeles como fuera posible entre cada llamada a la función Apply.

Si tenemos pensado regenerar constantemente texturas en tiempo de ejecución, puede ser más rápido generar un array
de colores de píxeles y asignarlos todos a la vez con la función SetPixels.

Esta función, por último, trabaja sólo con los formatos ARGB32, RGB24 y Alpha8. Para el resto de formatos SetPixel es
ignorada.

Vamos a verlo con un ejemplo. Previamente sería interesante colocar la cámara en las siguientes coordenadas:
position(0,1.5,-6), rotation(10,0,0). Así tendremos una mejor visión de lo que le acontecerá a la esfera en el próximo
script. Editamos MiPrimerScript ahora:
function Start () {
var nuevaTextura = new Texture2D(128, 128);
renderer.material.mainTexture = nuevaTextura;
var mitadH : int = nuevaTextura.width/2;
var mitadV : int = nuevaTextura.height/2;
var unColor : Color = Color.green;
for (var x : int = 0; x < mitadH; x++){
for (var y : int = 0; y < mitadV; y++){
nuevaTextura.SetPixel(x, y, unColor);
}
}
nuevaTextura.Apply();
}

Vamos por partes. Creamos primero una nueva textura y se la pasamos al gameobject al que tenemos vinculado el
script. Almacenamos acto seguido en sendos integers la mitad de la altura y la anchura de la nueva textura. De esta
manera, en un bucle for podemos asignar un determinado color a esa mitad. Recordemos en todo caso dejar la función
Apply fuera del bucle, a los efectos de no sobrecargar el pc.
GetPixel:

function GetPixel (x : int, y : int) : Color

Devuelve el color del píxel que se halla en las coordenadas dadas. Si éstas están fuera de los bordes (más grandes de
la anchura/altura o más pequeñas que cero) serán fijadas o repetidas en base al wrapmode de la textura

Si quieres leer un bloque amplio de píxeles de una textura, será más rápido usar GetPixels, la cual devuelve un bloque
entero de colores de píxeles.

La textura debe tener activa la variable Is Readable en import settings, ya que de lo contrario fallará la función.

98. CLASE TEXTURE2D (y II)


GetPixelBilinear:

function GetPixelBilinear (u : float, v : float) : Color

Devuelve el color de los píxeles filtrado en coordenadas normalizadas (u, v).

dichas coordenadas normalizadas u y v van de 0.0 a 1.0, al igual que las coordenadas UV en mallas.

SetPixels:

function SetPixels (colors : Color[], miplevel : int = 0) : void

Establece un bloque de colores de píxeles. Esta función toma un array de colores y cambia los colores de los píxeles
del nivel mip entero de la textura. Precisa una posterior llamada a Aply para que los cambios se actualicen en la tarjeta
gráfica.

El array de colores es un array 2d plano, donde los píxeles se distribuyen de izquierda a derecha y de abajo arriba. El
tamaño del array debe ser al menos la anchura por la altura o el level de mapeo mip usado.

Usar SetPixels puede ser más rápido que llamar a SetPixel repetidamente, especialmente para texturas grandes.
Además, SetPixels puede acceder a niveles de mipmap individuales.

function SetPixels (x : int, y : int, blockWidth : int, blockHeight : int, colors : Color[], miplevel : int = 0) : void

Esta versión de la función no modifica el nivel mip entero, sino sólo la parte comprendida entre los parámetros
blockWidth y blockHeight empezando desde x,y. El array de colores debe tener un tamaño de blockWidth*blockHeight, y
el bloque modificado debe caber dentro del nivel mip usado.

SetPixels32:

function SetPixels32 (colors : Color32[], miplevel : int = 0) : void

Establece un bloque de colores de píxeles. Esta función toma un array de tipo Color32 y cambia los colores de los
píxeles del nivel de mip entero de la textura. No nos olvidemos de llamar después a Apply.

LoadImage:

function LoadImage (data : byte[]) : boolean

Carga una imagen de tipo JPG o PNG desde un array de bytes.

GetPixels:

function GetPixels (miplevel : int = 0) : Color[]

Esta función devuelve un array de colores de píxeles del nivel de mip entero de la textura.

function GetPixels (x : int, y : int, blockWidth : int, blockHeight : int, miplevel : int = 0) : Color[]

Este segundo prototipo retorna sólo el mip de la región blockWidth por blockHeight empezando por x,y.
GetPixels32:

function GetPixels32 (miplevel : int = 0) : Color32[]

Obtiene un bloque de colores de píxeles en formato Color32.

Apply:

function Apply (updateMipmaps : boolean = true, makeNoLongerReadable : boolean = false) : void

Aplica en la práctica todos los cambios previamente efectuados con las funciones SetPixel y SetPixels.

Si el parámetro updateMipmaps es true, los niveles de mipmap son recalculados también, usando el nivel base como
fuente. Normalmente tendremos este parámetro en true salvo cuando queramos modificar los niveles mip nosotros
mismos usando SetPixels.

Si el parámetro makeNoLongerReadable es true, la textura se marcará como no legible y la memoria será liberada en la
siguiente actualización. Por defecto makeNoLongerReadable se pone en false.

Esta función, como ya dijimos previamente, consume muchos recurso, así que es deseable hacer el mayor número de
cambios precisos entre cada llamada a la misma.

Resize:

function Resize (width : int, height : int, format : TextureFormat, hasMipMap : boolean) : boolean

Redimensiona la textura, y en concreto modifica la anchura, altura, formato y en ocasiones crea mipmaps. Esta función
es muy similar al constructor, salvo que esta trabaja con una textura que ya existe.

precisa llamar a Apply para que los cambios tengan efecto en la tarjeta gráfica.

function Resize (width : int, height : int) : boolean

Esta variante cambia meramente la altura y o la anchura de la textura.

ReadPixels:

function ReadPixels (source : Rect, destX : int, destY : int, recalculateMipMaps : boolean = true) : void

Lee píxeles de pantalla dentro de los datos de la textura guardada. Copia un área de píxeles rectangular de la
RenderTexture en uso o bien la vista (especificada por el parámetro source) dentro de la posición definida por destX y
destY. Ambas coordenadas usan el espacio de píxeles, donde (0,0) is abajo a la izquierda.

Si recalculateMipMaps está establecido en true, los mip maps de la textura serán tambien actualizados. En caso
contrario deberemos llamar a Apply para recalcularlos.

Esta función trabaja sólo con los formatos de textura ARGB32 y RGB24.

EncodeToPNG:
function EncodeToPNG () : byte[]

Codifica esta textura en formato PNG. El array de bytes que devuelve es el archivo PNG. Puedes escribirlo entonces en
disco o enviarlo a través de la rec, por ejemplo.

Esta función solo trabaja con los formatos de textura ARGB32 y RGB24. La textura también debe tener el flag Is
Readable marcado en import settings.

99. CLASE INPUT (I)

Es la interfaz que controla todo el sistema de entradas (inputs) de Unity. Esta clase se usa, por ejemplo, para leer la
configuración de ejes en el input Manager.

Para leer un eje usaríamos Input.GetAxis con uno de los ejes por defecto: “Horizontal” y “Vertical” están configurados
para joystick, así como A,W,S,D, y las teclas de flecha. “MouseX” y “Mouse Y” están configurados para el movimiento
del ratón. Por su parte “Fire1", "Fire2" y "Fire3" están configurados para Ctrl, Alt y tres botones de ratón o joystick.
Pueden añadirse nuevos ejes de entrada en el Input Manager.

Si hemos se usar inputs para cualquier tipo de comportamiento que entrañe movimiento, es aconsejable usar
Input.GetAxis. Esto nos dará una entrada más suave y configurable que puede servir para teclado, joystick o mouse. En
cambio es mejor usar Input.GetButton para acciones como eventos, mejor que para movimientos.

Las llamadas a inputs se deben hacer dentro de la función update.

VARIABLES DE CLASE:

mousePosition:

static var mousePosition : Vector3

Variable de sólo lectura que indica la posición actual del ratón en coordenadas de píxeles, donde la esquina inferior
izquierda de la pantalla o ventana está en (0, 0) y la superior derecha en (Screen.width, Screen.height).

Podemos probarlo con un ejemplo sencillo, para el cual eliminamos el script vinculado a la esfera, y reeditamos
MiPrimerScript como sigue:
function Update(){
var apunten : Vector3 = Input.mousePosition;
Debug.Log(apunten);
}

Salvamos y arrastramos hasta PortaScripts. Meramente se nos imprimirá en pantalla la posición exacta en píxeles de
nuestro cursor. Podemos observar que la esquina inferior izquierda se mueve en parámetros del 0,0.

anyKey:
static var anyKey : boolean

Booleano de sólo lectura que comprueba si hay alguna tecla o botón del ratón apretada en ese momento.

Podemos modificar nuestro script anterior para ilustrar esta variable:


function Update(){
if(Input.anyKey){
Debug.Log("Se ha pulsado una tecla o botón");
}
}
No tenemos más que apretar cualquier tecla o botón del ratón para ver el mensaje impreso.

anyKeyDown:

static var anyKeyDown : boolean

Variable de sólo lectura que devuelve true el primer frame en que el usuario golpea cualquier tecla o botón del ratón.
Debemos colocar esta variable dentro de la función update, ya que el estado de la misma se resetea cada frame. No
devolverá true de nuevo hasta que el usuario libere todas las teclas/botones y presione alguna tecla/botón otra vez.

Para entender gráficamente la diferencia entre la variable anterior y la siguiente vamos a hacer una cosa: volvemos a
darle al play (sigue en vigor el ejemplo anterior) y hacemos un click con un botón del ratón. Automáticamente nos
aparece el mensaje impreso bajo la ventana del juego. Hacemos un click ahora sobre dicho mensaje, para que nos
aparezca el popup de la consola, tal como muestra la captura:

Vale. Ahora, sin retirar dicho popup, presionamos sin soltar el botón del mouse sobre la ventana Game. Vemos que se
imprime de manera continuada el mensaje (una por cada frame que mantenemos pulsado el botón del ratón).

Y ahora sustituimos en el script la variable "anyKey" por "anyKeyDown", y repetimos los pasos anteriorer.
Comprobaremos que aunque mantengamos el botón del ratón presionado, el mensaje sólo se imprime el primer frame
que lo pulsamos, y no se vuelve a imprimir hasta que lo soltamos y lo volvemos a apretar.

inputString:

static var inputString : String

Variable de sólo lectura que devuelve la entrada de teclado introducida este frame. La misma sólo puede contener
caracteres ASCII o un par de caracteres especiales que deben ser manejados: el carácter “\b” significa retroceso y “\n”
representa return o enter.

acceleration:

static var acceleration : Vector3


Ultima medicion de aceleración lineal de un dispositimo en espacio tridimensional. Sólo lectura

100. CLASE INPUT (II)

accelerationEvents:

static var accelerationEvents : AccelerationEvent[]

Variable de sólo lectura que devuelve una lista de mediciones de aceleración ocurridas durante el último frame. Dichas
medidas son alojadas en variables temporales.

Dichas variables temporales son un array del tipo AccelerationEvent, que es una estructura con dos valores:

acceleration: De tipo Vector3, es el valor de aceleración.


deltaTime: De tipo float, el tiempo transcurrido desde la última medición de aceleración.

accelerationEventCount:

static var accelerationEventCount : int

Número de mediciones de aceleración ocurrida durante el último frame.

eatKeyPressOnTextFieldFocus:

static var eatKeyPressOnTextFieldFocus : boolean

Propiedad que indica si las teclas impresas son comidas por la entrada de texto si esta tiene el foco (por defecto true).

FUNCIONES DE CLASE:

GetAxis:

static function GetAxis (axisName : String) : float

Devuelve el valor del eje virtual identificado por axisName.

El valor estará en el rango -1...1 para entradas de teclado (tradicionalmente la flechas de desplazamiento) y joystick. Si
el axis es indicado por el movimiento del ratón, éste será multiplicado por el eje de sensibilidad y su rango será distinto a
-1…1.

Es una de las funciones con las que más tropezaremos cuando programemos con Unity, así que merece la pena que
nos detengamos para ilustrarla con algún ejemplo.

El primer ejemplo lo hemos sacado del manual de referencia. Para que funciones bien (recordemos que tenemos en la
actualidad MiPrimerScript vinculado a la esfera) es necesario que eliminemos el componente Rigidbody de la esfera. Y
acto seguido editamos nuestro script como sigue:
var velocidad : float = 2.0;
var velocidadRotacion : float = 45.0;
function Update () {

var translacion : float = Input.GetAxis ("Vertical") * velocidad;


var rotacion : float = Input.GetAxis ("Horizontal") * velocidadRotacion;
translacion *= Time.deltaTime;
rotacion *= Time.deltaTime;
transform.Translate (0, 0, translacion);
transform.Rotate (0, rotacion, 0);
}

Tratemos de explicar lo que hemos hecho. A través de Input.GetAxis recogemos las entradas de teclado provinentes de
las flechas de desplazamiento verticales y horizontales. Así, la flecha de desplazamiento hacia arriba vale 1 y la de
desplazamiento hacia abajo vale -1, y lo mismo hacia la derecha (1) y la izquierda (-1). Dichos valores son multiplicados
por la velocidad contenida en la variable "velocidad" en el caso del eje arriba/abajo, y por la contenida en la variable
"velocidadRotacion" para el eje derecha/izquierda. Ambos valores son almacenados en variables de tipo Vector3 que
luego -tras ser reconvertidos en velocidad por frame a velocidad por segundo- se utilizan para participar en el
movimiento del game object tanto en el eje delante/detrás de translación como en el eje Y de rotación.

En suma, nos queda un script -muy básico y torpe, eso sí- para manejar el desplazamiento y giro de nuestra esfera en
base a las flechas de desplazamiento. Probémoslo un rato.

Hemos dicho que Input.GetAxis también acepta como entrada el provocado por el movimiento vertical y horizontal del
ratón, así que ilustrémoslo:
var velocidadHorizontal : float = 20.0;
var velocidadVertical : float = 20.0;
function Update () {
var h : float = velocidadHorizontal * Input.GetAxis ("Mouse X");
var v : float = velocidadVertical * Input.GetAxis ("Mouse Y");
transform.Rotate (v, h, 0);
}

Es un ejemplo sencillo. Se almacena en sendas variables el fruto de multiplicar el valor de los ejes de desplazamiento
horizontal y vertical del ratón (que recordemos que no es -1,1) por el valor de velocidad que le hayamos dado a las
variables expuestas del inicio de script. En base a ello, nuestra esfera girará sobre su eje X e Y en respuesta a los
movimientos vertical y horizontal de nuestro ratón.

GetAxisRaw:

static function GetAxisRaw (axisName : String) : float

Devuelve el valor del eje virtual identificado por el parámetro axisName sin ningún filtro de suavizado aplicado. El valor
estará en el rango de -1…1 para entrada de teclado y joystick. Como a la entrada -al contrario de la función anterior- no
se le aplica smooth (suavizado),la entrada de teclado será siempre o -1 o cero o 1. Esta función puede sernos útil para
el caso de que queramos hacer todo el proceso de suavizado de entrada nosotros mismos manualmente.

GetButton:

static function GetButton (buttonName : String) : boolean

Devuelve true mientras el botón virtual que le pasemos como parámetro en formato string esté presionado. Podemos
pensar por ejemplo en un disparador automático, que devolvería true mientras el botón estuviera presionado.

Eso sí, así como esta función es óptima para acciones como disparar un arma, para cualquier tipo de movimiento es
mejor usar GetAxis, que a éste le introduce valores de suavizado que en GetButton no hallaremos (ni podremos
implementar manualmente, al contrario que en GetAxisRaw.

Un ejemplo:
var proyectil : GameObject;
var frecuenciaDisparo : float = 0.5;
private var proximoDisparo : float = 0.0;
function Update () {
if (Input.GetButton ("Fire1") && Time.time > proximoDisparo) {
proximoDisparo = Time.time + frecuenciaDisparo;
var clon : GameObject =
Instantiate(proyectil, transform.position+Vector3.forward, transform.rotation) as
GameObject;
}
}

Salvamos y arrastramos el cubo a la variable expuesta proyectil. Pulsamos play y observaremos que estamos
clonando/disparando cubos cada vez que -dentro del lapso de tiempo permitido- pulsamos el botón izquierdo del ratón o
la tecla Ctrl situada a la izquierda del teclado (que son por defecto los dos elementos que tenemos vinculados al evento
Fire1. Por otro lado, si mantenemos pulsado de manera ininterrumpida bien el botón o bien la tecla indicados,
observaremos que disparamos un cubo cada medio segundo.

En sí el script comprueba si hemos pulsado la tecla o botón que tengamos asignada a Fire1. En caso afirmativo pasa a
comprobar si ha transcurrido un lapso de tiempo superior al que le hemos fijado en frecuenciaDisparo (que para el
primer disparo valdrá cero). Si también es true esta segunda condición se le añade medio segundo de espera al resto
de disparos más el tiempo transcurrido en hacerlo, el script nos permite clonar/disparar otro proyectil más.

GetButtonDown:

static function GetButtonDown (buttonName : String) : boolean

Devuelve true durante el frame en que el jugador aprieta el botón virtual identificado como buttonName. Debemos llamar
siempre a esta función desde la función Update, dado que el estado se resetea cada frame. No devolverá true hasta
que el usuario libere la tecla y la presione de nuevo, al igual que sucedía con anyKeyDown.

GetButtonUp:

static function GetButtonUp (buttonName : String) : boolean

Devuelve true el primer frame en que el jugador libera el botón virtual identificado como buttonName. Recordemos
llamar esta función desde Update ya que se resetea su estado cada frame. No devolverá true hasta que se libere la
tecla y se vuelva a presionar.

101. CLASE INPUT (III)


GetKey:

static function GetKey (name : String) : boolean

Devuelve true mientras el jugador aprieta la tecla identificada como name (pensemos de nuevo en un disparador
automático)

Para ver la lista de indentificadores de tecla podemos consultar Input Manager en el menú=>Edit=>Project
Settings=>Input.

Un ejemplo sencillo:
function Update () {
if (Input.GetKey ("up"))
print ("Has presionado la flecha de desplazamiento superior");
if (Input.GetKey ("down"))
print ("Has presionado la flecha de desplazamiento inferior");
}

static function GetKey (key : KeyCode) : boolean

En este segundo prototipo la función devuelve true mientras el jugador presiona la tecla identificada por el parámetro de
tipo KeyCode.

Así, el ejemplo anterior en esta segunda modalidad se quedaría así:


function Update () {
if (Input.GetKey (KeyCode.UpArrow))
print ("Has presionado la flecha de desplazamiento superior");
if (Input.GetKey (KeyCode.DownArrow))
print ("Has presionado la flecha de desplazamiento inferior");
}

Paso a relacionar todo el enum KeyCode:


None Not assigned (never is pressed)
Backspace The backspace key
Delete The forward delete key
Tab The tab key
Clear The Clear key
Return Return key
Pause Pause on PC machines
Escape Escape key
Space Space key
Keypad0 Numeric keypad 0
Keypad1 Numeric keypad 1
Keypad2 Numeric keypad 2
Keypad3 Numeric keypad 3
Keypad4 Numeric keypad 4
Keypad5 Numeric keypad 5
Keypad6 Numeric keypad 6
Keypad7 Numeric keypad 7
Keypad8 Numeric keypad 8
Keypad9 Numeric keypad 9
KeypadPeriod Numeric keypad '.'
KeypadDivide Numeric keypad '/'
KeypadMultiply Numeric keypad '*'
KeypadMinus Numeric keypad '-'
KeypadPlus Numeric keypad '+'
KeypadEnter Numeric keypad enter
KeypadEquals Numeric keypad '='
UpArrow Up arrow key
DownArrow Down arrow key
RightArrow Right arrow key
LeftArrow Left arrow key
Insert Insert key key
Home Home key
End End key
PageUp Page up
PageDown Page down
F1 F1 function key
F2 F2 function key
F3 F3 function key
F4 F4 function key
F5 F5 function key
F6 F6 function key
F7 F7 function key
F8 F8 function key
F9 F9 function key
F10 F10 function key
F11 F11 function key
F12 F12 function key
F13 F13 function key
F14 F14 function key
F15 F15 function key
Alpha0 The '0' key on the top of the alphanumeric keyboard.
Alpha1 The '1' key on the top of the alphanumeric keyboard.
Alpha2 The '2' key on the top of the alphanumeric keyboard.
Alpha3 The '3' key on the top of the alphanumeric keyboard.
Alpha4 The '4' key on the top of the alphanumeric keyboard.
Alpha5 The '5' key on the top of the alphanumeric keyboard.
Alpha6 The '6' key on the top of the alphanumeric keyboard.
Alpha7 The '7' key on the top of the alphanumeric keyboard.
Alpha8 The '8' key on the top of the alphanumeric keyboard.
Alpha9 The '9' key on the top of the alphanumeric keyboard.
Exclaim Exclaim key
DoubleQuote Double quote key
Hash Hash key
Dollar Dollar sign key
Ampersand Ampersand key
Quote Quote key
LeftParen Left Parent key
RightParen Right Parent key
Asterisk Asterisk key
Plus Plus key
Comma Comma ',' key
Minus Minus '-' key
Period Period '.' key
Slash Slash '/' key
Colon Colon ',' key
Semicolon Semicolon ';' key
Less Less '<' key
Equals Equals '=' key
Greater Greater '>' key
Question Question mark '?' key
At At key
LeftBracket Left bracket key
Backslash Backslash key
RightBracket Backslash key
Caret Caret key
Underscore Underscore '_' key
BackQuote Back quote key
A 'a' key
B 'b' key
C 'c' key
D 'd' key
E 'e' key
F 'f' key
G 'g' key
H 'h' key
I 'i' key
J 'j' key
K 'k' key
L 'l' key
M 'm' key
N 'n' key
O 'o' key
P 'p' key
Q 'q' key
R 'r' key
S 's' key
T 't' key
U 'u' key
V 'v' key
W 'w' key
X 'x' key
Y 'y' key
Z 'z' key
Numlock Numlock key
CapsLock Capslock key
ScrollLock Scroll lock key
RightShift Right shift key
LeftShift Left shift key
RightControl Right Control key
LeftControl Left Control key
RightAlt Right Alt key
LeftAlt Left Alt key
LeftApple Left Apple key
LeftWindows Left Windows key
RightApple Right Apple key
RightWindows Right Windows key
AltGr Alt Gr key
Help Help key
Print Print key
SysReq Sys Req key
Break Break key
Menu Menu key
Mouse0 First (primary) mouse button
Mouse1 Second (secondary) mouse button
Mouse2 Third mouse button
Mouse3 Fourth mouse button
Mouse4 Fifth mouse button
Mouse5 Sixth mouse button
Mouse6 Seventh mouse button
JoystickButton0 Button 0 on any joystick
JoystickButton1 Button 1 on any joystick
JoystickButton2 Button 2 on any joystick
JoystickButton3 Button 3 on any joystick
JoystickButton4 Button 4 on any joystick
JoystickButton5 Button 5 on any joystick
JoystickButton6 Button 6 on any joystick
JoystickButton7 Button 7 on any joystick
JoystickButton8 Button 8 on any joystick
JoystickButton9 Button 9 on any joystick
JoystickButton10 Button 10 on any joystick
JoystickButton11 Button 11 on any joystick
JoystickButton12 Button 12 on any joystick
JoystickButton13 Button 13 on any joystick
JoystickButton14 Button 14 on any joystick
JoystickButton15 Button 15 on any joystick
JoystickButton16 Button 16 on any joystick
JoystickButton17 Button 17 on any joystick
JoystickButton18 Button 18 on any joystick
JoystickButton19 Button 19 on any joystick
Joystick1Button0 Button 0 on first joystick
Joystick1Button1 Button 1 on first joystick
Joystick1Button2 Button 2 on first joystick
Joystick1Button3 Button 3 on first joystick
Joystick1Button4 Button 4 on first joystick
Joystick1Button5 Button 5 on first joystick
Joystick1Button6 Button 6 on first joystick
Joystick1Button7 Button 7 on first joystick
Joystick1Button8 Button 8 on first joystick
Joystick1Button9 Button 9 on first joystick
Joystick1Button10 Button 10 on first joystick
Joystick1Button11 Button 11 on first joystick
Joystick1Button12 Button 12 on first joystick
Joystick1Button13 Button 13 on first joystick
Joystick1Button14 Button 14 on first joystick
Joystick1Button15 Button 15 on first joystick
Joystick1Button16 Button 16 on first joystick
Joystick1Button17 Button 17 on first joystick
Joystick1Button18 Button 18 on first joystick
Joystick1Button19 Button 19 on first joystick
Joystick2Button0 Button 0 on second joystick
Joystick2Button1 Button 1 on second joystick
Joystick2Button2 Button 2 on second joystick
Joystick2Button3 Button 3 on second joystick
Joystick2Button4 Button 4 on second joystick
Joystick2Button5 Button 5 on second joystick
Joystick2Button6 Button 6 on second joystick
Joystick2Button7 Button 7 on second joystick
Joystick2Button8 Button 8 on second joystick
Joystick2Button9 Button 9 on second joystick
Joystick2Button10 Button 10 on second joystick
Joystick2Button11 Button 11 on second joystick
Joystick2Button12 Button 12 on second joystick
Joystick2Button13 Button 13 on second joystick
Joystick2Button14 Button 14 on second joystick
Joystick2Button15 Button 15 on second joystick
Joystick2Button16 Button 16 on second joystick
Joystick2Button17 Button 17 on second joystick
Joystick2Button18 Button 18 on second joystick
Joystick2Button19 Button 19 on second joystick
Joystick3Button0 Button 0 on third joystick
Joystick3Button1 Button 1 on third joystick
Joystick3Button2 Button 2 on third joystick
Joystick3Button3 Button 3 on third joystick
Joystick3Button4 Button 4 on third joystick
Joystick3Button5 Button 5 on third joystick
Joystick3Button6 Button 6 on third joystick
Joystick3Button7 Button 7 on third joystick
Joystick3Button8 Button 8 on third joystick
Joystick3Button9 Button 9 on third joystick
Joystick3Button10 Button 10 on third joystick
Joystick3Button11 Button 11 on third joystick
Joystick3Button12 Button 12 on third joystick
Joystick3Button13 Button 13 on third joystick
Joystick3Button14 Button 14 on third joystick
Joystick3Button15 Button 15 on third joystick
Joystick3Button16 Button 16 on third joystick
Joystick3Button17 Button 17 on third joystick
Joystick3Button18 Button 18 on third joystick
Joystick3Button19 Button 19 on third joystick

102. CLASE INPUT (y IV)

GetKeyDown:

static function GetKeyDown (name : String) : boolean

Devuelve true durante el frame en que el usuario empieza a presionar la tecla identificada como name. Recordemos
llamarla dentro de la función Updata, ya que resetea su estado cada frame. No devuelve true hasta que el usuario suelta
y luego aprieta la tecla de nuevo (tal como hace por ejemplo GetButtonDown con respecto a GetButton).

static function GetKeyDown (key : KeyCode) : boolean

Devuelve true durante el frame en que el jugador empieza a presionar la tecla identificada por la key de tipo
enumeración KeyCode, que vimos en el capítulo anterior.
GetKeyUp:

static function GetKeyUp (name : String) : boolean


static function GetKeyUp (key : KeyCode) : boolean

Devuelve true durante el frame en que el jugador libera la tecla identificada por name.

GetJoystickNames:

static function GetJoystickNames () : string[]

Devuelve un array de strings describiendo los joysticks conectados. Esto puede ser útil en una configuranción de
entradas de pantalla de usuario. Así, en lugar de enseñar etiquetas como “joystick 1”, puedes mostrasr títulos más
personalizados.

GetMouseButton:

static function GetMouseButton (button : int) : boolean

Devuelve true si el botón indicado del ratón es apretado. La variable button es un int que representa 0 para el botón
izquierdo, 1 para el derecho y 2 para el central.

Por poner un ejemplo muy simple:


function Update() {
if(Input.GetMouseButton(0))
Debug.Log("presionado botón izquierdo.");
if(Input.GetMouseButton(1))
Debug.Log("presionado botón derecho.");
if(Input.GetMouseButton(2))
Debug.Log("presionado botón central.");
}

GetMouseButtonDown:

static function GetMouseButtonDown (button : int) : boolean

Devuelve true durante el frame en que el usuario aprieta el botón del ratón indicado. Debes llamar esta función dentro
de update, ya que el estado se resetea cada frame. No devolverá true hasta que el botón sea liberado y vuelto a pulsar
(recordemos de nuevo la diferencia de GetButtonDown con respecto a GetButton, para aplicarla también aquí).

GetMouseButtonUp:

static function GetMouseButtonUp (button : int) : boolean

Devuelve true durante el frame en que el usuario libera el botón del ratón indicado.

ResetInputAxes:

static function ResetInputAxes () : void


Resetea todos los inputs, con lo que todos los axes y botones retornan a 0. Esto puede ser util cuando se regenera al
jugador y no te interesa conservar ningún imput que proceda de alguna tecla que pudiera continuar presionando.

GetAccelerationEvent:

static function GetAccelerationEvent (index : int) : AccelerationEvent

Devuelve mediciones de aceleración que ocurrieron durante el último frame.

103. ESTRUCTURA BOUNDS


Como es fácil apreciar, Bounds no es una clase, sino una estructura. La diferencia, aunque a efectos prácticos una
clase es lo mismo que una estructura, es más de tipo semántico. Esto es, cuando nos topamos con una estructura, se
nos quiere informar de que la misma tiene un componente de complemento a una o varias clases que no suelen tener
las clases en sí.

La estructura bounds representa una caja de bordes con los ejes alineados. También denominada AABB (para abreviar
axis-aligned bounding box), es una caja alineada con los ejes de coordenadas globales que envuelve algún objeto.

Como la caja nunca rota respecto de los ejes, puede ser definida por su centro y extensiones, o alternativamente por
mínimo y máximo. (center, extents, min y max, en inglés)

La estructura Bounds se usa en Collider.bounds, Mesh.bounds y Renderer.bounds.

VARIABLES:

center:

var center : Vector3

El centro de la caja.

size:

var size : Vector3

El tamaño total de la caja. Es siempre dos veces más grande que las extensiones (extents)

extents:

var extents : Vector3

Las extensiones de la caja. Es siempre la mitad del tamaño (size)

min:

var min : Vector3

El punto mínimo de la caja. Es siempre igual a center – extents.

max:

var max : Vector3

El punto máximo de la caja. Siempre es igual a center+extents.

FUNCIONES:

static function Bounds (center : Vector3, size : Vector3) : Bounds


Crea una nueva caja de bordes con un centro dado y un tamaño total. Las extensiones deben ser la mitad del tamaño
dado.

SetMinMax:

function SetMinMax (min : Vector3, max : Vector3) : void

Establece los bordes en los valores mínimos y máximos de la caja. Usar esta función es más rápido que asignar min y
max de manera separada.

Encapsulate:
function Encapsulate (point : Vector3) : void
function Encapsulate (bounds : Bounds) : void

Incrementa la caja para incluir el punto que se pasa como parámetro (1er prototipo) o para incluir la nueva caja (2º
prototipo)

Expand:

function Expand (amount : float) : void


function Expand (amount : Vector3) : void

Expande la caja para incrementar su tamaño en una cantidad a lo largo de cada cara.

Intersects:

function Intersects (bounds : Bounds) : boolean

¿Hay alguna otra caja intersectando nuestra caja?

Contains:

function Contains (point : Vector3) : boolean

¿Está el punto point contenido en la caja?

sqrDistance:

function SqrDistance (point : Vector3) : float

El cuadrado de la distancia entre el punto point y la caja.

intersectRay:

function IntersectRay (ray : Ray) : boolean


¿El rayo ray intersecta esta caja?

Pongamos un ejemplo. Previamente ubicamos la esfera (que es el gameobject al que tenemos vinculado nuestro script)
en la posicion (2,0,2), y le añadimos de nuevo un componente Rigidbody.

Ahora reeditamos MiPrimerScript así:


var rayo : Ray = new Ray (Vector3.zero, Vector3.forward);;
function Update () {
Debug.DrawRay (Vector3.zero, Vector3.forward * 999, Color.green);
var bordes : Bounds = transform.collider.bounds;
if (bordes.IntersectRay (rayo))
Debug.Log("La caja tocó el rayo");
}
function FixedUpdate() {
rigidbody.AddForce(-Vector3.right*6*Time.deltaTime);
}

Creamos primero un rayo que surge en el centro global de la escena y se prolonga a lo largo del eje z positivo
(vulgarmente, hacia delante). Ya dentro de la función update dibujamos ese mismo rayo que ya tenemos descrito, para
que visualmente podamos seguir lo que está pasando. Acto seguido creamos una instancia de la estructura Bounds que
en un alarde de originalidad llamaremos bordes, y la inicializaremos con la caja de bordes del collider de nuestro
gameobject (la caja que envuelve el collider, ojo, no confundir con el collider mismo. En este caso el collider tiene forma
de escena y el Bounds de la esfera sigue teniendo forma de caja). Dejamos establecido que si la caja toca el rayo se
mostrará un mensaje en pantalla, así que sólo nos queda darle movimiento a la esfera para que cuando intersecte el
rayo (que aquí hemos dibujado a través de DrawRay, pero que podríamos haber dejado invisible) se muestre dicho
mensaje.

104. CLASE COLLISION

Clase que obtiene diferente información de una colisión.

La información de la collision es pasada a los eventos Collider.OnCollisionEnter, Collider.OnCollisionStay y


Collider.OnCollisionExit.

VARIABLES:

relativeVelocity:

Variable de sólo lectura que devuelve la velocidad lineal relativa de los dos objetos que colisionan.

Para realizar un ejemplo, recoloquemos antes la esfera en (2,0,0). Luego escribamos nuestro script:
function OnCollisionEnter(colision : Collision) {
var velCol: Vector3 = colision.relativeVelocity;
Debug.Log(velCol.magnitude);
}
function FixedUpdate(){
rigidbody.AddForce(Vector3(-4,0,0));
}

Expliquemos el script de abajo arriba. En la función FixedUpdate lo único que hacemos es aplicar una fuerza de cuatro
unidades hacia la izquierda a la esfera, forzándola a colisionar con el cubo. En el momento en que la esfera topa con el
cubo, los datos de la colisión son almacenados en el parámetro colision. Uno de dichos datos es, como estamos
estudiando, la velocidad relativa a la que aquélla se produce, velocidad que almacenamos en la variable velCol, de tipo
Vector3. Ya sólo nos queda imprimir la fuerza o longitud de dicho vector, que como recordaremos se hace a través de la
variable magnitude.

Pulsamos play y observamos que la magnitud de la colisión es de algo menos de 4 (fruto de descontar el rozamiento y
la resistencia respecto de la fuerza aplicada)

rigidbody:

var rigidbody : Rigidbody

Variable de sólo lectura que hace referencia al rigidbody que golpeamos. Es nulo si el objeto que golpeamos es un
collider sin rigidbody vinculado.

Es fácil de ilustrar con un ejemplo:


function OnCollisionEnter(colision : Collision) {
if(colision.rigidbody){
Debug.Log("He topado con el rigidbody del objeto " + colision.rigidbody.name);
}
}
function FixedUpdate(){
rigidbody.AddForce(Vector3(-4,0,0));
}

No hay mucho que explicar. Si el collider con el que topa nuestra esfera tiene un rigidbody, se nos imprime un texto que
además incluye el nombre del objeto al que dicho rigidbody pertenece.

collider:

var collider : Collider

El collider que golpeamos (sólo lectura).

Para determinar la parte concreta que golpeamos de cada collider deberemos iterar a través de los puntos de contacto
con la propiedad contacts.

transform:

var transform : Transform

El transform del objeto que golpeamos (read only). Si chocamos contra un collider con un rigidbody, el transform será el
vinculado al rigidbody. Si colisionamos contra un collider sin rigidbody, el transform será el vinculado al collider.
gameObject:

var gameObject : GameObject

Variable de sólo lectura que devuelve el objeto con el que chocamos.

contacts:

var contacts : ContactPoint[]

El punto de contacto generado por el engine de físicas.

Cada contacto implica un punto de contacto, un normal y dos colliders que colisionan (ver ContactPoint). A través de
OnCollisionStay o OnCollisionEnter puedes siempre estar seguro de que el contacto tiene al menos un elemento.

ContactPoint es una estructura que tiene estas variables:

1) point: el punto de contacto. Por ejemplo:


function OnCollisionEnter(colision : Collision) {
if(colision.relativeVelocity.magnitude > 2){
print("Puntos de colision: " + colision.contacts.Length);
print("Primer punto de colision: " + colision.contacts[0].point);
}
}
function FixedUpdate(){
rigidbody.AddForce(Vector3(-8,0,0));
}

Una breve explicación del script: Como en el anterior aplicamos una fuerza a la esfera que la obliga a colisionar con el
cubo. Si dicha colisión tiene una fuerza superior a 2 unidades se imprimen dos mensajes: uno que indica el número de
colisiones que se ha producido y otro el punto exacto de la primera colisión. Si no hubiéramos establecido una fuerza
mínima del contacto para que aparecieran los mensajes, el propio contacto de la esfera con el suelo habría disparado
dichos mensajes. Pulsamos play y veremos uno de los mensajes, y si hacemos click sobre dicho mensaje para que nos
aparezca el popup de la consola, encontraremos el segundo, tal como muestro en esta captura:
2) normal: El normal del punto de contacto.

3) thisCollider: El primer collider en contacto, o sea, el vinculado al script.


function OnCollisionEnter(colision : Collision) {
if (colision.relativeVelocity.magnitude > 2){
print("This collider is named: " + colision.contacts[0].thisCollider.name);
}
}
function FixedUpdate(){
rigidbody.AddForce(Vector3(-8,0,0));
}

En este sencillo script observamos que thisCollider se corresponde a la esfera.

4) otherCollider: El otro collider en contacto. Para comprobar que hace referencia al cubo, meramente sustituir en el
script anterior thisCollider por otherCollider.

105. CLASE CONTROLLERCOLLIDERHIT

ControllerColliderHit es usado por CharacterController.OnControllerColliderHit para dar información detallada sobre la


colisión y como trabajar con ella.

VARIABLES:

controller:

var controller : CharacterController

El controller que golpea el collider.

un simple ejemplo, para el cual con la esfera seleccionada vamos a menú=>Component=>Physics=>Character


Controller, y reemplazamos la Sphere Collider. Luego escribimos el siguiente script:
function OnControllerColliderHit(colision : ControllerColliderHit) {
Debug.Log(colision.controller.name);
}
function FixedUpdate(){
collider.Move(Vector3(-8,0,0));
}
Poco que explicar. Movemos la esfera hacia el cubo (notad que sustituimos la función AddForce por Move, ya que ahora
nuestra esfera tiene un character controller. Una vez impacta con el cubo, se imprime el nombre del controller (la esfera)

collider:

var collider : Collider

El collider que fue golpeado por el controller. Para verlo en un ejemplo sustituyamos "colision.controller.name" por
"colision.collider.name".

rigidbody:

var rigidbody : Rigidbody

El rigidbody que fue golpeado por el controller. Es null si no se toca un rigidbody sino un collider estático.

gameObject:

var gameObject : GameObject

El game object que fue golpeado por el controller.

transform:

var transform : Transform

El transform que fue golpeado por el controller.

point:

var point : Vector3

El punto de impacto en coordenadas globales.

normal:

var normal : Vector3

El normal de la superficie con la que colisionamos en coordenadas globales.

moveDirection:

var moveDirection : Vector3

Aproximadamente la dirección desde el centro de la cápsula al punto que tocamos. Esto puede ser útil para encontrar
una dirección razonable para aplicar fuerzas a rigidbodies golpeados.
Por poner un ejemplo muy básico:
function OnControllerColliderHit(colision : ControllerColliderHit) {
colision.rigidbody.AddForce(colision.moveDirection * 15);
Debug.Log(colision.moveDirection);
}
function FixedUpdate(){
collider.Move(Vector3(-2,0,0));
}

Cuando el cubo recibe el impacto de la esfera, a su vez al rigidbody vinculado al cubo le aplicamos una fuerza en la
misma dirección del impacto, multiplicada por quince. Paralelamente imprimimos la dirección desde el centro de la
cápsula de nuestro controller al punto de contacto.

moveLenght:

var moveLength : float

Indica lo lejos que el character controller ha viajado hasta golpear al collider. Note que esto puede ser diferente de lo
que pasas a CharacterController.Move, porque todos los movimientos están constreñidos por colisiones.

106. CLASE DEBUG

Clase que contiene métodos para desbugear con facilidad mientras se desarrolla un juego:

VARIABLES DE CLASE:

isDebugBuild:

static var isDebugBuild : boolean

En el diálogo Build Settings, que podemos encontrar en el menú=>File, hay un check box llamado “Development Build”.
Si dicho check box está marcado, entonces isDebugBuild será true.

FUNCIONES DE CLASE:

DrawLine:

static function DrawLine (start : Vector3, end : Vector3, color : Color = Color.white, duration : float = 0.0f) : void
Dibuja una lidea desde el punto que le pasemos como parámetro start hasta el establecido como end con el color que le
establezcamos como tercer parámetro y durante un tiempo fijado en duration. Si la duración es 0 entonces la linea es
dibujada un frame.

La linea será dibujada en la vista de escena del editor. Si en la ventana Game está habilitada la opcion Gizmos, también
se mostrará ahí la línea.

Un pequeño ejemplo:
function Update () {
Debug.DrawLine (Vector3.zero, transform.position, Color.red);
}

Estamos dibujando una línea que empezaría en el centro de la escena y que iría hasta el centro del objeto que tiene
vinculado el script, esto es, la esfera. La línea sería de color rojo y se dibuja cada frame, coincidiendo con las veces que
es llamada la función Update. Si queremos ver también la línea en la vista Game, recordemos activar el botón Gizmos.

DrawRay:

static function DrawRay (start : Vector3, dir : Vector3, color : Color = Color.white, duration : float = 0.0f) : void

Dibuja una línea desde start a start+dir con el color que especifiquemos por una duración de tiempo también
establecida. Si duración es 0 entonces la línea es dibujada un frame.

Para ver las diferencias con la función anterior, lo mejor será realizar el mismo ejemplo con la nueva función. Así:
function Update () {
Debug.DrawRay (Vector3.zero, transform.position*10, Color.red);
}

Vemos que aquí el segundo parámetro no es el destino final del rayo, sino la dirección y distancia de éste. En este caso
veremos una línea/rayo de 10 metros saliendo desde el centro de la escena.

Break:

static function Break () : void

Pausa el editor. Esto es útil cuando queremos comprobar ciertos valores en el inspector y no somos capaces de
pausarlo manualmente.

Log:

static function Log (message : object) : void

Anota mensajes en la consola de Unity:


Debug.Log("Hola, mundo");

static function Log (message : object, context : Object) : void

Cuando seleccionas en mensaje en la consola se dibuja una conexión con el objeto contextual. Esto es muy útil si
queremos saber qué errores ocurren en un objeto. Me explico. Escribimos esto en MiPrimerScript (que recordemos que
tenemos vinculado a la esfera):
Debug.Log ("Hola, mundo", gameObject);

Al darle al play, tal como podemos suponer, aparece el mensaje impreso en la consola. Pero en este caso, si hacemos
click sobre ese mensaje, observaremos que en la jerarquia automáticamente se nos selecciona la esfera durante unos
segundos.

LogError:

static function LogError (message : object) : void

Una variable de Debug.Log que anota un mensaje de error en la consola (en color rojo)
Debug.LogError("Esto es un error");

static function LogError (message : object, context : Object) : void

Variante similar a la que tratábamos antes para Log.

LogWarning:

static function LogWarning (message : object) : void

Una variante de Debug.Log que muestra un mensaje de aviso en la pantalla (en color amarillo)

static function LogWarning (message : object, context : Object) : void

Mensaje vinculado a un determinado objeto, como en los dos casos anteriores.

107. CLASE EVENT (I)

Tipos de teclas modificadores que pueden estar activos durante un proceso de pulsación de tecla.

Los eventos pueden corresponder a entradas de usuario (teclas presionadas, acción de ratón) o bien ser distribuciones
(layers) de unityGUI o eventos de renderizado. Asi como OnGUI es potencialmente llamado multiples veces por frame.
Event.current corresponde al actual evento dentro de la llamada a OnGUI.

VARIABLES:

type:

var type : EventType

Variable que indica el tipo de evento. Es de tipo EventType, que es una enumeración que permite los siguientes
valores:
MouseDown Un botón del ratón ha sido presionado.
MouseUp Un botón del ratón ha sido liberado.
MouseMove El ratón se ha movido (sólo en vista de edición).
MouseDrag El ratón fue arrastrado.
KeyDown Una tecla del teclado fue presionada.
KeyUp Una tecla del teclado fue liberada.
ScrollWheel La rueda del ratón se movió.
Repaint Un evento de repintado. Se envía uno cada frame.
Layout Un evento de distribución.
DragUpdated Solo en editor: operación de drag & drop actualizada.
DragPerform Sólo en editor: operación de drag & drop realizada.
DragExited Sólo en editor: operacion de drag & drop finalizada.
Ignore el evento debe ser ignorado.
Used Evento ya procesado.
ValidateCommand Valida un comando especial (p.ej. copy & paste)
ExecuteCommand Ejecuta un comando especial (p.ej. copy & paste)
ContextClick El usuario ha hecho click con el botón derecho.

Pongamos un breve ejemplo:


function OnGUI () {
Debug.Log("Current event detected: " + Event.current.type);
}

Este script meramente imprime el evento que está teniendo lugar cada frame. Prueba, tras pulsar play, a hacer click con
diferentes botones del ratón, pulsa distintas teclas, mueve el ratón, etc. Luego detén el reproductor y haz click sobre el
último mensaje de la consola para que te aparezca el pop up. Verás una lista enorme de eventos que han tenido lugar,
la mayoría de repintado y distribución, pero entre ellos estarán los que hayas provocado durante ese tiempo.

mousePosition:

var mousePosition : Vector2

La posición del ratón. Variable usada en los eventos EventType.MouseMove y EventType.MouseDrag.

delta:

var delta : Vector2

El movimiento relativo del ratón comparado con el último evento.

button:

var button : int

Qué botón del ratón ha sido presionado.

Apliquemos un ejemplo:
function OnGUI() {
var miEvento : Event = Event.current;
if(miEvento.button == 0 && miEvento.isMouse){
Debug.Log("Botón izquierdo");
} else if(miEvento.button == 1) {
Debug.Log("Botón derecho");
} else if (miEvento.button == 2) {
Debug.Log("Botón del centro");
}
}

Creamos primero una instancia de la clase Event, que contendrá el evento actual. Si dicho evento es un evento de ratón
(isMouse, que estudiaremos de aquí a un momento) y button vale cero (esto es, button vale cero pese a haber un
evento de ratón, ya que si no hay evento de ratón button también vale cero, no sé si me estoy explicando)
se imprime un mensaje, y así sucesivamente (con valores de button en uno y dos no hace falta descartar la posibilidad
de que no se esté produciendo un evento de ratón por lo anteriormente explicado.

modifiers:

var modifiers : EventModifiers

Qué tecla modificadora está siendo pulsada( ahift, ctrl, alt...)


function OnGUI() {
var miEvento : Event = Event.current;
Debug.Log(miEvento.modifiers);
}

Probad a pulsar las teclas modificadoras y las veréis impresas, a diferencia de las que no tienen esa condición.

104. CLASE EVENT (II)

clickCount:

var clickCount : int

Cuántos clicks de ratón consecutivos hemos recibido.

Es usado en el evento EventType.MouseDown. Usadlo para diferenciar entre un click único y un doble click.

Un ejemplo:
private var numeroClicks : int = 0;
function OnGUI() {
var miEvento : Event = Event.current;
if (miEvento.isMouse) {
numeroClicks +=miEvento.clickCount;
Debug.Log("Mouse clicks: " + numeroClicks);
}
}

Si lo probamos, vemos que tenemos un contador de clicks que contabiliza cada actividad (down y up) del botón del
ratón desde el inicio del juego. Es una adaptación del script que está en el manual de referencia. Si os fijáis, declaramos
la variable numeroClicks fuera de la función onGUI, para que no nos contabilice (como hace en el manual de referencia)
los clicks de cada frame, sino los totales. Por lo demás, el script no tiene mucho misterio: inicializamos una variable de
tipo Event con el evento actual, nos aseguramos de que el evento tenga que ver con el ratón y pasamos a contar clicks.

character:

var character : char

El tipo de caracter.
function OnGUI() {
var miEvento : Event = Event.current;
if (miEvento.isKey) {
Debug.Log("Pulsado caracter: " + miEvento.character);
}
}

commandName

var commandName : String

El nombre de un evento de tipo ExecuteCommand o Validate Command ("Copy", "Cut", "Paste", "Delete",
"FrameSelected", "Duplicate", "SelectAll", etc)

keyCode:

var keyCode : KeyCode

El key code para eventos de teclado. Usado en los eventos EventType.KeyDown y EventType.KeyUp; devuelve el valor
del KeyCode, por lo que se usa para manejar, por ejemplo, teclas de cursor, de funciones, etc.

Teclead este código y tras salvar y darle al play pulsad por ejemplo una de las flechas de desplazamiento del teclado:
function OnGUI() {
var miEvento : Event = Event.current;
if (miEvento.isKey) {
Debug.Log("El key code es: " + miEvento.keyCode);
}
}
shift:

var shift : boolean

¿Está shift pulsado? (sólo lectura)

control:

var control : boolean

¿Está control pulsado? (sólo lectura)

alt:

var alt : boolean

¿Está alt pulsado? (Sólo lectura)

capsLock:

var capsLock : boolean

¿Está el bloqueo de mayúsculas pulsado? (sólo lectura)

numeric:

var numeric : boolean

¿Se está presionando alguna tecla del teclado numérico) (sólo lectura)

functionKey:

var functionKey : boolean

¿Es la tecla presionada una tecla de función (alt, ctrl, shift, etc)? (Sólo lectura)

isKey:

var isKey : boolean

¿Es este evento un evento de teclado? (sólo lectura)

isMouse:
var isMouse : boolean

¿Es este evento un evento de ratón? (sólo lectura)

105. CLASE EVENT (y III)

FUNCIONES:

GetTypeFromControl:

function GetTypeForControl (controlID : int) : EventType

Esta función devuelve un tipo de evento que es filtrado para un determinado control cuya id pasamos como parámetro.
Esta función es usada para implementar bloqueos de ratón y de focos de teclado.

El id del control para el que requerimos el tipo de evento se obtiene de GUIUtilty.GetControlID (), y en EventType
podemos ver una lista de sus posibles valores.

Use:

function Use () : void

Evento ya utilizado. deberíamos llamar a este método cuando ya hemos usado un evento. El tipo de evento será
colocado en EventType.Used, causando que otros elementos GUI lo ignoren.

VARIABLES DE CLASE:

current:

static var current : Event

El evento actual/corriente que está siendo procesado en este mismo momento.

Un ejemplo:
function OnGUI() {
var miEvento : Event = Event.current;
if(miEvento.type != EventType.repaint && miEvento.type != EventType.layout){
Debug.Log("Current detected event: " + Event.current);
}
}

Salvamos y tras pulsar al play disparamos los eventos que deseemos. Detenemos el reproductor y accedemos a la
consola donde se muestran los mensajes haciendo click sobre el último y ahí tendremos toda la información sobre
teclas pulsadas, movimientos y clics del ratón, etc. Observaréis que descarté la impresión de eventos de tipo repaint y
layout, que son los que se producen de manera automática y en un número mayor.

106. CLASE GIZMOS

Los Gizmos son usados para permitir un debug visual o bien para colocar ayudas en la vista de escena.

Todos los gizmos deben ser dibujados o con la función OnDrawGizmos o con la función. OnDrawGizmosSelected. La
diferencia de ambas es que:

OnDrawGizmos es llamada cada frame.


OnDrawGizmosSelected es llamada sólo si el objeto al cual está vinculado el script es seleccionado.

VARIABLES DE CLASE:

color:

static var color : Color

Establece el color para los gizmos que serán dibujados a continuación.

Vamos a hacer un pequeño ejemplo. Aseguráos de que MiPrimerScript sigue estando vinculado a la esfera, y acto
seguido deseleccionar cualquier gameobject haciendo click en un espacio vacío de la Jerarquía. Comprobamos que en
la vista game tengamos marcada la pestaña Gizmos. Escribimos:
function OnDrawGizmosSelected () {
Gizmos.color = Color.blue;
var direction : Vector3 = transform.TransformDirection (Vector3.forward) * 5;
Gizmos.DrawRay (transform.position, direction);
}

Le damos al play y no parece ocurrir nada. Esto es porque estamos llamando a la función OnDrawGizmosSelected, que
sólo muestra el dibujo cuando seleccionamos el objeto al cual va vinculado el script, así que en la jerarquía
seleccionamos la esfera y automáticamente nos debería aparecer una línea de color azul que va desde la posición del
transform de la esfera cinco metros en adelante.

FUNCIONES DE CLASE:

DrawRay:

static function DrawRay (r : Ray) : void


static function DrawRay (from : Vector3, direction : Vector3) : void

Dibuja un rayo que empieza desde from hasta from + direction. Lo hemos visto en funcionamiento en el ejemplo anterior.

DrawWireSphere:
static function DrawWireSphere (center : Vector3, radius : float) : void

Dibuja una esfera de alambre con centro y radio.


var radio = 2.0;
function OnDrawGizmos() {
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere (transform.position, radio);
}

Meramente como apunte, aquí la esfera de alambre se ve tengamos o no seleccionado el game object esfera, porque la
hemos dibujado con la función OnDrawGizmos.

DrawSphere:

static function DrawSphere (center : Vector3, radius : float) : void

Dibuja una esfera sólida con centro y radio.

DrawWireCube:

static function DrawWireCube (center : Vector3, size : Vector3) : void

Dibuja una caja de alambre con centro y tamaño.

DrawCube:

static function DrawCube (center : Vector3, size : Vector3) : void

Dibuja una caja sólida con centro y tamaño.

DrawIcon:

static function DrawIcon (center : Vector3, name : String) : void

Dibuja un icono en posición global en la vista de escena. El icono deberá tener el mismo nombre que le asignamos al
parámetro name y estará ubicado en las coordenadas que le pasemos al parámetro center. El path del icono puede
encontrarse en la carpeta Assets/Gizmos.

Vamos por partes: Antes que nada necesitamos una imagen tipo icono. Yo para el ejemplo he usado ésta.

Renombramos a la imagen como "nave". Por otro lado, en el archivo donde estamos guardando estos ejemplos, dentro
de la carpeta assets, hemos de crear una carpeta llamada Gizmos, que es el path que Unity buscará para dar con los
iconos de este tipo. Luego arrastramos nuestra nave a la recién creada carpeta.

Y ahora editamos el script:


function OnDrawGizmos () {
Gizmos.DrawIcon (transform.position + Vector3(0,2,0), "nave.png");
}

Pulsamos el play, y dos metros por encima de nuestra esfera debería aparecernos la nave, tal que así:
DrawGUITexture:

static function DrawGUITexture (screenRect : Rect, texture : Texture, mat : Material = null) : void
static function DrawGUITexture (screenRect : Rect, texture : Texture, leftBorder : int, rightBorder : int, topBorder : int,
bottomBorder : int, mat : Material = null) : void

Dibuja una textura en coordenadas de pantalla. Es útil para backgrounds de GUI.

107. CLASE LIGHTMAPSETTINGS

Almacena los mapas de luces (lightmaps) de la escena.

Una escena puede tener varios lightmaps almacenados en ella, y suss componentes Renderer pueden usar esos
lightmaps. Esto hace posible usar el mismo material en múltiples objetos, mientras cada objeto puede referirse a
diferentes lightmaps o diferentes porciones del mismo lightmap.

VARIABLES DE CLASE:

lightmaps:

static var lightmaps : LightmapData[]

Es un array de tipo LightmapData que puede almacenar diferentes lightmaps. LightmapData es una clase con dos
variables:
lightmapFar: Lightmap que almacena la totalidad de la luz entrante.
lightmapNear: Lightmap que almacena sólo la luz indirecta entrante.

lightsmapMode:

static var lightmapsMode : LightmapsMode

Modo de renderizado de lightmaps. LightmapsMode es una enumeración con los siguientes dos valores:
Single: Modo de renderizado de lightmap tradicional.
Dual: Modo de renderizado de lightmap dual.

108. ESTRUCTURA MATHF (I)


Estructura que contiene una colección de funciones matemáticas que podemos usar para nuestros scripts.

VARIABLES DE CLASE:

PI:

static var PI : float

El famoso 3.141592. (sólo lectura)

Como pequeña anotación, observad que las variables de clase de Mathf comienzan por mayúscula, al contrario de las
variables de clase del resto de clases y funciones en Unity. Tenedlo presente, ya que es una fuente importante de
errores.

Infinity:

static var Infinity : float

Representación del infinito positivo (sólo lectura)

NegativeInfinity:

static var NegativeInfinity : float

Una representación del infinito negativo (sólo lectura)

Deg2Rad:

static var Deg2Rad : float

Conversión constante de grados a radianes (sólo lectura)

Rad2Deg:

static var Rad2Deg : float

Conversión constante de radianes a grados (sólo lectura)

Epsilon:

static var Epsilon : float

El valor más pequeño que un float puede tener diferente de cero (sólo lectura)

FUNCIONES DE CLASE:

Sin:
static function Sin (f : float) : float

Devuelve el seno del ángulo f en radianes.

Cos:

static function Cos (f : float) : float

Devuelve el coseno del ángulo f en radianes.

Tan:

static function Tan(f : float) : float

Devuelve la tangente del ángulo f en radianes.

Asin:

static function Asin (f : float) : float

Devuelve el arco seno de f menos el ángulo en radianes cuyo seno es f.

Acos:

static function Acos (f : float) : float

Devuelve el arco coseno de f menos el ángulo en radianes cuyo coseno es f.

Atan:

static function Atan (f : float) : float

Devuelve el arco tangente de f menos el ángulo en radianes cuya tangente es f.

Atan2:

static function Atan2 (y : float, x : float) : float

Devuelve el ángulo en radianes cuya tangente es y/x.

El valor retornado es el ángulo entre el eje X y un vector 2D que empieza en cero y acaba en (x,y)

Sqrt:

static function Sqrt (f : float) : float


Devuelve la raíz cuadrada de f.

Abs:

static function Abs (value : float) : float


static function Abs (value : int) : int

Devuelve el valor absoluto de value.

Min:

static function Min (a : float, b : float) : float


static function Min (params values : float[]) : float
static function Min (a : int, b : int) : int
static function Min (params values : int[]) : int

Devuelve el valor mínimo de dos o más valores dados.

Max:

static function Max (a : float, b : float) : float


static function Max (params values : float[]) : float
static function Max (a : int, b : int) : int
static function Max (params values : int[]) : int

Devuelve el valor máximo de dos o más valores.

Pow:

static function Pow (f : float, p : float) : float

Devuelve f elevado a la potencia p.

Exp:

static function Exp (power : float) : float

Devuelve la potencia natural de un determinado número.

Log:

static function Log (f : float, p : float) : float


static function Log (f : float) : float

Devuelve el logaritmo de un determinado número en una base especificada.

Log10:

static function Log10 (f : float) : float


Devuelve el logaritmo en base diez de un determinado número.

109. ESTRUCTURA MATHF (II)


Ceil:

static function Ceil (f : float) : float

Devuelve el integer más pequeño igual o mayor que f.

Floor:

static function Floor (f : float) : float

Devuelve el mayor integer igual o más pequeño que f.

Round:

static function Round (f : float) : float

Devuelve f redondeado al integer más cercano. Si el número acaba en .5 y queda entre dos integers, uno de los cuales
es par y el otro impar, se devolverá el numero par.

CeilToInt:

static function CeilToInt (f : float) : int

Devuelve el integer más pequeño igual o mayor que f.

FloorToInt:

static function FloorToInt (f : float) : int

Devuelve el mayor integer menor o igual que f.

RoundToInt:

static function RoundToInt (f : float) : int

Devuelve f rendondeada al integer más cercano. Si e número acaba en .5 y por lo tanto está a medio camino entre dos
integers, uno impar y el otro par, se devuelve el número par.

Sign:

static function Sign (f : float) : float

Devuelve el signo de f. Devuelve 1 si es positivo o cero, y -1 si f es negativo.

Clamp:
static function Clamp (value : float, min : float, max : float) : float
static function Clamp (value : int, min : int, max : int) : int

Restringe un valor entre un mínimo y un máximo, sean floats o ints.

Vamos a verlo con un ejemplo:


for(var x : int = 0; x <= 10; x++) {
var numeroFijado : int = Mathf.Clamp(x, 1, 5);
Debug.Log(numeroFijado);
}

Mediante un bucle for le pasamos como primer parámetro a la función Clamp números del 10 al diez. Si desplegamos la
consola tras probar este ejemplo veremos que cuando x vale 0, clamp devuelve el valor mínimo fijado (en este caso 1).
Lo mismo pasa cuando x vale más de 5.

Clamp01:

static function Clamp01 (value : float) : float

Fija un valor entre 0 y 1 y lo devuelve.

Lerp:

static function Lerp (from : float, to : float, t : float) : float

Interpola a hacia b pasando por t. t queda fijada entre 0 y 1. Cuando t = 0 devuelve from. Cuando t = 1 devuelve to.
Cuando t = 0.5 devuelve la media de a y b.

LerpAngle:

static function LerpAngle (a : float, b : float, t : float) : float

Lo mismo que lerp, pero asegurándonos de interpolar valores correctamente cuando dé un giro de 360 grados. Las
variables a y b representan grados.

Para ilustrar esta función y la anterior vamos a apañar un pequeño ejemplo. Sería interesante que eliminárais el script
vinculado a la esfera. Editamos:
var origen = -2.0;
var destino = 1.0;
var anguloInicial= 0.0;
var anguloFinal= 90.0;
function Update () {
transform.position = Vector3(Mathf.Lerp(origen, destino, Time.time * 0.1), 0, 0);
var angle : float = Mathf.LerpAngle(anguloInicial, anguloFinal, Time.time * 0.1);
transform.eulerAngles = Vector3(0, angle, 0);
}

Salvamos y vinculamos el script al cubo. A la función Lerp le pasamos el parámetro de situación inicial -2, que coincide
con su posición actual en el eje x, y le indicamos un destino en 1. Tradicionalmente como tercer parámetro se coloca
Time.time, que pasa de 0 a 1 en un segundo, pero como queremos que el trayecto sea más lento, multiplicamos
Time.time por 0.1, de tal manera que el trayecto total dure 10 segundos.

Otro tanto hacemos con el angulo inicial y final. Como resultado, el cubo se moverá tres metros en el eje X y girará 90
grados sobre el eje Y en diez segundos.

MoveTowards:

static function MoveTowards (current : float, target : float, maxDelta : float) : float

Mueve el valor que indicamos en el parámetro current hacia el que indicamos en target. Hasta aquí la función sería
parecida a Mathf.Lerp, pero aquí nos aseguramos de la que velocidad no exceda de maxDelta. Valores negativos para
maxDelta empuja el valor lejos del objetivo.

MoveTowardsAngle:

static function MoveTowardsAngle (current : float, target : float, maxDelta : float) : float

Lo mismo que MoveTowards pero estando seguros de que los valores se interpolarán correctamente cuando gire 360
grados.

La diferencia de MoveTowards y MoveTowardsAngle con respecto de Lerp y LerpAngle la veremos más fácilmente
rehaciendo el script anterior:
var origen = -2.0;
var destino = 1.0;
var minAngle = 0.0;
var maxAngle = 90.0;
function Update () {
transform.position = Vector3(Mathf.MoveTowards(origen, destino, Time.time * 1),
0,0);
var angle : float = Mathf.MoveTowardsAngle(minAngle, maxAngle, Time.time * 30);
transform.eulerAngles = Vector3(0, angle, 0);
}

La diferencia la tenemos que hallar en el tercer parámetro. En el caso de MoveTowards lo que le estamos pidiendo aquí
al cubo es que se mueva en el eje x de la posición -2 a la 1 (3 metros, o sea) a razón de un metro por segundo. Y Para
MoveTowardsAngle estamos indicándole al cubo que gire 90 grados a razón de 30 grados por segundo. De esta
manera, en 3 segundos el cubo debería haber completado ambos movimientos. Probadlo.

SmoothStep:

static function SmoothStep (from : float, to : float, t : float) : float

Interpola entre mínimo y máximo y facilita entrada y salida de los límites. Sería como Lerp, pero con un impulso inicial.
Si meramente sustituís em el último script MoveTowards por SmoothStep veréis a qué me refiero.

Approximately:

static function Approximately (a : float, b : float) : boolean

Compara si dos valores en punto flotante son similares. Debido a que los numeros en punto flotante son imprecisos no
es recomendable compararlos usando el operador == (podría no devolver true), y es mejor utilizar esta función.

110. ESTRUCTURA MATHF (y III)


SmoothDamp:

static function SmoothDamp (current : float, target : float, ref currentVelocity : float, smoothTime : float, maxSpeed : float
= Mathf.Infinity, deltaTime : float = Time.deltaTime) : float

Gradualmente cambia un valor hacia un objetivo en un determinado tiempo. La función se puede usar para suavizar la
transición de valores, colores, posiciones, escalares….

Cuenta con los siguientes parámetros:


current La posición actual.
target La posición que estamos tratando de alcanzar.
currentVelocity La velocidad actual. Este valor es modificado por la función cada
vez que la llamamos.
smoothTime Aproximadamente el tiempo que tardaremos en alcanzar el objetivo.
Un valor pequeño hará que allcancemos el objetivo más rápido.
maxSpeed Opcionalmente nos permite fijar la velocidad máxima.
deltaTime El tiempo desde la última llamada a esta función. Por defecto
Time.deltaTime.

Un ejemplo:
var objetivo : Transform;
var tiempoEmpleado = 3;
private var yVelocity =4.0;
function Update () {
var newPosition : float = Mathf.SmoothDamp(transform.position.x,
objetivo.position.x, yVelocity, tiempoEmpleado);
transform.position = Vector3(newPosition, transform.position.y,
transform.position.z);
}
Salvamos y arrastramos el cubo a la variable expuesta "objetivo". Lo que aquí estamos haciendo es usar la función
SmoothDamp para ir marcando la nueva posición de nuestro cubo. El punto de origen será la posición actual del cubo
(en el eje x), el destino la posición actual en dicho eje del transform que marcamos como objetivo, le establecemos un
tiempo para que el origen alcance al objetivo de 3 segundos y le limitamos la velocidad máxima que pueda alcanzar
nuestro cubo a cuatro metros por segundo.

SmoothDampAngle:

static function SmoothDampAngle (current : float, target : float, ref currentVelocity : float, smoothTime : float, maxSpeed :
float = Mathf.Infinity, deltaTime : float = Time.deltaTime) : float

Cambia gradualmente un ángulo dado en grados hacia el ángulo que constituye el objetivo en un tiempo determinado.
El uso más común de esta función es para suavizar una cámara que esté siguiendo algún personaje o escena.

Repeat:

static function Repeat (t : float, length : float) : float

Introduce en un bucle el valor t, de tal manera que nunca sea más grande que length y nunca más pequeño que 0.

PingPong:

static function PingPong (t : float, length : float) : float

Hace rebotar el valor t, de tal manera que nunca sea mayor que length y nunca mayor que 0. El valor retornado se
moverá atrás y adelante entre 0 y length.

InverseLerp:

static function InverseLerp (from : float, to : float, value : float) : float

Calcula el parámetro Lerp entre dos valores.

ClosestPowerOfTwo:

static function ClosestPowerOfTwo (value : int) : int

Retorna la potencia de dos más cercana.

IsPowerOfTwo:

static function IsPowerOfTwo (value : int) : boolean

Devuelve true si el valor es potencia de dos


NextPowerOfTwo:

static function NextPowerOfTwo (value : int) : int

Devuelve el valor de la siguiente potencia de dos.

DeltaAngle:

static function DeltaAngle (current : float, target : float) : float

Calcula la diferencia más corta entre dos ángulos dados.

111. CLASE PHYSICS (I)

Componen esta clase, al igual que la estructura Mathf anterior, propiedades globales y métodos de ayuda relacionados
con las físicas.

VARIABLES DE CLASE:

gravity:

static var gravity : Vector3

La gravedad aplicada a todos los rigidbodies en la escena. Puede ser desconectada para un rigidbody individual usando
su propiedad useGravity.

FUNCIONES DE CLASE:

Raycast:

static function Raycast (origin : Vector3, direction : Vector3, distance : float = Mathf.Infinity, layerMask : int =
kDefaultRaycastLayers) : boolean

Lanza un rayo contra todos los colliders en la escena. Devuelve true cuando el rayo intersecta algún collider.

Tiene los siguientes parámetros:


origin El punto inicial del rayo en coordenadas globales.
direction La dirección del rayo.
distance La longitud o fuerza del rayo.
layerMask Una máscara de distribución (Layer mask) que se usa para ignorar
selectivamente colliders cuando se proyecta un rayo.
Por ejemplo:
function Update () {
var derecha : Vector3 = transform.TransformDirection (Vector3.right);
if (Physics.Raycast (transform.position, derecha, 10)) {
print ("Hay algo a mi derecha");
}
}

Lo que hacemos aquí es primero tomar la dirección local de nuestro cubo y convertirla en dirección global, a través de la
función TransformDirection. Dicha dirección global la almacenamos en la variable "derecha". Acto seguido, imprimimos
un mensaje si desde la posición de nuestro cubo en una distancia no superior a diez metros hay a la derecha global del
mismo otro objeto.

static function Raycast (origin : Vector3, direction : Vector3, out hitInfo : RaycastHit, distance : float = Mathf.Infinity,
layerMask : int = kDefaultRaycastLayers) : boolean

Proyecta un rayo contra todos los colliders en la escena y devuelve información detallada sobre qué golpeó.

Los parámetros de este segundo prototipo de función son:


origin El punto de inicio del rayo en coordenadas globales.
direction La dirección del rayo.
distance La fuerza o longitud del rayo.
hitInfo Si se devuelve true, esta variable contendrá más información sobre
donde colisionó el collider.
layerMask Un layer mask usado para ignorar colliders selectivamente cuando se
proyecte un rayo.

static function Raycast (ray : Ray, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean
static function Raycast (ray : Ray, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask : int =
kDefaultRaycastLayers) : boolean

Son similares a las funciones anteriores, sólo que usando ray.origin y ray.direction en vez de origen y dirección como
sendos Vector3.

RaycastAll:

static function RaycastAll (ray : Ray, distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) :
RaycastHit[]
static function RaycastAll (origin : Vector3, direction : Vector3, distance : float = Mathf.Infinity, layermask : int =
kDefaultRaycastLayers) : RaycastHit[]

Lanza un rayo a través de la escena y devuelve todos los choques.

Vamos a adaptar un ejemplo del manual de referencia:


function Update () {
var choques : RaycastHit[];
choques = Physics.RaycastAll (transform.position, transform.right, 100.0);
for (var i = 0;i < choques.Length; i++) {
var miChoque : RaycastHit = choques[i];
var renderer = miChoque.collider.renderer;
if (renderer) {
renderer.material.shader = Shader.Find("Transparent/Diffuse");
renderer.material.color.a = 0.3;
}
}
}
Vamos paso a paso en la explicación. Primero declaramos una variable que contendrá un array de tipo RaycastHit, que
es precisamente lo que hemos visto que devuelve la función RaycastAll. La inicializamos con todas aquellas colisiones
que sufra nuestro rayo, el cual proyectamos desde el cubo 100 metros a la derecha.

Dado que puede haber más de una colisión, iteramos a través del array y el collider con el que se ha producido cada
colisión le es asignado temporalmente a la variable miChoque, a través de la cual lo volvemos semitransparente.

Si pulsamos play vemos que nuestra esfera se torna semiinvisible.

LineCast:

static function Linecast (start : Vector3, end : Vector3, layerMask : int = kDefaultRaycastLayers) : boolean

Devuelve true si hay algún collider intersectando la línea entre start y end.

static function Linecast (start : Vector3, end : Vector3, out hitInfo : RaycastHit, layerMask : int = kDefaultRaycastLayers) :
boolean

En este segundo prototipo, si se devuelve true, hitinfo contendrá más información sobre dónde colisionó el collider.

OverlapSphere:

static function OverlapSphere (position : Vector3, radius : float, layerMask : int = kAllLayers) : Collider[]

Devuelve un array con todos los colliders que toquen o estén dentro de la esfera cuya posición y radio pasamos como
parámetros.

112. CLASE PHYSICS (y II)

CapsuleCast:

static function CapsuleCast (point1 : Vector3, point2 : Vector3, radius : float, direction : Vector3, distance : float =
Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean
static function CapsuleCast (point1 : Vector3, point2 : Vector3, radius : float, direction : Vector3, out hitInfo : RaycastHit,
distance : float = Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean
Lanza una cápsula contra todos los colliders de la escena y devuelve información de contra qué ha chocado. Devuelve
true cuando el sweep (barrido) de la cápsula choca con algún collider.

Los parámetros de esta función son:


point1 El inicio de la cápsula.
point2 El fin de la cápsula.
radius El radio de la cápsula.
direction La dirección en la cual hace el barrido la cápsula.
hitInfo Si devuelve true, hitInfo contendrá más información sobre dónde golpeó
el collider.
distance La longitud del barrido.
layerMask Un Layer mask que se usa para ignorar selectivamente colliders cuando
se proyecte la cápsula.

La cápsula viene conformada por las dos esferas con radios alrededor del point1 y point2, que forman los dos finales de
la cápsula. Esto es útil cuando un Raycast no tiene suficiente precisión para lo que queremos hacer, como por ejemplo
asegurarnos de que un personaje podrá moverse a cualquier sitio sin colisionar con nada en el camino.

SphereCast:

static function SphereCast (origin : Vector3, radius : float, direction : Vector3, out hitInfo : RaycastHit, distance : float =
Mathf.Infinity, layerMask : int = kDefaultRaycastLayers) : boolean

Proyecta una esfera contra todos los colliders de la escena y proporciona información sobre los que colisionan.
Devuelve true si topa con alguno en su barrido.

Cuenta con estos parámetros:


origin El centro de la esfera al principio del barrido.
radius El radio de la esfera.
direction La dirección en la cual hace el barrido la esfera.
hitInfo Si la función devuelve true, esta variable contendrá más información
acerca de dónde el collider colisionó (Para más información ver:
RaycastHit).
distance La longitud del barrido.
layerMask Un Layer mask que se usa para ignorar selectivamente colliders cuando
se proyecta la cápsula.

Esta función es útil cuando un Raycast no nos da suficiente precisión, como por ejemplo en el caso en que queramos
averiguar si un objeto de un determinado tamaño, como un character, será capaz de de moverse a algún lado sin
colisionar con algo por el camino. En casos como estos es preferible usar la función SphereCast.

Hemos de tener presente, eso sí, que la SphereCast no funcionará contra aquellos colliders configurados como
triggers.

Probemos un ejemplo. Previamente hay que añadirle un character controller al cubo. Acto seguido tecleamos este
script:
function Update () {
var hit : RaycastHit;
var charCtrl : CharacterController = GetComponent(CharacterController);
var p1 : Vector3 = transform.position + charCtrl.center;

if (Physics.SphereCast (p1, charCtrl.height /2, transform.right, hit, 10)) {


Debug.Log("Hay un obstáculo a " + hit.distance + " metros");
}
}
Lo que hemos hecho aquí es, a grandes rasgos, que nuestro cubo efectúe un barrido hacia la derecha en busca de
obstáculos para un character con un radio equivalente a la mitad de la altura de nuestro character. Al topar con uno, se
muestra un mensaje en pantalla junto con información suplementaria, como en este caso la distancia a que se halla
dicho obstáculo. El ejemplo es un poco rupestre, pero nos permite intuir la utilidad de esta función para aplicársela a un
personaje.

static function SphereCast (ray : Ray, radius : float, distance : float Mathf.Infinity, layerMask : int =
kDefaultRaycastLayers) : boolean
static function SphereCast (ray : Ray, radius : float, out hitInfo : RaycastHit, distance : float = Mathf.Infinity, layerMask :
int = kDefaultRaycastLayers) : boolean

CapsuleCastAll:

static function CapsuleCastAll (point1 : Vector3, point2 : Vector3, radius : float, direction : Vector3, distance : float =
Mathf.Infinity, layermask : int = kDefaultRaycastLayers) : RaycastHit[]

Es como Physics.CapsuleCast, pero devolviendo todos los colliders que la cápsula intercepta en su barrido e
información sobre los mismos. Devuelve un array con todos esos colliders.

Parámetros:
point1 El principio de la cápsula.
point2 El final de la cápsula.
radius El radio de la cápsula.
direction La dirección en la cual efectúa el barrido la cápsula.
distance La longitud del barrido.
layerMask Un Layer mask que se usa para ignorar selectivamente algunos
colliders cuando se proyecta la cápsula
La cápsula es definida por las dos esferas con su radio alrededor de point1 y point2, que forman los dos extremos de la
cápsula. Son devueltos datos de todos los colliders contra los que nuestra cápsula proyectada choque en esa dirección.

Recordemos que esta función no funciona contra colliders configurados como triggers.

SphereCastAll:

static function SphereCastAll (origin : Vector3, radius : float, direction : Vector3, distance : float = Mathf.Infinity,
layerMask : int = kDefaultRaycastLayers) : RaycastHit[]

static function SphereCastAll (ray : Ray, radius : float, distance : float = Mathf.Infinity, layerMask : int =
kDefaultRaycastLayers) : RaycastHit[]

Como physics.SphereCast, pero devolviendo todos los colliders que colisionen.

CheckSphere:

static function CheckSphere (position : Vector3, radius : float, layerMask : int = kDefaultRaycastLayers) : boolean

Devuelve true si hay algún collider tocando la esfera definida por position y radius en coordenadas globales.

CheckCapsule:

static function CheckCapsule (start : Vector3, end : Vector3, radius : float, layermask : int = kDefaultRaycastLayers) :
boolean

Devuelve true si hay algún collider tocando la cápsula definida por el eje que va de start a end y que tiene el radio
radius, en coordenadas globales.

IgnoreCollision:

static function IgnoreCollision (collider1 : Collider, collider2 : Collider, ignore : boolean = true) : void

Hace que el sistema de detección de colisiones ignore todas las colisiones entre collider1 y collider2. Esto es muy útil
por ejemplo para que los proyectiles no colisionen con el objeto que los dispara.

Esta función tiene algunas limitaciones:

1.- No es persistente. Esto significa que el estado de ignorar colisión no será almacenado en el editor cuando se salve la
escena.
2.- Sólo puedes aplicar esta función a colliders en gameobjects activos. Cuando se desactiva el collider o el rigidbody
vinculado se pierde el estado de IgnoreCollision y tendrás que llamar a esta función otra vez.

IgnoreLayerCollision:

static function IgnoreLayerCollision (layer1 : int, layer2 : int, ignore : boolean = true) : void

Hace que el sistema de detección de colisiones ignore todas las colisiones entre cualquier collider en layer1 y otros en
layer2.

GetIgnoreLayerCollision:

static function GetIgnoreLayerCollision (layer1 : int, layer2 : int) : boolean

Booleano que indica si las colisiones entre layer1 y layer2 están siendo ignoradas.

113. ESTRUCTURA QUATERNION

Nuestros amigos los quaterniones son usados para representar rotaciones. Unity internamente usa Quaterniones para
representar todas las rotaciones.

Sin embargo, los quaterniones están basados en números complejos y no son fáciles de entender intuitivamente, así
que casi nunca accederemos o modificaremos los componentes individuales de un Quaternión (x,y,z,w). Lo que será
más habitual es que queramos meramente tomar rotaciones ya existentes (por ej desde un Transform) y usarlas para
construir nuevas rotaciones (por ej interpolando suavemente entre dos rotaciones).

Las funciones sobre Quaterniones que usaremos el 99% del tiempo (las otras funciones son para usos exóticos) son
Quaternion.LookRotation, Quaternion.Angle, Quaternion.Euler, Quaternion.Slerp, Quaternion.FromToRotation y
Quaternion.identity.

VARIABLES:

eulerAngles:

var eulerAngles : Vector3

Devuelve la representacion en ángulos euler de la rotacion.

FUNCIONES:

ToAngleAxis:

function ToAngleAxis (out angle : float, out axis : Vector3): void

Convierte una rotación en una representación de angle-axis (ángulo de eje)

SetFromToRotation:

function SetFromToRotation (fromDirection : Vector3, toDirection : Vector3) : void

Crea una rotación que rota desde fromDirection hasta toDirection.


ToString:

function ToString () : String


function ToString (format : String) : String

Devuelve un string formateado del Quaternion.

VARIABLES DE CLASE:

identity:

static var identity : Quaternion

Sería el equivalente a no rotation. El transform quedará perfectamente alineado con el mundo o con los ejes del padre.

FUNCIONES DE CLASE:

AngleAxis:

static function AngleAxis (angle : float, axis : Vector3) : Quaternion

Crea una rotación que rota los ángulos que le pasemos como primer parámetro con respecto al eje que le pasamos
como segundo.

O sea, para girar nuestro cubo (previamente eliminadle el character controller que le añadimos el capítulo anterior),
tecleamos:
transform.rotation = Quaternion.AngleAxis(30, Vector3.up);

De esta forma tan simple giramos el cubo 30 grados sobre el eje Y.

FromToRotation:

static function FromToRotation (fromDirection : Vector3, toDirection : Vector3) : Quaternion

Crea una rotación que rota desde el primer parámetro al segundo.

Normalmente usaremos esta función para rotar un transform uno de cuyos ejes sigue un objetivo en una dirección en
coordenadas globales.

Slerp:

static function Slerp (from : Quaternion, to : Quaternion, t : float) : Quaternion

Interpola esfériamente del primer al segundo parámetro durante el tercero.

RotateTowards:

static function RotateTowards (from : Quaternion, to : Quaternion, maxDegreesDelta : float) : Quaternion


Efectúa una rotación entre el primer parámetro y el segundo. Esto es esencialmente lo mismo que Quaternion.Slerp,
pero aquí la función se aregura de que la velocidad angular nunca exceda de la marcada en maxDegreesDelta. Si
maxDegreesDelta tiene un valor negativo la rotación es empujada lejos del segundo parámetro (to).

Angle:

static function Angle (a : Quaternion, b : Quaternion) : float

Devuelve el ángulo en grados entre dos rotaciones dadas.

Veamos eso:
var objetivo : Transform;
function Update () {
var angulo : float = Quaternion.Angle(transform.rotation, objetivo.rotation);
Debug.Log(angulo);
}

Arrastramos la esfera a la variable expuesta objetivo. Si pulsamos play, observaremos que nos aparece un cero en
pantalla, ya que (si teneis ambas figuras colocadas como yo) ambas tienen sus respectivas rotaciones a 0,0,0. Probad
ahora a cambiar la rotación de una -o las dos- figuras.

Euler:

static function Euler (x : float, y : float, z : float) : Quaternion


static function Euler (euler : Vector3) : Quaternion

Devuelve en quaterniones una rotación pasada como parámetro en grados euler.

114. CLASE RANDOM

Clase para generar números aleatorios.

VARIABLES DE CLASE:

seed:

static var seed : int

Coloca la semilla para el generador de números aleatorios.

value:

static var value : float


Devuelve un número aleatorio entre 0.0 (inclusive) y 1.0 (inclusive).
for(var x: int =0; x<10; x++){
print(Random.value);
}

Podréis comprobar que los diez números que nos aparecerán en pantalla están entre ambos valores.

insideUnitSphere:

static var insideUnitSphere : Vector3

Devuelve un punto aleatorio dentro de una esfera con radio 1.


transform.position = Random.insideUnitSphere * 2;

Este ejemplo situaría nuestro cubo en un punto aleatorio dentro de una esfera (3 dimensiones) con un radio de 2
unidades.

insideUnitCircle:

static var insideUnitCircle : Vector2

Devuelve un punto aleatorio dentro de un círculo con radio 1.


var newPosition : Vector2 = Random.insideUnitCircle * 5;
transform.position.x = newPosition.x;
transform.position.y = newPosition.y;

en este caso nuestro cubo se movería dentro de un círculo (2D) con radio de 5 unidades.

onUnitSphere:

static var onUnitSphere : Vector3

Devuelve un punto aleatorio sobre la superficie de una esfera con radio 1.


function FixedUpdate(){
rigidbody.velocity = Random.onUnitSphere * 10;
}

Esta función mueve al rigidbody de nuestro cubo a una velocidad de 10 en una dirección aleatoria, por lo que no
esperéis ver otra cosa al darle al play que un cubo volviéndose loco.

rotation:

static var rotation : Quaternion

Devuelve una rotación aleatoria (read only)


var prefab : GameObject;
Instantiate(prefab, Vector3.zero, Random.rotation);
Este ejemplo instanciaría un nuevo gameobject en el centro de la escena y con una rotación aleatoria.

FUNCIONES DE CLASE:

Range:

static function Range (min : float, max : float) : float

Devuelve un float aleatorio entre un min (inclusive) y max (inclusive).


var prefab : GameObject;
function Start () {
var position: Vector3 = Vector3(Random.Range(-5.0, 5.0), 0, Random.Range(-5.0, 5.0));
Instantiate(prefab, position, Quaternion.identity);
}

Si arrastramos la esfera a la variable expuesta prefab, al darle al play observaremos que se clona una instancia de la
misma y aparece en un lugar aleatorio en un margen de 5 metros en los ejes X y Z.

static function Range (min : int, max : int) : int

La misma función, pero admite y devuelve integers.

115. ESTRUCTURA RAY

Estructura que nos permite representar y modificar rayos. Un rayo es una linea infinita que empieza en un punto dado y
va en alguna dirección.

VARIABLES:

origin:

var origin : Vector3

El punto de origen del rayo.

direction:

var direction : Vector3

La dirección del rayo. La dirección es siempre un vector normalizado(1,0,0 o 0,1,0 o 0,0,1. Si asignamos un vector de
longitud distinta de la unidad, será normalizado.
FUNCIONES:

Ray:

static function Ray (origin : Vector3, direction : Vector3) : Ray

Crea un rayo que empieza en origin a lo largo de direction.


var ray = new Ray (transform.position, transform.forward);

En este ejemplo crearíamos un rayo que parte de la posición del transform al que está vinculado el script y que parte
hasta el infinito a través del eje Z.

GetPoint:

function GetPoint (distance : float) : Vector3

Devuelve un punto tantas unidades como le pasemos en el parámetro a lo largo del rayo.
var r : Ray;
print( r.GetPoint (10) );

Este ejemplo imprime un punto situado 10 unidades a lo largo del rayo.

ToString:

function ToString () : String


function ToString (format : String) : String

Devuelve un string formateado para este rayo.

116. ESTRUCTURA RAYCASTHIT


Estructura usada para obtener información de vuelta de un raycast (rayo proyectado).

VARIABLES:

point:

var point : Vector3

El punto de impacto en coordenadas globales donde el rayo golpea el collider

normal:

var normal : Vector3

El normal de la superficie que golpea el rayo.

baryentricCoordinate:

var barycentricCoordinate : Vector3

La coordenada baricéntrica del triángulo que golpeamos (baricentro = es un punto de una figura geométrica la recta que
pasa por el cual divide la figura en dos partes iguales. )

Esto nos permite interpolar cualquiera de los datos de los vértices a lo largo de los tres ejes.

distance:

var distance : float

La distancia desde el origen del rayo hasta el punto de impacto.

triangleIndex:

var triangleIndex : int

el índice del triángulo que ha sido golpeado. El índice del triángulo es sólo válido si el colider que lo golpea es un
MeshCollider.

textureCoord:

var textureCoord : Vector2

La coordenada de la textura UV en el punto de impacto. Esto puede ser usado para pinturas de textura 3d o impactos
de bala dibujados. Si el collider no es una mesh collider, retorna un Vector2 a cero.
textureCoord2:

var textureCoord2 : Vector2

Las coordenadas de la textura uv secundaria.

lightmapCoord:

var lightmapCoord : Vector2

La coordinada del lightmap de uv en el punto de impacto.

colider:

var collider : Collider

El collider que fue golpeado. Esta propiedad es nula si no se golpea nada y no-nula si golpeas algo.

rigidbody:

var rigidbody : Rigidbody

El rigidbody del collider que ha sido golpeado. Si el collider no está vinculado a un rigidbody es null.

transform:

var transform : Transform

El Transform del rigidbody o collider que ha sido golpeado.

También podría gustarte