Unity: Estado global del juego

Algo fundamental en cualquier juego es el estado global. Esto mantiene en memoria datos como la puntuación, en qué nivel estás, qué idioma está utilizando el juego, cuántas vidas te quedan… etc. Sin estado global, sencillamente no se puede hacer un juego de más de un nivel.

En Unity se suele utilizar el patrón Singleton. Necesitaremos una clase con una instancia estática, llamémosla GameState, y luego necesitaremos inicializarla cuando empiece el juego. Unity destruye todos los objetos entre escena y escena para liberar memoria, pero el UnityEngine.Object puede pasarse a la función de MonoBehaviour DontDestroyOnLoad para que se mantenga en memoria. No obstante, este tipo de objetos no se pueden instanciar mediante new… así que la forma de implementar el Singleton va a ser un poco distinta a la que estamos acostumbrados en otros entornos.

Una vez creado el script para GameState, crearemos la propiedad para su instancia e implementaremos el patrón en su función Awake(), que se llama antes de Start():

public static GameState instance;

public void Awake()
    {
        DontDestroyOnLoad(instance);
        if (instance == null)
            instance = this;
    }

public static GameState GetInstance()
    {
        return instance;
    }

Así, cuando se invoque la clase, la instancia se creará. No obstante, la primera llamada no funcionará correctamente, puesto que debemos instanciarla primero. Esto lo haremos con una clase que podemos llamar GameStart o GameManager, algo así. Este script debe contener una instancia pública de un objeto GameState, para así instanciarlo al ejecutarse. Ambos scripts se pueden asignar a la cámara de la primera pantalla, por ejemplo la splash screen, donde se pasará por la interfaz el GameState al GameStart.

using UnityEngine;
using System.Collections;

public class GameStart : MonoBehaviour {

    public GameState gameManager;

    void Awake()
    {
        if (GameState.instance == null)
            Instantiate(gameManager);
    }
}

Al pasarle el GameState al GameStart por el inspector, podemos comprobar en el Awake() si la instancia se ha creado ya, al ser null, usando Instantiate se lanzará el Awake del GameStart, creando así la instancia y ya podemos usar GameState.GetInstance() en cualquier script del juego para recuperar y cambiar el estado actual.

Unity: Texto deslizante dinámico

Una parte importante de la presentación de un videojuego puede ser mostrar una introducción épica en un texto deslizante que va de abajo hacia arriba o viceversa. Aunque puede parecer una tarea sencilla (y lo es), tiene ciertos detalles que puede costar encontrar cuando eres nuevo en Unity.

Para mostrar el texto en pantalla vamos a utilizar la API de GUI. Esto nos permite cargar un estilo y hacer ciertos cálculos necesarios desde el script de forma dinámica, pudiendo adaptarlo a cualquier texto y tipo de fuente. Las llamadas a GUI se realizan desde una función llamada OnGUI, que se llama varias veces por cada frame. Es decir, hay que tener cuidado con su uso en el proyecto, dado que es, con diferencia, la parte que más CPU va a consumir. Si tu proyecto es para móvil, quizá prefieras crear una imagen y moverla mediante su Transform, aunque tengas que hacer varias para distintos textos.

Lo que haremos será cargar el texto en la función Start, texto que se puede cargar desde un archivo o ponerlo con un string literal. Luego, podemos definir nuestros estilos utilizando la clase GUIStyle, donde se carga la fuente que quieras utilizar, fuente que siempre debe estar en una carpeta Resources para poder utilizar sin problemas Resources.Load. Además, se especifica el color, el tamaño, el wrap y otros detalles de presentación:

        var style = new GUIStyle();
        style.font = (Font)Resources.Load("RutaDeLaFuente");
        style.normal.textColor = Color.white;
        style.fontSize = 28;
        style.wordWrap = true;

Ahora que ya tenemos el texto, podemos implementar el método que irá cargando el texto. Dentro de la función OnGUI, primero iremos calculando la posición vertical utilizando la altura de la pantalla con Screen.height y el tiempo transcurrido, siempre disponible con Time.time. Dado que esto sería lento para ir subiendo el texto, podemos calcular el movimiento multiplicando el tiempo transcurrido por la velocidad deseada. Con esto, en cada pasada podemos definir una posición vertical que va disminuyendo, por lo cual en cada renderización se verá el texto ligeramente más arriba, creando el efecto de scrolling.

Para dibujar el texto con GUI, utilizamos GUI.Label, al cual le pasamos una Rect que creemos al acto y un GUIContent conteniendo el texto que queremos mostrar, lo cual nos permitirá saber cuándo el texto ha salido de pantalla. Una vez dibujado el texto con el estilo que hemos definido, podemos utilizar el GUIContent para calcular el tamaño del mismo, definiendo así en qué posición estará el texto fuera: cuando la altura del texto sumada a la de la pantalla más un pequeño offset multiplicado por -1 sea mayor que la posición vertical del texto, es decir, cuando esté por encima de ese punto arbitrario. Así sabremos cuándo pasar a la siguiente escena.

El script entero:

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;

public class RollingText : MonoBehaviour {

    private string text;
    private GUIStyle style = new GUIStyle();
    private float textypos;
    private int textSpeed = 20;
    private float yTreshold = 0f;

	void Start ()
    {
	    text = "Esto es un texto largo.\nSe podría cargar desde un Resource.";

        style.font = (Font)Resources.Load("FontDirectoryInResourcesAndName");
        style.normal.textColor = Color.white;
        style.fontSize = 28;
        style.wordWrap = true;
    }

    void Update ()
    {
        if (Input.GetKeyDown(KeyCode.Return))
        {
            GoToMenu();
        }
    }

    void OnGUI ()
    {
        // Ponemos la posición inicial ligeramente por debajo de la pantalla.
        // Mientras el tiempo pasa, la posición va subiendo.
        textypos = Screen.height + 5 - Time.time * textSpeed;
        GUIContent content = new GUIContent(text);
        GUI.Label(new Rect(160, textypos, 330, 480), content, style);
        // Aquí calculamos el tamaño del texto una sola vez y luego lo utilizamos para saber cuándo ir al menú (o siguiente escena).
        if (yTreshold == 0)
            yTreshold = ((Screen.height + style.CalcSize(content).y) * -1) - 60;
        if (yTreshold > textypos)
            GoToMenu();
    }

    void GoToMenu ()
    {
        SceneManager.LoadScene("next_scene_name");
    }
}

