viernes, 28 de junio de 2013

Crea tu juego indie - Lección 05 - Movimiento del jugador

Entramos ya en el movimiento del personaje. En este ejemplo práctico, una implementación muy genérica de diferentes esquemas de control del personaje.

Es más que recomendable consultar la sección "¿Deseas saber más?", ya que hay bastante complejidad en algunos puntos. Como siempre, el código fuente estará disponible y libre para ser utilizado en vuestros juegos sin problemas. Pero si queréis hacer cambios o recortar y optimizar esta solución a vuestras necesidades, os ayudará saber bien cómo funciona por debajo.

(Muy recomendado verlo a pantalla completa en HD)

¿Deseas saber más?

El patrón de diseño Estrategia

Los patrones de diseño son soluciones bien conocidas y repetibles para problemas de diseño típicos. En este caso, se recurre a este patrón cuando se quiere desvincular el funcionamiento de una clase con la forma de llevar a cabo alguna (o algunas) operaciones.

En este patrón, se define una clase base que contiene todas las variables y métodos que se van a necesitar para utilizar esa estrategia, y luego se dan implementaciones particulares para esa estrategia. Cuando se va a usar la estrategia, se trata como si fuera el tipo genérico (la clase base), pero la implementación que se lanza por debajo es la del subtipo concreto. Algunos ejemplos:
  • Comprimir archivos:
    • Clase base: Compresor (incluye las funciones Comprimir y Descomprimir)
    • Subclases: CompresorZip, CompresorRar
  • Transferir archivos por red:
    • Clase base: ProtocoloDeTransferencia (incluye las funciones Subir y Descargar)
    • Subclases: TransferenciaHttp, Transferencia Https, TransferenciaFtp
Por ejemplo, utilizando el primer ejemplo para comprimir un archivo:
var comp : Compresor;
comp = new CompresorZip ();
// La instancia almacenada en comp es de tipo CompresorZip
// Ahora estaríamos llamando a CompresorZip.Comprimir()
comp.Comprimir (archivo); // Comprime archivo en un Zip

comp = new CompresorRar ();
// La instancia almacenada en comp es de tipo CompresorRar
// Ahora estaríamos llamando a CompresorRar.Comprimir()
comp.Comprimir (archivo); // Comprime archivo en un Rar
Como se puede ver el tratamiento que se hace de comp es siempre como si fuera de tipo Compresor, la clase base donde se definen las operaciones de la estrategia, pero como los tipos almacenados son subclases (estrategias concretas), se ejecuta la implementación específica de cada una. Como esas funciones vienen heredadas de la clase base, se puede asegurar (sin miedo a equivocarnos) que esas funciones van a existir en todos los subtipos. Esta es en mi opinión la mayor potencia de la herencia de tipos en la orientación a objetos.

Como podéis imaginar, es especialmente potente si además se pueden ir acoplando diferentes estrategias de forma externa. De hecho, así es como funcionan los plugins y los codecs de audio/vídeo: la aplicación que los va a usar expone una serie de funciones, y cada plugin/codec da su implementación concreta, a la que se accede según el plugin seleccionado en ese momento.

En nuestro caso, utilizamos este patrón en dos ocasiones diferentes: transformar la entrada a un espacio relativo (al avatar del jugador y a la cámara, concretamente), y aplicar esa entrada para dar movimiento al jugador. Con sólamente seleccionar una de cada, tenemos acceso a una de las cuatro combinaciones posibles. Añadiendo otra estrategia como, por ejemplo, un nuevo tipo de movimiento (scroll lateral para juegos de plataformas), serían seis combinaciones... Por eso, a pesar de ser algo más difícil de seguir, he preferido hacerlo así: están cubiertos casi todos los casos en los que el personaje se va a controlar usando un mando o el teclado, seleccionables desde dos desplegables en PlayerComponent ;)

Colecciones de datos

Las colecciones de datos son tipos especiales que almacenan varios datos de un mismo tipo.

