Mostrando entradas con la etiqueta Principiante. Mostrar todas las entradas
Mostrando entradas con la etiqueta Principiante. Mostrar todas las entradas

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...")

lunes, 3 de junio de 2013

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

En la última lección ya vimos cómo funciona por encima el editor de Unity. En esta ocasión vamos a comenzar a crear nuestros propios componentes para que los GameObject se comporten como nosotros queramos.

Veremos una introducción a la variante de JavaScript que utiliza Unity, explicando de momento en qué consiste la programación orientada a objetos y qué son las variables.

Perdonad que sea tan tostona esta parte, ahora que aún estamos con los fundamentos y no hemos llegado a la lógica del juego. El próximo día cambiará eso, prometido. De momento, con leer esto y conocer (que no memorizar) cómo funciona, será suficiente. Siempre será posible usar estos posts como referencia ;)

Como en la primera lección, el vídeo estará complementado por una información más detallada y extensa en esta misma entrada, muy recomendada si eres nuevo/a en la programación.

(Muy recomendado verlo a pantalla completa en HD)

¿Deseas saber más?

Programación orientada a objetos

Como hemos visto en el vídeo, la programación orientada a objetos se basa en distribuir la lógica del programa entre varios objetos, instancias de clases (o estructuras), con miembros que pueden ser variables o funciones, además de otras clases, enumeraciones y otros tipos que veremos más adelante, cuando los vayamos a necesitar. Estas clases pueden estar heredando de otras clases. Cuando B hereda de A, se dice que B es subclase de A, y A es superclase de B. En el ejemplo hemos visto la declaración de la clase que hace implícitamente Unity si es necesario.
public class PlayerComponent extends MonoBehaviour {
// ...
}
La declaración consta de una parte básica:
class PlayerComponent {
// ...
}
Por una parte está la palabra clave class, que indica que lo que estamos declarando es una clase. A continuación iría el identificador de la clase.
Los identificadores tienen ciertas limitaciones:
  • Sólamente pueden comenzar por una letra (minúscula o mayúscula) o un guión bajo,
  • seguido de 0 o más letras, números, o guiones bajos.
  • Las letras no incluyen letras con acentos o especiales (por ejemplo, á, Ó, ü o ñ no están aceptados)
  • No pueden contener otros espacios, ni signos de puntuación, etc.
La palabra clave public antes de class indica que es una clase de visibilidad pública (hablaremos de la visibilidad un poco más abajo). La última parte, extends MonoBehaviour, indica que esta clase extiende (hereda de) MonoBehaviour. La sintaxis a utilizar es extends IdentificadorSuperclase. Una clase sólo puede tener una superclase, aunque puede tener varias subclases.

Las variables

Una variable no es más que un pequeño espacio de memoria asociado a un identificador. La forma más básica de declarar una variable en JavaScript es esta:
var Variable1;
La palabra clave var indica que vamos a declarar una variable, y a continuación le asociamos un identificador. Terminamos la sentencia con un punto y coma (;) JavaScript es un lenguaje débilmente tipado donde no es necesario especificar de qué tipo será la variable que estamos declarando. Esto es muy cómodo para hacer prototipos, porque a menudo cuando vamos a estar haciendo muchas pruebas y cambiando muchas cosas, tener que andar especificando un tipo diferente por cada cambio es bastante pesado. Sin embargo, es mucho más seguro especificar explícitamente un tipo, y ahorra bastantes quebraderos de cabeza cuando se pasa a fases más serias. Mi consejo es especificarlo siempre que sea posible, aunque eso será cosa de cada uno. Los puntos desde los que una variable será accesible se conocen como ámbito de la variable. Si es una variable de clase o de instancia, dependerá de la visibilidad (ver debajo). Si es una variable local definida dentro de una función u otro bloque (como veremos el próximo día), su ámbito es el bloque en el que se declara, desde el punto en el que se declara (aunque en JavaScript su ámbito es para el resto de la función, aunque hayamos salido de ese bloque).

La visibilidad

La visibilidad indica el ámbito de los miembros de una clase (o una estructura o un espacio de nombres, que no veremos aún). Existen cuatro visibilidades diferentes:
  • Pública (public): Accesible desde fuera de la clase/instancia.
  • Protegida (protected): Accesible desde la propia clase y sus subclases (y sus respectivas instancias, pero no entre las instancias).
  • Privada (private): Accesible sólo desde la propia clase y sus instancias (pero no entre ellas).
  • Interna (internal): Es un tipo especial del que no hablaremos aún.
Unity automáticamente reconoce las variables públicas de cada componente y permite manipularlas desde el inspector en el editor, aunque existen mecanismos para alterar esto.