Y, por supuesto, disponible en GitHub.

Unity: Movimiento básico de un juego de plataformas

No hay nada como un clásico juego de plataformas en 2D para aprender a utilizar Unity o incluso desarrollar un maravilloso e innovador juego. Para empezar, añadimos nuestras pantallas iniciales, cómo no. Pero vamos a la chicha. Cogemos un magnífico sprite, sin duda dibujado por manos expertas, y creamos un personaje que está quieto, camina y salta. Lo típico. Ante nada, pondremos el objeto (GameObject) que va a representar el personaje. Va a tener un Transform, un Sprite Renderer, un Script de movimiento de personaje, un Rigidbody 2D, un Polygon Collider 2D y un Animator.

¿Para qué sirve cada objeto, preguntas? Bien. Veamos: El Transform controla la posición, rotación y escala. Es información que se usa en el movimiento y en el cambio de animación. El Sprite Renderer es lo que nos va a mostrar el sprite que pertoque. El Rigidbody 2D tiene la información de físicas, es decir, en este caso nos provee la gravedad y el movimiento. El script es donde tenemos la programación del movimiento: con qué teclas se mueve. El Polygon Collider 2D es una forma de crear un polígono para detectar las colisiones con una forma específica (por ejemplo sólo en los pies y no en la cabeza) y por último el Animator es el objeto donde se programa la animación del sprite según el estado en el que se encuentre.

En el momento en el que el personaje tenga un collider y el suelo tenga un collider, sólo con tener gravedad desde el Rigidbody ya caerá sobre el suelo y se quedará quieto. Ahí, bien. Ahora toca programar el movimiento, es decir, que se mueva a izquierda o derecha y que salte. No obstante, ahora hay que configurar la gravedad y el movimiento correctamente. Es fácil generar fuerzas con el método AddForce del Rigidbody, hacia cualquier dirección. Para ello bien puedes utilizar un evento de tecla específica o usar el eje horizontal/vertical.

Bien. Ahora mismo tenemos un objeto representando el suelo, con un Box Collider 2D (o uno de sus amigos) y el personaje en cuestión, con su Polygon Collider 2D y su Rigidbody 2D. Lo primero que hay que hacer es configurar correctamente el cuerpo rígido para que se adecúe a la pantalla en la que estamos trabajando. Por ejemplo, en un juego de 640×480 píxeles con una cámara de tamaño 2.4, podemos ponerle una masa de 5, un Linear Drag de 5 y una escala de gravedad de 1.

Ahora necesitaremos el script. Empezaremos añadiendo un FixedUpdate, que se ejecuta en un intérvalo fijo de tiempo, mientras que el Update normal se ejecuta cada vez que se refresca el frame. Dentro del FixedUpdate podemos coger el eje horizontal para ver si el jugador está moviéndose usando el input por defecto de Unity. Con ese dato, podemos comprobar la velocidad máxima y añadirle a la velocity del Rigidbody un Vector2 (vector de dos dimensiones) con el movimiento realizado.

void FixedUpdate()
{
    float h = Input.GetAxis("Horizontal"); 

    if (h * rb2d.velocity.x < maxSpeed) rb2d.AddForce(Vector2.right * h * moveForce); if (Mathf.Abs(rb2d.velocity.x) > maxSpeed)
    {
        rb2d.velocity = new Vector2(Mathf.Sign(rb2d.velocity.x) * maxSpeed, rb2d.velocity.y);
    }
}

Ahora hace falta especificar el valor de velocidad máxima y, además, añadir la opción de saltar (para algo esto es un juego de plataformas, ¿no?). Añadimos una función Update, que decidirá si el jugador salta cada vez que se aprete el botón de “salto”. Pero… ¿Cómo podemos saber si el jugador está en el suelo para que el salto no sea infinito? Pues hay diversas maneras. Una manera sencilla es crear un GameObject dentro del objeto que contiene el personaje del jugador, donde obtendremos un Transform invisible. Este objeto, llamémoslo ground_check, puede tener el tamaño y la posición sobre el personaje que queramos. Utilizando la position del Transform, lo ponemos en los pies del personaje.

Pero ahora necesitamos que este objeto nos indique cuándo el personaje toca el suelo con los pies. Le añadimos, pues, un Box Collider 2D, le ponemos un tamaño pequeño de 0.1 para que no sobresalga del personaje y además lo marcamos como Is Trigger. Al estar este objeto dentro del objeto del personaje, accederemos desde el script de movimiento a los eventos de entrada y salida de colisión. Ahora completar el script con un salto es bastante sencillo, haciendo uso de dichos eventos.

using UnityEngine;
using System.Collections;

public class PlatformerMovement : MonoBehaviour
{
    [HideInInspector]
    public bool facingRight = true;
    [HideInInspector]
    public bool jump = false;
    public float moveForce = 50f;
    public float maxSpeed = 0.5f;
    public float jumpForce = 250f;
    private bool grounded = false;
    private Animator anim;
    private Rigidbody2D rb2d;

    // Los vas a necesitar.
    void Awake()
    {
        anim = GetComponent();
        rb2d = GetComponent();
    }

    // Update se llama una vez cada refresco de frame.
    void Update()
    {
        // Por defecto es "espacio"
        if (Input.GetButtonDown("Jump") && grounded)
        {
            jump = true;
        }
    }

    // FixedUpdate se llama en un tiempo fijado, por ello la física va aquí.
    void FixedUpdate()
    {
        float h = Input.GetAxis("Horizontal");

        anim.SetFloat("Speed", Mathf.Abs(h)); 

        if (h * rb2d.velocity.x < maxSpeed) rb2d.AddForce(Vector2.right * h * moveForce); if (Mathf.Abs(rb2d.velocity.x) > maxSpeed)
            rb2d.velocity = new Vector2(Mathf.Sign(rb2d.velocity.x) * maxSpeed, rb2d.velocity.y);

        if (h > 0 && !facingRight || h < 0 && facingRight)
            Flip();

        if (jump)
        {
            anim.SetTrigger("Jump");
            rb2d.AddForce(new Vector2(0f, jumpForce));
            jump = false;
        }
    }

    // Para darle la vuelta al personaje sin tener que dibujar más.
    void Flip()
    {
        facingRight = !facingRight;
        Vector3 theScale = transform.localScale;
        theScale.x *= -1;
        transform.localScale = theScale;
    }

