10 abril 2014

WaveEngine: Mostrar un sprite 2D y mover con teclado

¿Qué tiene de bueno WaveEngine?


Es un motor de juegos en C#, multiplataforma y gratuito, con el que es fácil crear juegos para Windows 8 y Windows Phone, y no mucho más complicado crearlos para Android e iOS.


¿Qué tiene de malo WaveEngine?


La curva de aprendizaje comienza de forma más pronunciada que con motores más básicos: es fácil crear un sistema de partículas para imitar humo, con poquísimas líneas de código, pero, por el contrario, es relativamente largo (comparado con otras plataformas básicas para 2D) hacer algunas tareas sencillas, como mostrar una imagen en pantalla. Y cuando uno empieza a programar, normalmente prefiere empezar por cosas que tengan una "lógica simple" antes de llegar a los "efectos vistosos".

Por eso, vamos a ver algún ejemplo básico que ayuden a crear un juego 2D simple usando WaveEngine.


Disclaimer


No soy ningún experto en WaveEngine, sólo alguien ha asistido al WaveEngine University Tour 2014, así que puede que alguna cosa se pueda hacer de formas (mucho) más eficientes que la que yo exponga. Si lees esto y conoces alguna, puedes publicar un comentario para ayudar a mejorarlo.


¿Qué pasos hay que dar para instalar (en Windows)?


1.- Descargar e instalar Visual Studio 2012 o 2013 (basta con la versión Express, gratuita): http://www.microsoft.com/es-es/download/details.aspx?id=40787

2.- Si usas Windows 7, deberás descargar (e instalar) "el Framework Punto Net 4.5"; si empleas Windows 8, ya lo tendrás instalado: http://www.microsoft.com/en-US/download/details.aspx?id=30653

3.- Necesitarás instalar el SDK de DirectX (no basta con usar el "runtime" que empleas para jugar a juegos que usan DirectX, porque ahora vas a crear cosas que utilizan DirectX). Puedes usar un instalador web desde http://www.microsoft.com/en-us/download/details.aspx?id=35

4.- Finalmente, el instalador de WaveEngine: http://waveengine.net/Download/Index

5.- Es habitual que las versiones recientes de WaveEngine incluyan el VC++ 2012 Runtime; si no es así, tendrás que descargarlo también): http://www.microsoft.com/en-US/download/details.aspx?id=30679


¿Qué apariencia tiene un programa?


Si entras a Visual Studio y creas un nuevo proyecto de tipo "WaveEngine Game Project", aparecerá un esqueleto casi vacío, como éste:

public class MyScene : Scene
{
    protected override void CreateScene()
    {
        RenderManager.BackgroundColor = Color.CornflowerBlue;

        //Insert your code here
    }
}

Nuestro "juego" tiene una única escena, con color de fondo azul claro... y nada más.

Si lanzas el proyecto (con el botón "Iniciar") de Visual Studio, debería aparecer esa escena vacía con color azul:



Vamos a añadir un elemento que el usuario pueda mover...


En primer lugar, tenemos que buscar la imagen que nos interese (por ejemplo, un fichero PNG) y convertirla al propio formato nativo de WaveEngine, de forma que esa imagen se pueda utilizar en cualquier plataforma.

Para convertir la imagen utilizaremos una de las herramientas que incorpora WaveEgine: el "Assets Exporter". Deberemos abrirla y crear un nuevo proyecto (File / New project), escogiendo una carpeta, que es en la que aparecerán los ficheros exportados. Ahora arrastramos nuestra imagen hasta la palabra "Assets", que aparece en el panel superior izquierdo. Después vamos al menú "Project" y escogemos la opción "Export". No tendremos ningún aviso (al menos en la versión actual) de que todo ha funcionado, pero si vamos a la subcarpeta "Export" dentro de la carpeta que habíamos escogido, veremos un fichero con el mismo nombre que nuestra imagen, y terminado en ".wpk" (en mi caso, a partir de "player.png" se ha generado "player.wpk").