El tipo de una variable

Cada variable tiene un tipo, aunque no se lo hayamos especificado explícitamente. Los tipos que puede tener una variable son los siguientes:
  • Primitivos: tipos básicos de variables.
    • int: Número entero.
    • uint: Número entero no-negativo (el cero y los números naturales).
    • float: Número decimal (de coma flotante).
    • boolean: Valor lógico booleano (puede ser únicamente verdadero o falso).
    • string: Cadena de caracteres.
    • Otros tipos primitivos más específicos.
  • Instancias de clases, estructuras...
  • Arrays: Son secuencias consecutivas de varias variables de un mismo tipo. Por ejemplo, una secuencia fija de 10 números enteros. Hablaremos de ellos en los próximos capítulos.

Operadores básicos

Para trabajar con variables se usan fundamentalmente los siguientes operadores:
  • Aritméticos: Operaciones matemáticas entre variables
    • Entre dos números
      • +: Suma de dos números
      • -: Resta de dos números
      • *: Multiplicación de dos números
      • /: División de dos números
      • %: Resto de la división entre dos números.
    • Con un sólo número
      • +: Como prefijo, es el propio número.
      • -: Como prefijo, es la negación del número (como multiplicar por -1)
    • Entre dos cadenas de caracteres
      • +: Concatena las cadenas (una cadena resultante de poner una a continuación de la otra)
  • Bit a bit: Son operadores para operaciones bit a bit entre variables enteras. De momento no entraremos con ellos.
  • Asignación: Asignan un valor a una variable (el valor puede ser una expresión constante o una expresión entre variables y constantes).
    • =: Asigna el valor de la derecha a la variable de la izquierda, si los tipos concuerdan o pueden convertirse a tipos compatibles.
    • +=, -=, *=, /=, %=: Son abreviaturas de "operación-asignación". Por ejemplo:
      a += 2; // a = a + 2;
      b *= 5; // b = b * 5;
  • Incremento/decremento de enteros:
    • ++: Incrementa en uno un entero. Si se usa como prefijo (preincremento), primero incrementa su valor y devuelve el valor incrementado. Si se usa como sufijo (postincremento), devuelve el valor original y luego incrementa la variable. Por ejemplo:
      a = 5;
      b = ++a; // a = 6, b = 6
      a = 5;
      b = a++; // a = 6, b = 5
    • --: Similar al incremento, pero decrementa en uno el valor.
  • Comparación: dan como resultado un valor lógico en función de la relación entre los dos operandos:
    • Entre dos valores cualesquiera
      • ==: Igualdad. Vale "verdadero" (true) si ambos operandos son iguales, o "falso" (false) si no.
      • !=: Desigualdad. Lo contrario que ==.
    • Entre dos números
      • <: Es verdadero si el primer operando es menor que el segundo.
      • <=: Menor o igual.
      • >: Mayor que.
      • >=: Mayor o igual que.
  • Lógicos: Operaciones entre dos valores lógicos.
    • ||: "O" lógico. Es verdadero si uno de los operandos lo es. Falso si los dos operandos son falsos.
    • &&: "Y" lógico. Es verdadero si los dos operandos son verdaderos. Falso si uno de los operandos lo es.
    • !: "No" lógico. Se usa como prefijo. Es verdadero si el operando es falso, y es falso si el operando es verdadero. En otras palabras, niega su valor.