    // Eventos que lanza el ground_check.
    void OnTriggerEnter2D()
    {
        grounded = true;
    }

    void OnTriggerExit2D()
    {
        grounded = false;
    }
}

Y listo. Por supuesto, es algo muy básico, pero funciona. El script actualizado se puede encontrar en GitHub. En una futura entrada entraré en más detalle en el desarrollo de un movimiento de plataformas más avanzado, cuando haya acabado de pelearme con ello.

Iniciarse en Rust

Hace un tiempo decidí echarle un vistazo a nuevos lenguajes de programación y adentrarme un poco en el mundillo del bajo nivel, para intentar mejorar mis malos hábitos de programador de lenguaje dinámico.

¿Qué es Rust? Es el nuevo lenguaje de sistemas (o systems language) de Mozilla. Actualmente se utiliza en el proyecto Servo, donde así se prueba la robustez y el desarrollo del lenguaje. ¿Para qué sirve, entonces? Aunque lo llaman de propósito general (o general-purpose), su utilidad principal es para programas que requieren de un lenguaje de bajo nivel, como el mencionado navegador web, un servidor HTTP o quizá hasta un kernel entero. ¿Y por qué no usar nuestros conocidos y viejos amigos C o C++? Por muchas razones.

Rust es un lenguaje con seguridad de memoria y concurrencia sin problemas de condiciones de carrera (race conditions). Su intención es ser un lenguaje de sistemas sin comportamiento sin definir (undefined behaviour) y de tipado seguro (type safety). Además de esto, Rust tiene macros higiénicas, una gran ventaja a la hora de extender el lenguaje. Por último, las variables son inmutables por defecto, aunque puedes indicar que sean mutables. Todo esto indica que, a cambio de una compilación algo más lenta y una forma distinta de trabajar, con Rust se pueden hacer sistemas y programas más seguros y que aprovechan con más facilidad múltiples núcleos de una CPU.

Suena interesante, ¿no? Pues, sin más dilación y tras leerme el Rust book, me lanzo de cabeza a realizar el ejercicio que he estado haciendo últimamente para probar nuevos lenguajes, un juego en CLI de piedra-papel-tijeras. Oh, tío, no sabía lo que me esperaba. Debería haberme leído el libro y la documentación mucho más antes de meterme en esto. En fin.

Empezamos instalando cargo (lee el libro) para empezar un nuevo proyecto y tener nuestra fn main() {} lista. Arriba del todo metemos un use std::io, que seguro que lo necesitamos. Comencemos: la idea del programa es sencilla, un bucle, un contador de victorias y derrotas, y un mapa para saber qué hacer con cada opción, que serán piedra, papel y tijeras. Obviamente hay soluciones más elegantes como usar aritmética lógica, pero eso sería rizar el rizo demasiado para el ejercicio, al menos de momento. Así pues, necesitamos un vector que contenga las opciones permitidas mencionadas, un aleatorio de la CPU por cada ronda y una forma de, dada tu opción y la de la CPU, computar el resultado. La primera cosa a tener cuenta es que en Rust, debido a su seguridad de tipo que incluye su interesante sistema de propiedad de datos, no es lo mismo el tipo String (en realidad collections::string::String) que &str (una slice). El primero es un buffer de bytes en el heap que la variable posee, el segundo es una “vista” fija a un String asignado en memoria, ya sea en el heap o en memoria estática en el caso de cadenas literales.

Por lo tanto, para asignar el input del jugador, vamos a utilizar un String mutable (como se puede ver en el ejemplo del libro del juego de adivinar). Ahora, imagina que tenemos el vector ["piedra", "papel", "tijeras"]. Bien, para empezar, eso es un literal de string, es decir, un &str, no un String. Si intentas utilizar uno en el lugar del otro, tendrás un error de compilación. No obstante, convertir un literal es tan sencillo como utilizar to_owned(): let estring = variable.to_owned();. La palabra let indica una variable local, y como se puede ver el compilador inferirá el tipo de la variable, sabiendo lo que devuelve ese método.

Un comentario sobre los vectores (o arrays). Un vector literal es del tipo [tipo; longitud], por lo tanto el vector literal de Strings (en vez de &str dado que hemos utilizado la función que devuelve un nuevo objeto del que somos dueños) será de tipo [String; 3]. En un literal del estilo [[0, -1, 1], [1, 0, -1], [-1, -1, 0]], el tipo será [[_;3]; 3].  ¿Qué significa la barra baja (underscore)? Que el compilador sabe que quieres usar un integer, pero no tiene ni idea de qué tipo es. Va a ser mejor que le indiques qué tipos estás utilizando: let arrayses: [[u8;3];3] = [[0, -1, 1], [1, 0, -1], [-1, -1, 0]]. Todo esto es importante para comprobar la combinación de las opciones elegidas por el jugador y la CPU.

Siguiendo el ejemplo del libro de Rust para el juego de adivinar, utilizamos el io estándar y asignamos el valor que nos da el jugador. Primero, tenemos que comprobar si es una opción válida, después, deberíamos traducirla a un número integral para utilizarlo como índice en un vector de vectores para sacar el resultado de forma fácil, dado que la función de aleatorio para la CPU nos devuelve números integrales. Tras pensar en una buena forma de decidir cómo relacionar el input del jugador y su resultado, me decanto por HashMap de tipo HashMap<String, u8>, así puedo tener un mapa de pares de String y un pequeño valor numérico integral. Es hora de, utilizando el input del jugador y del aleatorio de la CPU, conocer el resultado para avanzar en el juego. Tenemos el resultado de la CPU en let cpuAction: u8 = rand::thread_rng().gen_range(0, 3);, dado que 8 bits son suficientes para un número del 0 al 2. Para la opción del jugador, tenemos let playerAction: u8 = answers.get(&finalInput).unwrap().to_owned();, lo que cogiendo la opción que ha puesto en la consola, nos devuelve un tipo Option<&u8>, es decir, una referencia al valor del HashMap envuelta en el tipo Option. La opción unwrap nos saca el valor y luego pasamos a poseerlo directamente, es decir, a hacer una copia en memoria. Así tendremos todos los literales del mismo tipo para acceder fácilmente a ellos (cosa que no sé si es la más adecuada, idiomática o mejor, ¡estoy aprendiendo!). src/main.rs:41:16: 41:32 error: the trait `core::ops::Index` is not implemented for the type `[[u8; 3]]` [E0277]. Mierda.