La forma más básica de colecciones que incluyen casi todos los lenguajes de programación son los arrays. Un array es una variable que contiene no un dato de un tipo concreto, sino un lote estático de datos de ese tipo concreto. Es estático, porque una vez reservado el espacio para ese lote, éste no puede ni crecer ni disminuir. El símil que más cómodo me ha resultado siempre es ver un array como si fuera una cajonera. Se define el tipo (la forma del cajón) y al crear el array, se dice el número de elementos (número de cajones) o se le pasa una lista de elementos que tienen que caber, y se hace del tamaño apropiado para que entren todos justos. Ésto se ve muy bien con un ejemplo:
var arrayEnteros : int[];
// Se sabe que será una cajonera para 
// números enteros, pero no su tamaño.
arrayEnteros = new int[10];
// arrayEnteros es ahora una nueva cajonera, 
// vacía, y con capacidad para 10 números enteros.
arrayEnteros = new int[] {2, 3, 5, 7};
// arrayEnteros es un array de tamaño 4, y cada 
// elemento contiene uno de los cuatro 
// números primos menores de 10.
Para indicar que es un array en vez de una variable convencional, se añaden los dos corchetes [] justo después del tipo de elementos que va a contener (línea 1). Para crear el array se hace con el operador new, seguido del tipo y, o bien se especifica una capacidad entre los corchetes (línea 4), o bien se pasa una lista de elementos entre llaves y separados por comas (línea 7).

Pero, ¿cómo saber cuál de todos los valores dentro del array hay que cambiar? ¿O cuál queremos obtener? Se utiliza un operador conocido como operador de indexación. Justo después de la referencia que apunta al array, se pasa entre corchetes la posición del elemento al que queremos acceder:
var arrayEnteros : int[] = new int[3]; // {-,-,-}
arrayEnteros[0] = 23;  // {23,-,-}
arrayEnteros[1] = 57;  // {23,57,-}
arrayEnteros[2] = 911; // {23,57,911}

Debug.Log (arrayEnteros[1]); // Imprime "57"
Pero existen otro tipo de colecciones, de tamaño dinámico, que no necesariamente vienen de serie en cada lenguaje de programación. Son clases que "imitan" muchas de las funcionalidades de los arrays, pero ofrecen más versatilidad (aumentar o reducir su tamaño "al vuelo" añadiendo y quitando elementos, reordenarlos, hacer búsquedas de algún elemento...). En .NET las implementaciones básicas se encuentran en el espacio de nombres System.Collections, así que al principio del script hay que añadir import System.Collections; para poder acceder a estos tipos. Unity no es compatible con todos ellos, pero los más básicos están soportados sin problemas: List y Dictionary.

List es para listas de elementos. Se comporta esencialmente como un array, aunque puede variar su tamaño o reordenar los contenidos, entre otras cosas.
import System.Collections;
//...
var listaEnteros : List = new List (); // {}

listaEnteros.Add (23); // {23}
listaEnteros.Add (57); // {23,57}
Como son clases, la forma de crear las referencias a estas instancias se hace con el operador new, pero la sintaxis es diferente a la usada en los arrays.

Dictionary es para listas de pares asociados clave-valor. Las claves son únicas dentro de un mismo diccionario, pero no tienen por qué ser de tipo entero, ni guardar un orden.
import System.Collections;
//...
var diccionarioNumeros : Dictionary = new Dictionary (); // {}

diccionarioNumeros.Add (1, "Uno"); // {(1,"Uno")}
diccionarioNumeros.Add (4, "Cuatro"); // {(1,"Uno"),(4,"Cuatro")}
En un diccionario la versatilidad viene principalmente en que se puede acceder a los valores a través de la clave que llevan asociada, en vez de su posición dentro del diccionario.

Sin embargo, si os habéis fijado bien, en ningún momento hemos indicado ni el tipo que van a llevar los contenidos de la lista, ni de las claves y los valores en el diccionario. Eso es porque todos ellos se guardan como referencias al tipo genérico Object.
import System.Collections;
//...
var diccionarioNumeros : Dictionary = new Dictionary (); // {}

diccionarioNumeros.Add (1, "Uno"); // {(1,"Uno")}
// Si quisiéramos mostrar el valor de 1 en mayúsculas...
Debug.Log ((diccionarioNumeros[1] as String).ToUpper ());
Por estar utilizando estas versiones de las colecciones donde no es necesario especificar el tipo, cada vez que queramos utilizar alguno de sus contenidos, habrá que indicar explícitamente el tipo del contenido que hemos extraído (diccionarioNumeros[1] as String). Sin embargo y por suerte, también hay versiones de estas colecciones que hacen uso de la genericidad.

Genericidad