Descargas

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

    lunes, 27 de mayo de 2013

    Crea tu juego indie - Lección 01 - Introducción a Unity

    En esta primera lección haré una pequeña introducción a Unity, para explicar algunos conceptos y el funcionamiento básico del motor, de manera que los que queráis ir "ensuciándoos las manos" y probando cosas (¡altamente recomendable!), podáis empezar ya mismo.

    Sin más dilación, aquí os dejo el vídeo de esta lección.
    (Muy recomendado verlo a pantalla completa en HD)

    ¿Deseas saber más?

    El sistema de coordenadas de Unity

    Unity utiliza un sistema de coordenadas de "mano izquerda". Se conoce así por la regla mnemotécnica usada para recordar "hacia dónde apunta Z". Si colocamos el dedo pulgar de la mano izquierda horizontalmente mirando hacia la derecha (X positiva), y el índice verticalmente, como haciendo una "L", apuntando hacia arriba (Y positiva), al colocar el dedo corazón perpendicular a ambos, esa será la dirección de la Z positiva. Creo que un pequeño dibujo lo hará más comprensible:
    Para no liaros mucho, recordad esta regla: 
    XYZ = RGB (rojo, verde, azul) = (pulgar, índice, corazón)
     Por cierto, en el vídeo hemos mencionado el concepto de sistema de coordenadas local y sistema de coordenadas global. Vamos a entrar un poco más en detalle en eso: Imaginad que partís en un viaje en coche. Pongamos que el punto de partida es el origen de coordenadas de la escena. En el sistema de coordenadas global (en el idioma que utiliza Unity se conoce como world space), vuestra posición y orientación es en relación a ese origen, la escena. Según vayáis avanzando en el trayecto, más os iréis alejando. En el sistema de coordenadas local (local space), vuestra posición no cambiaría, porque esa posición se refiere al coche (sois "locales al coche"). La analogía en el vídeo sería como si "la esfera está montada en el cubo, que es el coche". Hay otros muchos sistemas de coordenadas que iremos viendo más adelante con el tiempo (espacio de cámara, de textura...), cada uno con sus peculiaridades :)

    ¿Assets?

    Los assets (o activos en castellano) son los recursos que va a utilizar el proyecto. Aquí es donde van los modelos, las texturas, fuentes, sonidos, niveles... Se almacenan dentro de la subcarpeta Assets en el directorio del proyecto.
    Unity tiene una forma bastante inteligente de utilizar los assets, de tal manera que cuando se va a empaquetar el juego, determina cuáles de estos assets son utilizados y cuáles no, de manera que se descartará el resto para aligerar el paquete final (véase la carpeta Intermediate Assets de la que hablamos debajo). Aunque existe una excepción a esa regla, Resources, que veremos ahora.

    Los nombres de las carpetas

    Cuando ponemos a punto el proyecto al final del vídeo, creamos tres carpetas. Cada una tiene un propósito bien diferente:
    • Intermediate Assets: Esta carpeta es una costumbre mía, pero no tiene ninguna interpretación especial en Unity. La utilizo para almacenar assets que no irán en el juego pero que necesito poder utilizar en Unity para generar los assets definitivos que sí irán en él. Por ejemplo, sprites individuales que desde una herramienta en Unity voy a "empaquetar" en una única hoja de sprites.
    • Resources: Es una carpeta de assets especial para Unity. Los contenidos de esta carpeta se van a incluir íntegramente en el paquete final, independientemente de que sean utilizados o no. Se usa para assets que van a ser gestionados manualmente. Más adelante iremos viendo casos de assets que deben ir aquí.
    • Scripts: Es donde almaceno toda la parte de programación del proyecto. Personalmente no me gusta mezclarlos con el resto de assets porque su naturaleza es radicalmente diferente a la de los demás. En la siguiente lección la iremos rellenando ;)
    Hay una cuarta carpeta que no he mencionado en el vídeo al no ser parte de los assets del proyecto, pero que me gusta mantener dentro del proyecto: Original Assets. Esta carpeta va al mismo nivel que Assets dentro del proyecto, y es donde almaceno los originales (las imágenes en alta resolución del programa de dibujo, por ejemplo). Son datos en bruto de los assets, ni más ni menos.
    Estas carpetas las iremos subdividiendo según vayamos necesitando, pero está bien poder ir empezando la clasificación de antemano.

    El control de versiones por "meta-ficheros"

    Esto va bastante adelantado al momento en el que hablaremos de el control de versiones, aunque es lo primero que se debería hacer nada más crear el proyecto.
    En pocas palabras, un sistema de control de versiones va a permitirnos mantener un repositorio donde quedan almacenados los cambios que vamos haciendo al proyecto, de manera que podamos volver a una versión anterior, repartir el trabajo entre varias personas para luego reintegrarlo, y mucho más.
    Para que esta información pueda ser persistente entre equipos diferentes, Unity (en su versión gratuita) ofrece la posibilidad de generar un "meta-fichero" para cada asset, donde almacena datos internos de Unity hacia ese asset (por ejemplo, el identificador interno, en el caso de una textura su tamaño máximo, etc.). Lo único que hay que tener en cuenta es:
    • Al registrar cada asset en el sistema de control de versiones, hay que registrar ese asset y su correspondiente meta-fichero.
    • Si se van a hacer operaciones con el asset fuera del editor (renombrarlo o moverlo desde el explorador de archivos, por ejemplo), su meta-fichero debe ir emparejado, y con el mismo nombre. 
    Si el editor encontrase un asset sin su meta-fichero, generaría uno nuevo y podría haber conflictos.

    Descargas

    Por ahora no es que haya mucho, pero aquí está la estructura que debería tener el proyecto en este punto:
    Descargar ARPG.rar
    (para descargar desde GDrive, ir a "Archivo -> Guardar como...")