Ya tenemos la imagen preparada. Ahora hay que llevarla a nuestro proyecto de Visual Studio. En el panel superior derecho ("Explorador de soluciones") aparecerá la carpeta "Content". Debemos arrastrar nuestra imagen hasta allí.



Para asegurarnos de que la imagen realmente se incluya en el proyecto distribuible, nos falta un paso adicional: hacer clic en ella, ir a la pestaña "propiedades" y, en la opción "Copiar en el directorio de resultados", cambiar la opción "No copiar" por "Copiar si es posterior".



Nos vamos acercando. Nuestra imagen está incluida al proyecto y será distribuida con el proyecto. Ahora vamos a incluir el código que realmente la muestre en pantalla. Para conseguirlo, deberemos crear una nueva "entidad", un nuevo elemento del juego.



Cada "Entity" (entidad) estará formado por varios "componentes". Algunos de estos componentes serán necesarios para que la entidad sea visible en pantalla:


  • Un "Transform2D", que tendrá detalles como la posición, escala y rotación del elemento.
  • Un "Sprite", que será la imagen en sí.
  • Un "SpriteRenderer", que dará detalles adicionales sobre cómo se debe dibujar el Sprite (y que de momento se limitará a avisar de que se debe mostrar en una "capa opaca").


Así, un fuente básico que muestre la Entity correspondiente a nuestra imagen será:

public class MyScene : Scene
{
    protected override void CreateScene()
    {
        RenderManager.BackgroundColor = Color.CornflowerBlue;

        Entity player = new Entity()
                .AddComponent(new Transform2D())
                .AddComponent(new Sprite("content/player.wpk"))
                .AddComponent(new SpriteRenderer(DefaultLayers.Opaque));
    }
}

En un principio no compilará: no sabrá no que es Transform2D, ni Sprite, ni SpriteRenderer... Podemos añadir

using WaveEngine.Common.Graphics;
using WaveEngine.Components.Graphics2D;

o bien pulsar "Ctrl+." para que el propio VisualStudio nos proponga los "using" que faltan.

Si lo lanzamos, veremos que aún no funciona, sigue apareciendo la pantalla azul, porque falta un último detalle: existe un elemento en el juego, que es el encargado de gestionar todas esas "entidades". Se llama "EntityManager", y debemos incluir en él cada "Entity" que creemos:

EntityManager.Add(player);

Ahora sí aparecerá la imagen dentro de la pantalla de juego:









Pero esa imagen aún es estática. Para añadir un que un objeto se mueve, debemos añadirle un "comportamiento" ("Behavior"). El comportamiento será una clase que herede de "Behavior" y que deberá tener un método "Update" (actualizar), que será llamado de forma regular. Desde ese método podremos comprobar distintos métodos de entrada. Por ejemplo, podemos mirar si se ha pulsado la flecha hacia la derecha con:

if ((WaveServices.Input.KeyboardState.Right == ButtonState.Pressed))

En ese caso deberíamos cambiar la coordenada X del jugador. Podemos acceder a ella a través de su "Transform2D". La forma de conseguirlo es incluyendo al principio de nuestra clase PlayerBehavior las líneas

[RequiredComponent]
private Transform2D playerData;

De modo que a partir de entonces podremos acceder a detalles como la X y la Y del jugador con "playerData.X" y "playerData.Y".  El fuente completo de esa clase "PlayerBehavior" (comportamiento del jugador) sería algo como:

using System;
using WaveEngine.Common.Input;
using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Services;

namespace WaveEnginePrueba01Project
{
    class PlayerBehavior : Behavior
    {
        [RequiredComponent]
        private Transform2D playerData;

        protected override void Update(TimeSpan gameTime)
        {
            if ((WaveServices.Input.KeyboardState.Right == ButtonState.Pressed))
            {
                 playerData.X += 5;

            }

            if ((WaveServices.Input.KeyboardState.Down == ButtonState.Pressed))
            {
                playerData.Y += 5;

            }
        }
    }
}

(Sí, sólo se mueve hacia la derecha y hacia abajo, pero seguro que no te cuesta completarlo).