Parece ser que el tipo de los índices de arrays es usize. ¡Ya hemos aprendido algo nuevo! No hay problema, retiramos el type hinting de las elecciones del jugador y la CPU y añadimos al final as usize, para decirle a Rust que queremos usar esos números de ese tipo (coerción de tipo); usize es un tipo que se decide según donde se compile, puede ser de 32 o de 64 bits, por ejemplo. Bien, ya podemos comprobar el resultado de la partida, añadimos una fácil comprobación de victorias y derrotas, y listo. Compilamos, quitamos los warnings (hay que usar snake case en vez de camel case) y probamos el juego:

juanma@le-toucan:/home/proyectos/ConsoleGames/rps/rust/target/debug$ ./rps
Rock, paper, scissors; best of five!
Choose an action[rock, paper, scissors].
rock
Wrong action!

Ouch. Resulta que la función contains_key del HashMap me dice que “rock” no es una clave. Bien, no entremos en pánico, hemos hablado de esto antes. La seguridad de tipado en Rust es muy, muy importante (fundación del diseño del lenguaje), así que estoy haciendo algo mal, obviamente. Como antes he dicho, he declarado la clave del mapa como un String, y para comprobar si la clave existe, le paso la referencia a un string que he creado a través del input del jugador. Pero este no es el problema. El problema es que, al apretar enter en la consola, le estoy enviando al programa tanto la cadena de texto como el salto de línea y he de hacerle un trim. Bien, fácil: se le aplica la función trim(), que devuelve una referencia, pero se asigna de nuevo y listo: input = input.trim().to_owned();. Bien, ahora ponemos el reporte de resultados y listo:

juanma@le-toucan:/home/proyectos/ConsoleGames/rps/rust$ cargo run
   Compiling rps v0.1.0 (file:///home/proyectos/ConsoleGames/rps/rust)
     Running `target/debug/rps`
Rock, paper, scissors; best of five!
Choose an action[rock, paper, scissors].
rock
The CPU chose paper
You lose! (0 - 1)
Choose a new action.
Choose an action[rock, paper, scissors].
paper
The CPU chose scissors
You lose! (0 - 2)
Choose a new action.
Choose an action[rock, paper, scissors].
fu cpu
Wrong action!
Choose an action[rock, paper, scissors].
paper
The CPU chose paper
It's a tie! (0 - 2)
Choose a new action.
Choose an action[rock, paper, scissors].
paper
The CPU chose rock
You win! (1 - 2)
Choose a new action.
Choose an action[rock, paper, scissors].
rock
The CPU chose paper
You lose! (1 - 3)
You lost the game...

Perfecto. Aquí tenemos el código resultante, listo para compilar y distribuir.

Cómo utilizar TypeScript en NodeJS

El ecosistema de Node es maravilloso. Es fácil levantar un proyecto (a mano o con StrongLoop), hay muchísimos módulos disponibles e incluso puedes montar tus aplicaciones con CoffeeScript (aunque no veo por qué habrías de querer hacerlo) o incluso con TypeScript. Aunque, ahora mismo quizá te preguntes: ¿qué es TypeScript?

TypeScript es un superset de JavaScript desarrollado por Microsoft. Añade type hinting, clases, interfaces, módulos, etc. Compila en un archivo .js legible. Así pues, se puede utilizar en cualquier entorno JavaScript y es compatible con herramientas como jQuery y D3.js, por supuesto también con Node. Si ES2015 te sabe a poco en tus desarrollos dado que quieres utilizar un sistema de tipado más explícito (cosa que no parece que vayamos a ver en JavaScript pronto), esta es una buena solución.

Cuando instalas TypeScript, tienes el comando tsc para compilar un archivo .ts al archivo .js que quieras. Aunque quizá quieras migrar poco a poco un proyecto ya existente a TypeScript y necesites utilizarlo desde tu aplicación de Node, sin compilarlos. Quizá requieras herramientas para probar archivos .ts en vivo. No te preocupes. El programa ts-node te permite tanto lo primero como lo segundo, y además existe este pequeño pero interesante proyecto llamado better-require que te permite requerir módulos .ts directamente.

Por qué migré de PHP a Node

No hay palabras para describir la frustración y el odio acumulados debido a PHP durante más de una década. Al final, lo que he aprendido a fuego es que PHP es un mal lenguaje de programación. Hay gente que lo ha explicado muy bien en diferentes ocasiones: PHP Sadness, PHP is a fractal of bad design (disponible en español), PHP is meant to die, the PHP singularity, Please stop pretending PHP is a good language y You use PHP because you don’t know better. Vale que se ha repetido hasta la saciedad, pero creo que es una información que hay que extender más, sobre todo en español. No he encontrado links similares a los anteriores en castellano, aunque la traducción de fractal of bad design ya dice lo suficiente como para comprender mis motivaciones a la hora de escribir este post.

PHP no es sólo malo en sí. PHP te suele venir con MySQL quieras o no, ya sea por los millones de hostings con el stack LAMP o por las aplicaciones que te hacen usar MySQL como base de datos sí o sí. MySQL también es deficiente. ¿Que por qué? También hay literatura de terror dedicada a ello, como Terrible choices: MySQL y Do No Pass This Way Again. Colegas de profesión, existen PostreSQL y SQLite. Por favor, utilizadlos.

Toda esta queja no es más que la introducción al tema. Me gustaría escribir un aviso tanto para novatos como para veteranos, pero especialmente dirigido a los primeros: No uses PHP, especialmente si tienes algún tipo de aspiración profesional. Como mínimo, aprende un lenguaje de programación mejor antes. De lo contrario, te arrastrará al averno, limitará tus posibilidades futuras y además te carcomerá la mente de forma que usar un lenguaje de programación de verdad se te hará muy difícil con el paso del tiempo. No quieres estar editando páginas WordPress o en el CMS Symfony2 número diez millones el resto de tu vida, ¿no? Quizá puedas llegar a una buena empresa que utiliza PHP pero que procude un código excelente y un entorno inmejorable, pero quizá sea mejor llegar a ella trabajando mientras tanto con otros lenguajes. Incluso con Java.

Entiendo la raíz del problema. PHP es fácil de usar, cualquiera puede empezar a programar en PHP, aunque no sea un programador. Esto es, además, parte de su ideología conceptual. Es un lenguaje de scripting interpretado, por lo que no tienes que preocuparte por el tipado y además simplemente haciendo el archivo y metiéndolo en el host ya funciona. Prácticamente todos los servicios de hosting del mundo, de pago o gratuitos, ofrecen la posibilidad de levantar un stack LAMP (del peor tipo, MySQL con PHP) de forma extremadamente fácil. No sólo eso, su equipo interno toma decisiones que se podrían considerar estúpidas.

Quiero decir, lo intentan. Realmente lo intentan. Hubo una gran discusión con flames y todo sobre cómo llamar a PHP 7, dado que PHP 6 ya se había dado a conocer e incluso se editaron libros que se vendieron pese a que se deshechó el proyecto, decantándose por el 7 (pasamos de PHP 5.6 a 7). Claro, que todo esto viene a la zaga de la HHVM, un logro de la ingeniería del equipo de Facebook. Convirtieron PHP en algo decente. ¿Por qué no es ya el estándar en toda la web? No lo sé. El caso es que PHP 7 ha pegado un buen golpe sobre la mesa, trayendo una velocidad y un uso de memoria jamás vistos en PHP. ¿Arregla esto sus principales problemas? No.

PHP ahora se puede escalar mejor en grandes aplicaciones. Bien. ¿Es a partir de la versión 7 un mejor lenguaje? ¿Deja de ser su uso un pecado? ¿Puedo montar una webapp moderna con PHP? Rotundamente no. El problema de PHP es radical, fundamental. El mundo moderno de internet empieza a moverse por webapps: aplicaciones con funcionalidades a tiempo real para muchos usuarios concurrentes. PHP no está diseñado ni pretende estar diseñado para eso. PHP está diseñado para hacer CMSs (Content Management System) con un cacheo potente.

PHP7 sigue necesitando un $ para inicializar cada variable. Sigue usando algo tan absurdo como -> para acceder a propiedades y métodos de un objeto. El orden de los parámetros sigue sin tener sentido, así y como los nombres de las funciones. Usar PHP sin un framework es intolerable. Voy a entrar en detalle, describiendo problemas que pasan a menudo, que he tenido y de paso dejando en castellano algunos de los ejemplos de los links que he mencionado en el primer párrafo.

El reporte de errores en PHP es un mundo. Para que sea mínimamente tolerable, necesitas un módulo aparte: xdebug. El reporte de errores del propio lenguaje da ascopena. Luego tienes otras herramientas como Blackfire, que le facilitan enormemente al desarrollador de PHP. Mientras que en otros lenguajes el reporte de errores puede ser suficiente, en PHP es a menudo insuficiente por su cuenta. Algo tan obvio y común como no cerrar un string por error puede llevar a un debugging curioso si no te das cuenta y tu editor no te lo marca. Vamos, que estás forzado a usar un IDE para cualquier edición, porque un IDE te marca más y mejor los errores que el propio intérprete de PHP.

Los nombres de las funciones a menudo no tienen sentido. Usar las funciones propias de PHP es una locura: en la librería estándar tienes una amalgama de funciones que o bien son wrappers de C de los tiempos remotos de PHP, o bien son creaciones nuevas, o bien no siguen el mismo tipo de nombre, o bien son un añadido posterior, a menudo una copia de Java. PHP te da opciones de programación imperativa y además OOP, pero es que encima te obliga a mezclar ambos por cómo funcionan sus primitivos y las funciones que actúan sobre los primitivos. Si quieres usar iteradores en vez del constructo array, puedes, pero vas a tener limitaciones. Sí, PHP te da iteradores pero la funcionalidad entre estos y los arrays no son 100% compatibles: las funciones de array no funcionan en iteradores. ¿Anidar operadores ternarios? No lo hagas sin paréntesis. Son asociativos hacia la izquierda, por lo que los resultados no son los que esperarás si estás acostumbrado a operadores ternarios en otro lenguaje

Y esto es sólo el principio. La HHVM era un mejor paso hacia un buen futuro en los lenguajes de scripting web presentes en básicamente todos los servidores, pero PHP7 va a convencer más a los phperos, acostumbrados a lo suyo. Nombrar el tipo de entrada y de retorno de una función sigue sin ser obligatorio, así que, si no va contigo, no los usas. De todas maneras, para PHP poner que quieres retornar un integer está muy bien, pero tampoco se va a quejar si es cualquier tipo que pueda convertir en un intenger. Como un string. Muy bien, PHP7, muy bien.

JavaScript ha sido un lenguaje motivo de burla durante muchos años. No obstante, con el advenimiento de Node, las cosas han cambiado. Ahora, utilizando este entorno, puedes desarrollar webapps modernas como jamás lo habías hecho con PHP. Todo dado, por supuesto, por la facilidad de tratar con websockets. Entre la potencia de poder montar aplicaciones en tiempo real con Node muy rápidamente y con poco código, las ventajas que JavaScript comparte con PHP (rápido y fácil de escribir, muchísimos desarrolladores disponibles, framework con una gran empresa detrás [StrongLoop de IBM], entorno de Unit Testing, …) y ahora las nuevas versiones estandarizadas de JavaScript (ES2015, ES2016, etc.), se puede ver que las aplicaciones modernas deberían escribirse en Node. Como me gusta lo moderno, lo novedoso, lo que va más allá del típico stack, de cabeza me metí en el mundo de Node. Os invito a reflexionar sobre dónde queréis trabajar en el mundo de la programación y sobre si para solucionar vuestro problema PHP es la mejor opción.

Git para legos

Git es un sistema de control de versiones. La potencia de git reside en que permite el desarrollo distribuido fácilmente. En un repositorio git, tienes un servidor central, el remoto origin, que contiene el proyecto. Cada persona tiene una copia completa del repositorio en su local, donde todo se mantiene mediante una simple carpeta .git y unos pocos archivos como .gitignore, todos en la raíz del proyecto (generalmente), al contrario de otros sistemas que te ensucian todo el árbol de directorio. A continuación, voy a describir el uso y funcionamiento de los principales comandos de consola para git.

Empezar un repositorio de git es muy sencillo: git init inicia un repositorio con una carpeta oculta .git. Añade un .gitignore para ignorar archivos. Usarlo es muy fácil, añade el directorio o archivo a ignorar por git en cada línea. No obstante, esto sólo te crea un repositorio vacío. Si quieres crear un repositorio a partir de un remoto ya existente: git clone path o git clone username@host:path, que hace una copia local exacta del remoto. Es lo más habitual a no ser que sólo trabajes en proyectos propios. Si has hecho un init, deberás añadir el remoto con el que vas a trabajar. Esto se hace con git remote add nombre remoto. Añade el servidor servidor como remoto por el nombre nombre, generalmente origin.

Y así tienes un repositorio listo. Pasemos a la parte de trabajar: Tu repositorio local tiene tres “árboles”:
Working Directory – El estado actual de los archivos del proyecto.
Index – El área de staging, donde están tus últimos cambios listos.
HEAD – El último commit que has hecho.
El Working Directory es donde trabajas normalmente en tu editor de texto o IDE, ahí están todos los cambios además del estado del proyecto. En el Index o staging es donde están los cambios listos y acabados para ser commiteados y pusheados, mientras que HEAD apunta al último commit que has hecho. Cuando haces cambios en un fichero, los pasas de tu Working Directory a Staging con: git add <filename>. Si haces git add . se añaden todos los cambios. Y, si quieres ser pro, puedes hacer git add -i, para añadir cambios de forma interactiva en la consola, eligiendo qué añadir y qué no.

Si quieres saber el estado de tu repositorio, viendo qué archivos has cambiado, cuáles están listos para commit y el último commit de HEAD, utiliza git status. Te enseña el estado do de tu local: archivos cambiados, archivos listos para ser commiteados, archivos nuevos y sin trackear…

Una vez has pasado todos los cambios a Staging, puedes realizar un commit con git commit -m “Mensaje” [-m “Descripción detallada”]. La primera -m es el mensaje del commit, la segunda -m es la explicación del mismo (que es opcional, pero deberías poner. Si no lo haces, eres mala persona). Un commit es, básicamente, una indicación de cómo y dónde aplicar un diff. Tu repositorio es un gran árbol, cada rama no deja de ser una combinación de commits, que podrían considerarse las hojas del árbol. Cada commit tiene un commit padre, que es el commit anterior. Al final, un repositorio es una historia de cambios, uno tras otro, en un orden específico.

Una vez están todos los cambios listos en sus respectivos commits, llega la hora de enviar tu excelente trabajo al remoto para que el resto del equipo y del mundo puedan maravillarse con él. Esto se hace con git push remoto rama, que empuja los cambios realizados (commits) al remoto (normalmente origin) a la rama especificada (como por ejemplo master).
Si no te importa lo que hay arriba y lo quieres sobreescribir, usa -f.
Es force, pero recuerda, todos sabemos que forzar puede salir mal. Se utiliza en pocas y especiales ocasiones, más adelante hablaré sobre la más común, los rebases.

Si quieres cambiar un commit, tan sólo tienes que realizar las acciones que quieras, utilizar git add de nuevo y luego re-utilizar git commit con la opción –amend, lo que actualizará el último commit hecho con los cambios realizados. Si ya habías pusheado el commit, deberás utilziar la opción -f que acabo de mencionar para reescribir la historia en el repositorio remoto.

Hablemos de las ramas. Como he mencionado antes, una branch o rama es una serie de commits que están separados de la rama principal, master. Una separación es una branch, una unión de vuelta de esta branch a otra es un merge. Cuando hace falta desarollar una nueva funcionalidad, se suele hacer en paralelo a master a través de una rama, así se pueden desarrollar diversas funcionalidades en diversas ramas, sin que el trabajo de unos afecte a los demás. Para crear una nueva rama, tan sólo tienes que hacer git checkout -b nombre, donde el nombre indica el nombre de la rama. Este comando, además, te pone en esa misma rama. Para cambiar de una rama a otra, se hace con git checkout nombre. Los cambios preparados en una rama, se quedan en esa rama. Los cambios del Working Directory pasan a la nueva rama de trabajo cuando creas una rama y pasas a ella.

Si ya no necesitas una rama por lo que sea, por ejemplo, has acabado los cambios y los has metido en master, es muy fácil borrarlas. Localmente, bastará con git branch -d nombre. ¿Y si quieres borrar toda su existencia? Bueno, pues entonces git push –delete remoto nombre, que borra la rama nombre del remoto remoto.

Bueno, ahora imagina que has estado trabajando un rato en una rama y estás listo para publicar los cambios que has commiteado con los comandos que he listado antes. Tu comando ahora es git push remoto nombre, que empuja los cambios locales a la rama nombre del remoto remoto. Así, si tienes un fork del proyecto /Hola/kase en /Rayo/lase, puedes añadir un remoto a /Rayo/lase y hacer un push a rayo master, donde rayo es el remoto /Rayo/lase. Un fork significa coger un proyecto y su repositorio entero y hacer una copia personal, bajo tu control. Un fork tiene su propio repositorio remoto y sus ramas. Puedes añadir tantos remotos como quieras a tu repositorio local, utilizando los comandos de remoto que he explicado al principio.

Si estás trabajando en distintos lugares en tu proyecto o en un equipo, eventualmente vas a necesitar coger los cambios del repositorio remoto y aplicarlos a tu código. Esto se hace con git pull, tira de los cambios remotos a tu entorno local para trabajar con ellos. Hace fetch y merge. Generalmente, especificarás de qué remoto y qué rama, como por ejemplo: git pull origin master.

Cuando trabajas en una nueva feature en una rama y llega el momento de meter los cambios en master, has de hacer un merge. El comando para esto es git merge nombre. Hace un merge automático de la rama nombre a tu rama actual. Eso sí, a veces puede haber conflictos. Si los hay, deberás editarlos a mano y, para solucionarlos, hacer un git add de los archivos y un git commit de la solución para terminar el merge correctamente.

A menudo, cuando has hecho cambios o vas a hacer un merge, necesitas saber qué ha cambiado. Git utiliza diff con el comando git diff, mostrándote en consola las diferencias actuales en el proyecto, por defecto los cambios en el Working Directory. Pero también puedes hacer git diff rama_origen rama_destino para ver los cambios que la segunda introduce sobre la primera. Increíble, ¿eh? Pero eso no es todo, git diff archivo muestra las diferencias de cambios de un solo archivo. Es una herramienta muy potente que te permite comparar cualquier tipo de cambio en el repositorio, algo muy útil en el día a día del programador.

Si lo que quieres es ver una lista de los últimos cambios en la rama, utilizarás el comando git log. Esto te mostrará un log de los commits realizados. En esta lista puedes ver el hash de un commit, el que se utiliza en comandos variados para referenciarlos. Si buscas los commits de alguien en específico, tan sólo has de aádir la opción –author=nombre. Pero esto no es todo, git log tiene muchas funcionalidades, como –pretty=oneline para verlo bonito en una línea, opciones como –graph –oneline –decorate –all para tener un bonito árbol ascii y colorines.
git log –name-status te enseña los archivos cambiados en cada commit. Con -–help puedes ver más utilidades, dado que git log tiene muchísimas configuraciones posibles.

Si lo que quieres es saber quién ha cambiado ciertas líneas en un archivo y no tienes ganas de andar buscando commit a commit, con git blame puedes ver quién lo ha hecho para así alzar un dedo acusador. Este comando te mostrará el nombre del autor de cada línea así y como el commit que introdujo el último cambio en la misma.

También puedes ver los cambios que ha realizado un commit en específico con git show commit_hash, de forma similar al git diff, y si lo que quieres es ver los cambios específicos que ha relaizado un commit sobre un archivo lo puedes hacer con git show commit_hash:nombre_archivo. Ningún cambio escapa al control de Git.

Si eres tú el que se ha equivocado, no pasa nada. Git también tiene herramientas para paquetes. Con git checkout archivo devuelves el archivo archivo al estado en el que está en el HEAD. Si la has liado mucho durante un refactor y ya no hay marcha atrás, suele ser la mejor solución. Con git reset nombre (donde nombre puede ser un punto) puedes hacer un reset de un archivo, quitando los cambios de staging, devolviéndolos al Working Directory. Ahora, si quieres deshacer los cambios para siempre, puedes utilizar la opción –hard, lo cual los obliterará. ¡Usar con sumo cuidado!

Además del anteriormente mencionado git pull, hay una opción que te permite recoger el estado del remoto. Es git fetch remoto, aunque también puedes utilizar git fetch –all para recoger todo. Una vez tienes el estado, puedes aplicar cambios utilizando el nombre del remoto y la rama, como git pull remoto/rama, aunque no la tengas en local, dado que has recogido el estado. Esto también te permite hacer una locura: git reset –hard origin/master, lo cual te deja el local tal cual está en el remoto origin en la rama master (o en la que quieras).

Cuando trabajas con diversas ramas, a veces puedes necesitar un único commit de otra. Esto es muy fácil, puedes aplicar cualquier commit sobre tu rama actual con el comando git cherry-pick commit_hash. ¡Eso sí, cuidado con los conflictos!

Si estás en medio de una serie de cambios o de una feature, pero los de márketing te vienen gritando que necesitan otra feature ahora mismo y tienes que trabajar en la misma rama, git stash es tu amigo. Este comando guarda los cambios del Working Directory para utilizar en otro momento. Puedes incluso ponerles nombre y se pueden recuperar en cualquier orden, con git stash list ves todos los que tienes y con git stash pop recuperas cambios stasheados y los aplicas sobre el actual Working Directory, pudiendo volver a lo que estabas haciendo.

Si no quieres un archivo del repositorio, así, en general, lo puedes borrar tranquilamente. Con git rm lo borrarás al completo (cuidado con esta opción) y git rm –cached nombre hará que git elimine el archivo del árbol, dejándolo como untracked file, es decir, archivo dentro del repositorio pero sin estar versionado, como si lo acabases de añadir.

Todo esto cubre los básicos más básicos de git. Hemos repasado cómo crear un proyecto, añadir y quitar cambios y manejarse a través de las ramas y los commits. Por supuesto, hay mucho más. A menudo, cuando estás trabajando y, sobre todo, cuando trabajas de forma descentralizada, vas llenando una rama de commits basura, o simplemente vas añadiendo commits de fix a un commit anterior. Queda feo, ¿no? Pues bien, la historia se puede reescribir. Nuestro amigo es git rebase -i. Rebase te permite reescribir la historia sin problemas, aunque los commits viejos quedarán guardados en el árbol de git, como hojas perdidas. Todos los commits que has enviado al repositorio permanecen para siempre. Con esta herramienta puedes unir commits, separarlos, borrarlos y editarlos.

Puedes elegir cambiar los últimos commits de HEAD con git rebase -i HEAD~5, dónde el número del final decide cuántos coger, o mejor aún, ir hasta cierto commit con git rebase -i commit_hash. Entonces tienes una lista de commits con varias opciones en tu editor favorito (espero que sea vim). Con pick, coges un commit para usarlo en el rebase. Si comentas un commit, se eliminará. Con reword, cambias el mensaje de un commit. Con edit, puedes cambiar el mensaje, autor, etc. de un commit, añadirle cambios, y tal, utilizando un –amend. Con squash, unes un commit al anterior y te paras para editar el mensaje. Con fixup, unes un commit al anterior ignorando su mensaje, es un squash rápido. Con exec ejecutas el comando escrito en la shell y, por último, con drop eliminas un commit.

Git rebase te permite unir commits redundantes y limpiar un árbol de trabajo antes de un merge, dejando sólo commits y mensajes limpios y entendibles. Lo que hace el rebase es coger el commit anterior al que has escogido para usarlo como base, luego aplica commit a commit en el orden que le indiques (es decir, puedes cambiar los commits de orden, aunque esto generalmente causará conflictos) y realizando las acciones que le indiques. Al final obtendrás una nueva historia de commits a aplicar, lo cual podrás pushear al remoto, utilizando la opción de force-push que he mencionado al principio del artículo.

Ahora que ya hemos hablado de los rebases, volvamos a un básico pero con un pequeño giro: git pull -–rebase. Coge HEAD, hace un fetch con el estado de tu rama y aplica un rebase, esto es, aplicando commit a commit desde el último punto en común ignorando los commits de merge.
Si hay conflictos, tendrás que solucionarlos, seguir con git rebase –continue tras hacer un add y commit. Esta es la mejor opción a la hora de actualizar ramas, dado que elimina commits de merge automáticos y mantiene una historia lo más limpia posible. Además, facilita la aplicación de force-pushes.

Bien. Ya tienes tu proyecto bien, limpio, con todos los commits y tus features hechas. Llega la hora de la release. Git también te permite marcar releases, esto es marcar un estado en el proyecto como una versión estable a la que siempre puedes volver independientemente de la rama. Esto es git tag. Las releases o tags que marques con este comando serán listadas en GitHub. Un ejemplo sería git tag 1.0.0 commit_hash, marcando la versión 1.0.0 del proyecto apuntando al commit_hash con toda su historia. Y con esto, estamos listos para llevar un proyecto de éxito en git.

Si todo esto te parece excesivamente complicado o te da alergia la consola, siempre puedes usar gitk.

Unity: Juegos en 2D con pixel art

Aunque Unity es un motor creado para hacer juegos en 3D, resulta también muy socorrido para hacer juegos en 2D, especialmente desde las últimas mejoras que le han ido añadiendo para este propósito. Eso sí, para que un juego se vea bien en pixel art, vas a tener que en cuenta unas cuantas cosas.

La primera de todas, por supuesto, es saber hacer pixel art. Como yo soy un lego en el tema, mejor os recomiendo dos guías que me han parecido útiles para no dibujar broza: creating pixel art en Pixel Joint y Pixel Art Tutorial en Make games.

Una vez tienes el arte listo y tu proyecto de Unity comenzado, con la escena esperando dicho arte, el siguiente paso es importar los archivos a tu proyecto. Hay dos opciones de importación clave para mantener los píxeles tal y como los quieres: dejar el Filter Mode en Point (no filter). Esto impedirá que Unity aplique filtro alguno. Luego, en Format, elige Truecolor, para tener el sprite sin compresión, tal cual.

Menú para importar un sprite correctamente para un juego 2D pixel art.
Menú para importar un sprite correctamente para un juego 2D pixel art.

Pero esto no es todo. La renderización tiene que ser adecuada para los píxels. Cada píxel de tu sprite ha de ser renderizado como un píxel en la pantalla, o al menos un múltiplo redondo. La mejor opción en este caso es tocar el tamaño ortográfico de la cámara. El tamaño ortográfico determina qué cantidad de unidades en el espacio del mundo (juego) se divide la altura de la pantalla. Se calcula desde el centro, es decir, si el tamaño ortográfico es X, la cantidad de píxels por unidad será (pixels_altura / 2X). Así pues, los Píxels Por Unidad o Pixels Per Unit de tu sprite ha de corresponder a esta medida. Recuerda que cambiar el tamaño ortográfico cambia cuánto del mundo del juego ve el jugador.

Por último, recuerda: no utilices mipmapping, no uses un filtrado anisotrópico, no uses antialiasing y si es posible utiliza un script para calcular el tamaño ortográfico de tu cámara.

Unity: Añadiendo textos

Imagina que quieres hacer una escena inicial o una escena introductoria donde avanzas la trama. Por supuesto, va a haber diálogo y vas a querer tener un texto que lo represente. Lo más probable es que lo primero que hagas tras crear una escena nueva sea intentar crear un componente Texto de UI a un objeto de juego vacío. Error.

El objeto de Texto de los componentes de UI en Unity 5 debe ir dentro de un Canvas. Los elementos del Canvas se renderizan después de que la escena se haya renderizado. El objeto Canvas tiene varias opciones, generalmente querrás que se renderice en Screen Space – Camera, por lo que el canvas (y lo que contiene) seguirá el tamaño de la cámara, además de estar siempre en pantalla.

Uso del canvas para añadir textos.
Uso del canvas para añadir textos.

Como se puede ver en la imagen, tendrás que arrastrar el objeto de la cámara a la opción de Render Camera para que el canvas sepa en qué cámara se va a renderizar. Una vez hecho esto, puedes crear el objeto de texto En Component > UI > Text. Este objeto ha de ser hijo de canvas, y podrás cambiar su tamaño y renderizado, siempre usando el canvas como referencia. Ahora lo podrás ver fácilmente en la pantalla y además controlarlo mediante scripting. ¿Cómo? Muy fácil.

Text text = GameObject.Find("Text").GetComponent();
text.text = "asdf";

Como podemos ver, usando la función Find de GameObject con el nombre del objeto de texto podemos obtener fácilmente el objeto de tipo Text, al cual podemos cambiarle el texto simplemente editando el contenido de su propiedad text, que es de tipo string. Además, en el ejemplo de código del repositorio, se puede ver cómo implementar un texto estilo RPG, donde se va rellenando la frase, aunque hablaré más de las coroutines en otra entrada. Quizá.

Unity: Splash screen y menú principal

Lo primero que vas a ver en un juego son splash screens y luego el menú principal. Una splash screen común podría ser tu logo como desarrollador de videojuegos (compañía o no), mientras que en el menú principal generalmente querrás mostrar una bonita y épica imagen a la par que permites al jugador elegir entre unas pocas opciones (jugar, salir, opciones, etc.).

Lo primero que tienes que hacer es crear una escena nueva e importar los assets necesarios, como bien podría ser una imagen PNG del logo que quieras para una Splash screen. Una vez tengas las escenas creadas, te preguntarás, ¿cómo puedo pasar de una a otra? Buena pregunta. Es uno de los conceptos más básicos y necesarios en Unity, y es tan sencillo como está línea:


SceneManager.LoadScene("nombre_escena");

Claro que, para utilizarla, primero habrás de usar el namespace de manejo de escenas:


using UnityEngine.SceneManagement;

Generalmente, querrás que la splash screen pase a la siguiente escena tras unos segundos o al pulsar una tecla, como Enter, comando que también puedes aprovechar en tu menú principal. Pues bien, tan sólo tienes que utilizar la palabra reservada yield y WaitForSeconds() para ello:

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;

// This class provides the utility you need for a Splash screen.
// The scene will be shown and, upon pressing Enter or waiting some seconds, the next scene will be called.
public class SplashScreen : MonoBehaviour {

    // Use this for initialization.
    // Yields a new wait, that will be executed in 4.00 seconds, and then it will run the following code (which loads a scene).
    IEnumerator Start () {
        yield return new WaitForSeconds(4f);
        SceneManager.LoadScene("title_screen");
    }

    // KeyCode.Return means the Return key. May be any key.
    void Update ()  {
        if (Input.GetKeyDown(KeyCode.Return)) {
            SceneManager.LoadScene("title_screen");
        }
    }
}

Este código utiliza la función Start, devolviendo un IEnumerator, para esperar cuatro segundos y luego cargar la siguiente escena, “title_screen” (¿Adivináis cuál?). Además, con el objeto Input puedes capturar entradas de teclado para fácilmente pasar de una escena a otra usando las teclas. El método Update se utiliza cada vez que hay un refresco de pantalla, lo cual ocurre muchas veces por segundo.

Y así, en un momento, lo tienes hecho.