Hemos visto una de las desventajas de las colecciones básicas, ya que tener que indicar explícitamente el tipo que hemos extraído es tedioso y difícil de mantener. Pero además utilizar esas versiones es peligroso porque en ningún momento durante la compilación o la ejecución se comprueba que los tipos que se están introduciendo son válidos, y puede darnos errores muy difíciles de rastrear si creemos que el dato que estamos extrayendo es de un tipo pero en realidad es de otro. Sin ir más lejos, se pueden hacer este tipo de aberraciones:
import System.Collections;
//...
var diccionarioNumeros : Dictionary = new Dictionary (); // {}

diccionarioNumeros.Add (1, "Uno"); // {(1,"Uno")}
diccionarioNumeros.Add (4, 4); // {(1,"Uno"),(4,4)}
// Si quisiéramos mostrar el valor de 4 en mayúsculas...
Debug.Log ((diccionarioNumeros[4] as String).ToUpper ()); // ¡¡ERROR!!
// El valor asociado a 4 es 4, no una cadena,
// y ese error salta cuando se ejecuta esa línea, no cuando se compila.
Puede parecer poca cosa, o hasta versátil, pero se agradece mucho cuando el compilador te advierte de antemano cuando intentas meter un cuadrado en una colección donde sólo entran círculos.

La genericidad es una característica del lenguaje de programación que permite "parametrizar" las clases (no los valores de las instancias, sino la estructura de la clase en sí). Para ello, al definir una clase o una función, se especifica una serie de tipos parametrizables. Cuando queramos crear una referencia o nuevas instancias de estas clases, tenemos que indicar explícitamente los tipos que se han dejado parametrizables.

Aprovechando que .NET ofrece versiones parametrizables de List y Dictionary (en System.Collections.Generic), vamos a usarlas como ejemplo para ver cómo se trabaja con ellas, siguiendo el ejemplo anterior:
import System.Collections.Generic;
//...
var diccionarioNumeros : Dictionary.<int,String> = new Dictionary.<int,String> (); // {}

diccionarioNumeros.Add (1, "Uno"); // {(1,"Uno")}
diccionarioNumeros.Add (4, 4); // ¡¡ERROR!! (en tiempo de compilación)
// El compilador dirá que el valor tiene que ser un String, y que 4 es un int.
diccionarioNumeros.Add (4, "Cuatro"); // {(1,"Uno"),(4,"Cuatro")}
// Si quisiéramos mostrar el valor de 4 en mayúsculas...
Debug.Log (diccionarioNumeros[4].ToUpper ()); // Imprime "CUATRO".
Ahora con indicar los parámetros entre ángulos siguiendo ese formato tanto para declarar la variable como para instanciar un nuevo diccionario, tenemos una versión mucho más segura y cómoda de manejar de estas colecciones ;)

Proyecciones vectoriales

En este diagrama se ve cómo la proyección (v3) del vector v2 sobre v1 se calcula siguiendo la misma dirección que v1, pero el extremo de v3 es el punto más cercano desde el extremo de v2 a la recta directora de v1. Por expresarlo de otra forma más visual, si estuviésemos mirando perpendicularmente a v1, el vector v2 "proyectaría" sobre v1 una sombra: v3. De ahí el nombre proyección vectorial. En Unity se calcula fácilmente con la función Vector3.Project
v3 = Vector3.Project (v2, v1);

Una forma de definir un plano en matemáticas es a través de un punto contenido en el plano y un vector conocido como normal del plano. El plano es el conjunto infinito de rectas que pasan por ese punto y son perpendiculares al vector normal (expresado de otra forma, el vector normal es un vector perpendicular al plano). Luego si tenemos un plano (ignoraremos el punto y sólo nos centraremos en la orientación del plano), su vector normal n, y un vector cualquiera v:

 vn es la proyección de v sobre n. Se puede ver que v está formado por dos componentes: una sobre el vector normal n y la otra sobre el plano definido por esa normal. Si a v le restamos su proyección sobre la normal vn, obtenemos la proyección de v sobre el plano. En el caso del vídeo, esa normal es por defecto la dirección vertical Y, luego obtendremos la proyección sobre el plano XZ. Este es el vector vXZ

El cálculo de la transformación de entradas al sistema de referencia