Y así habría quedado el bloque principal, la escena, al que sólo faltaría añadir (con AddComponent) el comportamiento (un nuevo objeto de la clase PlayerBehaviour):

using WaveEngine.Common.Graphics;
using WaveEngine.Components.Graphics2D;
using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;

namespace WaveEnginePrueba01Project
{
    public class MyScene : Scene
    {
        protected override void CreateScene()
        {
            RenderManager.BackgroundColor = Color.CornflowerBlue;

            Entity player = new Entity()
                    .AddComponent(new Transform2D() )
                    .AddComponent(new Sprite("content/player.wpk"))
                    .AddComponent(new SpriteRenderer(DefaultLayers.Opaque))
                    .AddComponent(new PlayerBehavior() );

            EntityManager.Add(player);
        }
    }
}


¿Tienes dudas (que quizá hasta pueda responder... pero no garantizo nada)?  ¿Quieres que esto sea el principio de una serie que cuente más detalles de cómo hacer un juego paso a paso?  ¡Deja un comentario!

04 abril 2014

Una semana para Google Code Jam

Faltan 7 días para que comience el "Google Code Jam 2014", que se abre el día 12 de abril a la una de la madrugada, hora de España peninsular.

Esa primera fase de clasificación suele estar formada por 3 problemas, a los que hay que responder en unas 24 horas (27 horas en esta edición). Los problemas no tienen el mismo "peso", y existen varios conjuntos de datos de pruebas para cada problema (típicamente uno "pequeño" y uno "grande"), que también tienen distinto valor. Es habitual que sea necesario contestar "no sólo uno" para pasar a la siguiente fase, más dura y que se realiza en tres tandas, con 3 horarios distintos, para que ninguna parte del planeta tenga una desventaja clara.

Si te gustan la programación y los retos, deberías intentarlo. Posiblemente no te llevarás los 15.000 dólares de premio y no te ofrecerán trabajo en Google. Es también bastante probable que ni siquiera quedes entre los 1.000 mejores del mundo, que tienen derecho a camiseta conmemorativa... porque ser uno de los 1.000 mejores de todo el mundo supone estar entre la auténtica élite de tu país, y eso no está al alcance de cualquiera. Aun así, puede ser interesante enfrentarse a retos que desengrasen las neuronas y que ayuden a mantener vivos los conocimientos de programación.

Más información y retos de ediciones anteriores, en code.google.com/codejam

Y si quieres algún reto en español para practicar (pero recuerda que el concurso de Google se realiza en inglés), aquí tienes unos cuantos: nachocabanes.com/retos

02 abril 2014

Pascal - 3a: Estructuras repetitivas, for

3 - Estructuras repetitivas

3.1. ¿Qué es un bucle?

Vamos a ver cómo podemos crear bucles o lazos, es decir, partes del programa que se repitan un cierto número de veces.
Según cómo queramos que se controle ese bucle, tenemos tres posibilidades, que vamos a comentar en primer lugar:
  • for..to: Repite una orden (o un bloque de órdenes) desde que una variable tiene un valor inicial hasta que alcanza otro valor final (un cierto NÚMERO de veces).
  • while..do: Repite una sentencia MIENTRAS se cumpla una condición.
  • repeat..until: Repite un grupo de sentencias HASTA que se dé una condición.
La diferencia entre estos dos últimos es que "while" comprueba la condición antes de repetir las demás sentencias, por lo que puede que estas sentencias ni siquiera se lleguen a ejecutar, en caso de que la condición de entrada fuera falsa. Por el contrario, en un "repeat", la condición se comprueba al final, de modo que las sentencias intermedias se ejecutarán al menos una vez.
Vamos a verlos con más detalle...

3.2. Desde... hasta... (for)

3.2.1 Avanzar de uno en uno

El formato de la orden "for" es
for variable := ValorInicial to ValorFinal do
    Sentencia;
Se podría traducir "desde que la variable valga ValorInicial hasta que valga ValorFinal" (y en cada pasada, su valor aumentará en una unidad).
Como primer ejemplo, vamos a ver un pequeño programa que escriba los números del uno al cinco:
(* FOR1.PAS, Primer ejemplo de "for": contador *)
(* Parte de CUPAS5, por Nacho Cabanes          *)
 
