viernes, 7 de junio de 2013

Crea tu juego indie - Lección 03 - Introducción a la programación con Unity y JavaScript (2)

Con la última lección empezamos a ver la programación de componentes para Unity, tratando los fundamentos de la programación orientada a objetos y las variables. En esta lección nos centraremos en las funciones y el control de flujo de ejecución, a través de condicionales y bucles.

Como parte práctica, comenzaremos a implementar un sencillo control de movimiento.

¡Dentro vídeo!
(Muy recomendado verlo a pantalla completa en HD)

¿Deseas saber más?

Funciones y métodos

Las funciones de clase/objeto (o métodos, como se las conoce en otras nomenclaturas), son "trozos de programa" asociados a un objeto o una clase.
Igual que con los programas, las funciones tienen un único punto de entrada, pueden recibir una serie de parámetros (que se especifican al declarar la función), y tendrán uno o varios puntos de salida. Pueden, además, haber especificado un tipo de retorno (un tipo de datos, o void cuando no se devuelve ningún valor). Si el tipo de retorno es distinto de void, deberán devolver explícitamente un valor para cada uno de los caminos que puede tomar con la palabra clave return, seguida de un valor del tipo indicado. Si el tipo de retorno es void, saldrá automáticamente al llegar al final de la función, aunque es posible salir de ella en cualquier punto usando return (sin especificar ningún valor).
La declaración de una función tiene esta sintaxis:
[visibilidad] function <identificador> ([<parámetro1>[ : <tipo1>][, <parámetro2>[ : <tipo2>][...]]]) : <tipoRetorno> {
  // Cuerpo de la función
}
(Las partes entre corchetes son opcionales. Entre ángulos van identificadores)
Algunos ejemplos de funciones:
// Función pública que no recibe parámetros ni devuelve un valor.
// Tan sólo muestra un mensaje y sale.
public function HelloWorld () : void {
  Debug.Log ("Hello World!");
}

// Función que recibe dos números enteros y devuelve su suma.
public function Add (num1 : int, num2 : int) : int {
  return num1 + num2;
}
También es posible "sobrecargar" las funciones. Una función sobrecargada es una función que se declara varias veces, usando el mismo identificador y tipo de retorno, pero variando sus parámetros. Por ejemplo:
// Función sobrecargada que recibe dos números enteros y devuelve su suma.
public function Add (num1 : int, num2 : int) : int {
  return num1 + num2;
}

// Función sobrecargada que recibe tres números enteros y devuelve su suma.
public function Add (num1 : int, num2 : int, num3 : int) : int {
  return num1 + num2 + num3;
}

public function DoSomething () {
  Debug.Log (Add (1, 2)); // Imprime "3"
  Debug.Log (Add (3, 5, 7)); // Imprime "15"
}

Funciones constructoras

Existe un tipo particular de funciones, conocidas como constructoras. Estas funciones se ejecutan durante el proceso de construcción de un nuevo objeto de una clase (instanciación)
La forma de declararlas tiene dos peculiaridades:
  • El nombre de la función es el identificador de la clase.
  • No pueden tener un tipo de retorno.
Si no se especifica ningún constructor, se añade uno implícitamente, conocido como "constructor por defecto":
public class MyClass {
  public function MyClass () {
  }
}
Sin embargo, es posible definir nuestros propios constructores (e incluso sobrecargarlos). Si declaramos explícitamente un constructor, el constructor por defecto no se añadirá.
public class MyClass {
  private var variable1 : int;
  public function MyClass (var1 : int) {
    variable1 = var1;
  }
}
...
public class MyOtherClass {
  public function DoSomething () {
    var myInstance1 : MyClass = new MyClass (10);
    // La variable variable1 de myInstance1 valdrá 10
    var myInstance2 : MyClass = new MyClass ();
    // ERROR: Ya no tiene un constructor por defecto, 
    // porque hemos declarado uno explícitamente.
  }
}

Variables y funciones de clase - El modificador "static"

Hasta ahora las variables y funciones que hemos creado son a nivel de objeto, de instancia. Cada una de estas variables puede tomar un valor distinto en cada instancia. Y si una de estas funciones modifica esas variables, las modifica en su instancia. Se necesita una instancia para acceder a esas variables e invocar esas funciones. Sin embargo, hay otras variables y funciones que no necesitan de una instancia, al estar compartidas por todas ellas. En vez de pertenecer a un objeto, pertenecen a toda su clase. En JavaScript y en C# se declaran utilizando el modificador static:
public class MyClass {
  public static var MyStaticVariable : int = 0;

  public static function MyStaticFunction () {
    Debug.Log ("¡Soy estático! XD");
  }
}
...
public class MyOtherClass {
  public function DoSomething () {
    // No se necesita una instancia para 
    // acceder a sus miembros estáticos
    Debug.Log (MyClass.MyStaticVariable);
    MyClass.MyStaticVariable = 5;
    Debug.Log (MyClass.MyStaticVariable);
    MyClass.MyStaticFunction ();
    // Se mostrará en el log de Unity:
    //   0
    //   5
    //   ¡Soy estático! XD
  }
}
Hay que tener cuidado, porque si tratamos una variable estática como no estática, al modificar su valor desde una instancia, estamos afectando a todas las demás instancias de esa clase.
public class MyClass {
  public static var MyStaticVar : int = 0;
}
...
public class MyOtherClass {
  public function DoSomething () {
    // Declaramos dos variables locales de tipo MyClass
    // Asignamos a cada una, una nueva instancia de MyClass
    var myInstance1 : MyClass = new MyClass ();
    var myInstance2 : MyClass = new MyClass ();

    Debug.Log ("Instancia 1 = " + myInstance1.MyStaticVar + ", Instancia 2 = " + myInstance2.MyStaticVar);
    // Salida: Instancia 1 = 0, Instancia 2 = 0
    myInstance1.MyStaticVar = 5;
    Debug.Log ("Instancia 1 = " + myInstance1.MyStaticVar + ", Instancia 2 = " + myInstance2.MyStaticVar);
    // Salida: Instancia 1 = 5, Instancia 2 = 5
  }
}