Vamos a considerar la siguiente escena:
Tenemos una cámara y un personaje controlado por el jugador, cada uno con su orientación. Cuando vayamos a utilizar al jugador como sistema de referencia de las entradas, los vectores que determinarán la dirección hacia la que, teóricamente, querremos movernos al pulsar el eje vertical (azul) y el eje horizontal (rojo) serán éstos:
Si, por el contrario, empleásemos la otra estrategia, utilizar la cámara como sistema de referencia, primero proyectaríamos el vector morado sobre el suelo (para evitar "penetrar en el suelo" o "levitar") al pulsar hacia arriba o hacia abajo, y luego utilizaríamos esa proyección para el eje vertical; seguiríamos el mismo proceso para el eje horizontal. Éstos serían nuestros nuevos vectores de dirección asociados a cada eje del mando.
Estos vectores nos dan las direcciones de cada eje, y su longitud vendrá determinada por el valor de ese eje (como hemos dicho, +1.0 pulsando en un sentido, -1.0 pulsando en sentido contrario, y 0.0 en reposo).

Una vez tenemos esos vectores con las longitudes correctas, los combinamos en CalculateDirections para determinar la dirección que queremos que siga el personaje. La longitud/magnitud de este vector debería estar entre 0 y 1 (donde 0 es estar quieto, y 1 es moverse al máximo de velocidad). Sin embargo, si los sumamos para combinarlos, la longitud resultante de la suma puede ser mayor que 1 (vector v1), luego hay que recortar su longitud a 1, utilizando Vector3.ClampMagnitude (vector v2).
Multiplicando ese vector por la velocidad máxima correspondiente, obtenemos el vector de movimiento del personaje, que utilizaremos, por ejemplo, en playerCharacterController.SimpleMove. En la implementación de la estrategia de movimiento con la dirección fija hay un ejemplo de cómo utilizar los valores que hemos mantenido en inputData para determinar si se mueve hacia adelante o hacia atrás, si se mueve un poco hacia un lado... La utilidad de esto es, aparte de poder hacer variar su velocidad máxima (como hemos visto aquí), ¡para mezclar diferentes animaciones (correr hacia un lado, hacia atrás...), y obtener animaciones de movimiento más fluidas y naturales!

Interpolación esférica slerp

En la anterior entrada del blog veíamos la función lerp, que calculaba una interpolación lineal entre dos valores. También es posible utilizarla con vectores para obtener un punto intermedio. El problema es que, si bien es bastante conveniente para interpolar posiciones en una trayectoria lineal de un punto A a un punto B, cuando esa posición viene determinada por una rotación, la solución más cómoda es recurrir a lo que se conoce como interpolaciones esféricas. En el vídeo utilizábamos una función llamada slerp para suavizar los giros del avatar.
Es evidente que si a y b están muy juntos, apenas se nota diferencia entre utilizar lerp y slerp. Sin embargo, cuanto más se separen a y b, más se deformará el vector intermedio si utilizamos lerp. La propiedad de slerp frente a lerp es que en la interpolación esférica, la longitud del vector interpolado será la misma (bueno, técnicamente, interpolada entre las longitudes de a y de b, en un elipsoide más que en una esferea, pero nunca inferior a las dos, como puede ser el caso de lerp).

Descargas

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

viernes, 14 de junio de 2013

Crea tu juego indie - Anexo - Matemáticas (1)

Sí amigos, esta es una de esas partes desagradables que tarde o temprano tenía que tocar. Sin embargo, no vamos a centrarnos en los fundamentos que hay por debajo o su implementación (que para eso ya está implementado y optimizado en Unity y en casi cualquier otro motor que decidáis utilizar), sino en su utilidad práctica; cómo sacar ventaja de estas funcionalidades para entender mejor cómo funciona un juego y ser mucho más productivos y versátiles. Con saber que existen y qué hacen es más que suficiente.

Hoy empezaremos por la clase fundamental de matemáticas en Unity: Mathf.

Mathf

Mathf es la clase de Unity encargada de ofrecernos la funcionalidad básica para el cálculo con números decimales (float).

Constantes