Program For1;
 
var
    contador: integer;
 
begin
    for contador := 1 to 5 do
        writeLn( contador );
end. 
 
(* Resultado:
1
2
3
4
5
*)
 

3.2.2. "For" encadenados

Los bucles "for" se pueden enlazar uno dentro de otro, de modo que podríamos escribir las tablas de multiplicar del 1 al 5, dando 5 pasos, cada uno de los cuales incluye otros 10, así:
(* FOR2.PAS, bucles enlazados: tabla de multiplicar *)
(* Parte de CUPAS5, por Nacho Cabanes               *)
 
Program For2;
 
var
    tabla, numero: integer;
 
begin
    for tabla := 1 to 5 do
        for numero := 1 to 10 do
            writeLn( tabla, ' por ', numero ,' es ', tabla * numero );
end. 
 
(* Resultado:
1 por 1 es 1
1 por 2 es 2
1 por 3 es 3
...
1 por 10 es 10
2 por 1 es 2
...
5 por 10 es 50
*)
 

3.2.3. "For" y sentencias compuestas

Hasta ahora hemos visto sólo casos en los que después de "for" había un única sentencia. Si queremos repetir más de una orden, basta encerrarlas entre "begin" y "end" para convertirlas en una sentencia compuesta, como hemos hecho hasta ahora.
Así, vamos a mejorar el ejemplo anterior haciendo que deje una línea en blanco entre tabla y tabla:
(* FOR3.PAS, bucles con sentencias compuestas *)
(* Parte de CUPAS5, por Nacho Cabanes         *)
 
Program For3;
 
var
    tabla, numero: integer;
 
begin
    for tabla := 1 to 5 do
    begin
        for numero := 1 to 10 do
            writeLn( tabla, ' por ', numero ,' es ', tabla * numero );
        writeLn;        (* Línea en blanco *)
    end;
end. 
 
(* Resultado:
1 por 1 es 1
1 por 2 es 2
1 por 3 es 3
...
1 por 10 es 10
 
2 por 1 es 2
...
5 por 10 es 50
*)
 
Como vimos, es muy conveniente usar la escritura indentada, que en este caso ayuda a ver mejor dónde empieza y termina lo que hace cada "for".

3.2.4. Contar con letras

Una observación: para "contar" no necesariamente hay que usar números, también podemos contar con letras:
(* FOR4.PAS, letras como índices en un bucle *)
(* Parte de CUPAS5, por Nacho Cabanes        *)
 
Program For4;
 
var
    letra: char;
 
begin
    for letra := 'a' to 'z' do
        write( letra );
 end. 
 

3.2.5. Contar de forma decreciente (downto)

Con el bucle "for", tal y como lo hemos visto, sólo se puede contar en forma creciente y de uno en uno. Para contar de forma decreciente, se usa "downto" en vez de "to".
(* FOR5.PAS, "for" que desciende      *)
(* Parte de CUPAS5, por Nacho Cabanes *)
 
Program For5;
 
var
    i: integer;
 
begin
    for i := 5 downto 1 do
        write( i );
end. 
 
(* Resultado:
54321
*)
 

3.2.6. Contar de 2 en 2 o usando otros incrementos

Para contar de dos en dos (por ejemplo), hay que buscarse la vida: multiplicar por dos o sumar uno dentro del cuerpo del bucle, etc... Eso sí,sin modificar la variable que controla el bucle (deberemos usar cosas como "write(x*2)" en vez de "x := x*2", que pueden dar problemas en algunos compiladores).
(* FOR6.PAS, "for" y nnmeros no correlativos  *)
(* Parte de CUPAS5, por Nacho Cabanes         *)
 
Program For6;
 
var
    contador: integer;
 
begin
    for contador := 1 to 5 do
        writeLn( 10 * contador );
end. 
 
(* Resultado:
10
20
30
40
50
*)
 
(Puedes ver la versión más actualizada, junto con ejercicios propuestos, en la página oficial del curso)