Condicionales

Las sentencias condicionales alteran el flujo normal del programa. Son una bifurcación, en la que una condición determina si una parte del programa se va a ejecutar o no. El tipo más básico es la sentencia if ("si..."), con la sintaxis que se muestra a continuación:
if (<condición>)
  <Condición cumplida>
[else 
  <Condición incumplida>]
La segunda parte (condición incumplida) es opcional. La condición es una expresión que resulta en un valor booleano. Se pueden combinar varias utilizando los operadores de la lección anterior. Los fragmentos dentro de la condición son o bien una única sentencia, o una secuencia de sentencias metidas entre llaves. Por ejemplo, en el trozo de código de debajo se han resaltado las sentencias que se ejecutarían dentro de sus respectivos condicionales:
if (a == 10)
  Debug.Log ("Dentro del condicional");
  Debug.Log ("Fuera del condicional");

if (b < 100)
{
  Debug.Log ("Dentro del condicional");
  Debug.Log ("También dentro del condicional");
}
Debug.Log ("Fuera del condicional");
Atentos, porque en el primer ejemplo, aunque el interior está indentado (sangrado con espacios), en JavaScript la indentación no delimita los bloques de código, sino que los delimita con llaves (por ejemplo, Python es un lenguaje que sí lo hace). Es posible anidar unas condiciones dentro de otras, para hacer bifurcaciones más complejas. Sin embargo, hay que tener cuidado y es aconsejable que si se va a hacer, se delimiten claramente los límites de cada una de las partes utilizando las llaves:
if (a == 10)
if (b < 100)
Debug.Log ("A");
else
Debug.Log ("B");
En este ejemplo (se ha eliminado la indentación deliberadamente), el condicional de la línea 2 está anidado dentro del condicional de la línea 1. Sin embargo, el else resaltado en la línea 4, ¿a cuál de los dos if corresponde? Cada lenguaje maneja esta situación de forma diferente, y por lo tanto es una buena costumbre el utilizar las llaves para evitar asumir un determinado funcionamiento al cambiar a un lenguaje nuevo. Hay una segunda forma condicional en JavaScript, algo más restrictiva pero algo más útil en algunos casos: Las sentencias switch/case. Estas sentencias se utilizan sobre expresiones con valores de tipos discretos (como pueden ser números enteros o enumeraciones, pero no referencias a objetos, ). Su sintaxis es:
switch (<expresión de valor discreto>) {
case <valor constante 1>:
  <Código si expresión == valor constante 1>
  break;
[case <valor constante 2>:
  <Código si expresión == valor constante 2>
  break;]
  ...
[default:
  <Código si expresión != todos los valores anteriores>]
}
Cada caso (salvo default) necesita cerrarse con la palabra clave break. default deberá ser el último caso de la ramificación. El funcionamiento es el siguiente: Primero evalúa la expresión y obtiene su valor. Después, comprueba, por cada case, si el valor de la expresión es igual al valor constante asociado a ese caso, y si es así, entra a ese bloque. Si no ha concordado con ninguno, y se ha definido el caso especial default, entra en esa parte. Si default tampoco se había definido, sale directamente.

Bucles

Otra forma de alterar el flujo normal de ejecución (secuencialmente) es a través de bucles. Un bucle es una sentencia que hace saltar la ejecución a una sentencia que ya se ha ejecutado antes bajo ciertas condiciones.
El bucle más básico es while ("mientras..."). Este bucle puede ejecutarse cero o más veces, y tiene esta sintaxis:
while (<Condición>)
  <Cuerpo del bucle>
Es similar a una sentencia if, solo que en este caso, una vez termina de ejecutar el cuerpo del bucle, vuelve otra vez a la primera línea del bucle, donde se ha definido la condición. La condición, como en if, es una condición de entrada en el bucle. El otro tipo de bucle básico es do/while. Se ejecutará una o más veces, y tiene esta sintaxis:
do
  <Cuerpo del bucle>
while (<Condición>)
La diferencia principal es que en este caso, se ejecutará como mínimo una vez, ya que la condición se comprueba después de haber ejecutado el código (es una condición de re-iteración del bucle). Una tercera forma es for, que es una forma abreviada y específica del bucle while
for (<Inicialización>; <Condición>; <Final de iteración>)
  <Cuerpo del bucle>

// Equivalente a
<Inicialización>
while (<Condición>) {
  <Cuerpo del bucle>
  <Final de iteración>
}
Hay que tener en cuenta que la equivalencia no es exacta, puesto que en el bucle for, si se quiere meter un cuerpo de bucle de más de una sentencia, deberán agruparse entre llaves manualmente. Existe una cuarta forma, foreach, aún más abreviada que for, sólo válida con tipos de datos enumerables. La veremos en algunas lecciones más adelante.

Descargas

Para descargar lo que llevamos de proyecto hasta ahora:
Descargar ARPG.rar (para descargar desde GDrive, ir a "Archivo -> Guardar como...")

No hay comentarios:

Publicar un comentario