En Mathf se definen una serie de valores constantes:
  • PI: El archiconocidísimo número 3.14159... Relaciona el radio de una circunferencia con su longitud o el área de su círculo. Es útil para trabajar con ángulos en radianes, por ejemplo.
  • Deg2Rad y Rad2Deg: Son dos constantes para convertir un número de grados a radianes y de radianes a grados, respectivamente. Como la relación entre grados y radianes es la misma siempre, pueden precalcularse (como en este caso). Para usarlos, basta con multiplicar el ángulo (en grados o en radianes) por la constante correspondiente para cambiarlos a la otra unidad. Por ejemplo:
    // Convertir 70 grados a radianes
    var anguloEnRadianes = 70.0 * Mathf.Deg2Rad;
    // Pi radianes = 180 grados
    var anguloEnGrados = Mathf.PI * Mathf.Rad2Deg;
    En ocasiones podremos encontrarnos librerías que sólo trabajan con una de las dos unidades, y será necesario hacer la conversión.
  • Infinity, NegativeInfinity y Epsilon: El número más grande positivo, el más grande negativo, y el más pequeño, respectivamente, que pueden almacenarse en un float. Los infinitos vienen muy bien para inicializar valores (por ejemplo, si quieres asegurarte de que el primer número que compares sea siempre menor que el valor por defecto, etc.). Épsilon es bastante útil si se quiere evitar una división por cero (y además, si se suma épsilon a un valor distinto de cero, ese valor no cambia, de manera que es seguro utilizarlo).

Signo y valor absoluto

  • Sign(x): Es una función que devuelve el signo de un número X, como un número 1 si X es positivo o -1 si X es negativo.
  • Abs(x): Esta función devuelve el valor absoluto de un número X (el valor de X sin su signo).

Comparación de números

  • Min(a,b) y Max(a,b): Comparan A y B, y devuelven el menor y el mayor de los dos, respectivamente. Por poner uno de los muchísimos ejemplos, si una IA tuviera que decidir a cuál de varios objetivos atacar según sus puntos de vida restantes.
  • Approximately(a,b): Compara A y B para ver si son casi iguales. Cuando se va a comparar que dos números decimales, por cuestiones de precisión, se debería usar esta función en vez de el comparador ==.

Redondeo

  • Round(x): Redondea X al número entero más cercano.
  • Floor(x) y Ceil(x): Redondean X estrictamente hacia abajo o hacia arriba.
Estas tres funciones son bastante útiles al trabajar con cuadrículas, por ejemplo.

Potencias y logaritmos

  • Pow(x,y) y Exp(y): El valor de multiplicar un número (X y la constante e, respectivamente) por sí mismo Y veces.
  • Sqrt(x): La raíz cuadrada de X. En otras palabras, el número que multiplicado por sí mismo vale X.
  • Log10(x), Log(x) y Log(x,b): El número al que hay que elevar la base (10, e y B, respectivamente) para obtener X. Es la inversa de las potencias (Log(Exp(x)) == x, Log(Pow(x,n),n) == x).
No tienen mucha utilidad, salvo hacer algunas implementaciones a bajo nivel por nuestra cuenta. Por ejemplo, las funciones ClosestPowerOfTwo(x), IsPowerOfTwo(x) y NextPowerOfTwo(x), que se usan sobre todo para trabajar a bajo nivel con texturas, las utilizan.

Trigonometría

  • Sin(x) y Cos(x): Devuelven respectivamente el seno y el coseno de X. Son increíblemente útiles. De entrada, por su naturaleza oscilante, se pueden usar para ciertas animaciones (enemigos que flotan hacia arriba y hacia abajo, volando alrededor de un punto en círculo, o como un pendulo). Además, son cíclicos y contínuos, y tienen mucha utilidad con patrones que se repiten (generar texturas fractales, valores aleatorios "repetibles" como el ruido Perlin...)
  • Tan(x): Devuelve la tangente de X.
  • Asin(x), Acos(x) y Atan(x): El arco-seno, el arco-coseno y la arco-tangente. Devuelven el ángulo tal que su seno, su coseno y su tangente (respectivamente) son X. Atan2(x,y) es una variante de Atan(x) que recibe las longitudes de los catetos (las componentes de un vector 2D) en vez del valor de la tangente.
Salvo el caso del seno y el coseno, el resto sólo se utilizan con frecuencia cuando se van a hacer cálculos a bajo nivel. En realidad casi todos sus usos están en otras implementaciones ya ofrecidas por Unity.
Hay que tener en cuenta que todos los ángulos que utilizan están en radianes.

Acotación

  • Clamp(x,a,b): Devuelve el valor de X recortado a un mínimo de A y un máximo de B. En otras palabras: si X es menor que A devuelve A, si X está entre A y B devuelve X, y si X es mayor que B, devuelve B. Por ejemplo si tras calcular daños y curaciones, queremos asegurarnos de que la vida del jugador no se sale de un cierto rango. Clamp01(x) es equivalente a Clamp(x,0,1).

Interpolación y otras funciones predefinidas

  • Lerp(a,b,t): Calcula el punto intermedio entre A y B, a una distancia relativa T. Si T == 0, devuelve A. Si T == 1, devuelve B. Si T == 0.5, devuelve la media exacta entre A y B. Calcula los valores intermedios interpolando linearmente. En estas gráficas se pude ver los valores de Lerp(0,1,x) en rojo y Lerp(3,0.5,x) en verde.
    • LerpAngle(a,b,t): Es similar a Lerp(a,b,t), salvo que está preparado para ángulos. Si se usa Lerp(350,10,x), va bajando de 350 hasta 10. Si se usa LerpAngle(350,10,x), pasará por 360 grados, continuando desde 0 grados.
    • InverseLerp(a,b,x): Es la inversa de Lerp(a,b,t). Devuelve la posición relativa de X entre A y B (0 si X==A, 1 si X==B).
  • SmoothStep(a,b,t): Es similar a Lerp(a,b,t), pero con una aceleración inicial y deceleración final, para suavizar el movimiento, en vez de usar una interpolación lineal (más brusca al empezar y parar). Aquí están los ejemplos usados en Lerp, pero usando SmoothStep.
  • SmoothDamp(...): Va cambiando un valor gradualmente hacia otro de forma suavizada (decelerando cuanto más se acerca al final). Es muy utilizado al animar cámaras, entre otros casos. SmoothDampAngle(...) es la versión especializada para ángulos.
  • Repeat(x,n): Cuando X > N, empieza de cero de nuevo. Se usa, por ejemplo, cuando se quiere que los valores se repitan de 0 a N. Por ejemplo, la gráfica de Repeat(x,1.7)
  • PingPong(x,n): Similar a Repeat(x,n), pero los valores van subiendo y bajando de 0 a N. La gráfica resultante sería como un diente de sierra (aquí la gráfica de PingPong(x,2))
  • DeltaAngle(a,b): La diferencia menor entre dos ángulos determinados A y B. Cíclicos en 360 grados, y devuelve el menor entre la diferencia de ángulos en sentido de las agujas del reloj y en sentido contrario.
  • PerlinNoise(x,y): Usa una combinación de varias funciones (normalmente trigonométricas, pero depende de la implementación) para generar una textura repetible y pseudo-aleatoria (en este caso, se devuelve el valor de esa "textura" en el punto (X, Y)). Su uso original era el de generar texturas, pero se puede usar para infinidad de cosas más (como aleatorizar mazmorras o crear terrenos mediante algoritmos). Para que os hagáis una idea de su utilidad para todo lo multimedia, en 1997 a su creador, Ken Perlin, le dieron un Oscar por logros técnicos por inventar este y otros métodos similares.
  • MoveTowards(x,y,max): Calcula la interpolación lineal de X a Y, pero sin superar una distancia máxima. Una especie de "limitador de velocidad" en la interpolación. MoveTowardsAngle(x,y,max) es similar, especializada para ángulos.

        domingo, 9 de junio de 2013

        Crea tu juego indie - Lección 04 - Sistema de cámaras básico

        Ahora que ya hemos visto cómo programar nuestros propios componentes, empezamos con un ejemplo práctico completo: Un sistema de cámaras en tercera persona sencillo pero flexible.

        (Muy recomendado verlo a pantalla completa en HD)

        Descargas

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

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

          Saludos a todos

          ¡Hola a todo el mundo!

          Bienvenidos a este blog que estreno donde intentaré que los interesados podáis ir aprendiendo a desarrollar videojuegos desde cero :)

          Mi idea es, en principio, ver todo lo necesario para crear un prototipo de juego en Unity, para más adelante si esto avanza, ir refinándolo y viendo algunos conceptos más avanzados. Estos cursos están pensados para poder empezar sin tener ningún conocimiento previo ni de programación, ni de dibujo, ni de cualquier otra disciplina relacionada con el desarrollo de juegos.

          Utilizaremos exclusivamente herramientas totalmente gratuitas de Windows (la mayoría disponibles también para MacOSX), para que el precio no sea una excusa ;)

          Comenzaremos viendo los fundamentos de Unity, mientras vamos aprendiendo los conceptos fundamentales de programación orientada a objetos, desde cero, con JavaScript.

          ¡Manos a la obra!