22 octubre 2011

20 minutos con Ubuntu 11.10

No he aguantado más de 20 minutos con el Oneiric Ocelot, y lo voy a desinstalar ya. Bueno, a sobreescribirlo con otro Linux que haga mi netbook agradable de usar.

¿Por qué? Pues por varias malas impresiones. Por ejemplo, abrir aplicaciones con Unity es desesperante. No sólo por lo que cuesta encontrarlas en ese mini-menú lateral que apenas muestra 5 opciones y que obliga a hacer muchos clics para cualquier cosa, sino también por lo que tardan en funcionar. Todo se abre mucho más despacio que con Gnome. Y es que al menos con la versión 11.04 se podía elegir arrancar con el "Gnome clásico", pero en esta... no lo he encontrado. Tampoco le he dedicado "demasiado" tiempo: buscarlo en el menú, probar en configuración, intentar abrir Synaptic desde el terminal, para instalar paquetes con un entorno "más detallado aunque sea menos vistoso"... pero ni siquiera Synaptic viene preinstalado... te dice que lo instales con apt-get... y el apt-get da error...

Se supone que lo pretenden hacer más accesible a usuarios novatos, pero no permiten el uso dual que incluso Windows permite: facilitan el acceso a usuarios noveles que vayan a usar apenas dos o tres aplicaciones, pero a cambio lo hacen casi inservible a los usuarios "clásicos" de Linux (ni siquiera digo usuarios "avanzados", simplemente "normales").

El caso es que aunque quizá "las tripas" sean buenas, el hecho de que venga con Unity y de que haga incómodo elegir otro entorno de escritorio, para mí es suficiente para no dedicarle más tiempo.

Me vuelvo a Linux Mint, que se movía muy bien y era "razonable" de usar. La versión basada en Ubuntu pero con escritorio LXDE seguirá teniendo una partición reservada, para cuando tenga que hacer algo rápido, porque vuela en un Atom. Y la partición de uso habitual, que iba a destinar a Ubuntu 11.10 ahora será para Linux Mint Debian 201109.

No es que me resista al cambio. Es que me resisto a ser improductivo.



07 agosto 2011

Raspberry pi en producción

El proyecto Raspberry Pi pretende fomentar el acercamiento de la informática a los lugares menos favorecidos y que sea factible y barato enseñar programación a niños, "devolver la diversión al aprendizaje de la informática".

Para ello han diseñado un ordenador de muy bajo coste: por unos 25 dólares se tendría un ordenador de tamaño cercano no muy superior al de una tarjeta de crédito (aunque más grueso, claro), al que se podría conectar a un teclado y un ratón USB, así como a una pantalla HDMI.

Las especificaciones provisionales hablan de un procesador ARM a 700 MHz, 128 Mb de RAM para el sistema básico, ranura SD para una tarjeta que contendría el sistema operativo (se están haciendo pruebas a partir de Ubuntu y Debian Linux), soporte de OpenGL 2.0 y reproducción de video 1080p. También se habla de una "versión B", que tendría 256 Mb de RAM y puerto Ethernet por unos 5 dólares extra.

Pero hasta aquí son noticias antiguas. Hay un par de novedades que merece la pena comentar. La primera es que la versión alfa ya está en producción, así que pronto se podrá probar "de verdad" (los creadores hablan del lanzamiento durante el cuarto trimestre de 2011). La segunda es que han convocado un concurso de logotipos, así que si tienes buen gusto para el diseño y quieres dejar tu huella en la historia aunque sea a cambio de "dinero cero" (sólo se habla de regalar un modelo B al ganador, y de invitarle a la presentación oficial), quizá te interese presentar algo.

11 junio 2011

Convertir de 3D a 2D usando perspectiva caballera


Hay varias formas de representar en un plano de 2 dimensiones, puntos que realmente pertenecen al espacio de 3 dimensiones. Algunas de ellas son muy típicas de dibujo técnico, como la perspectiva caballera, la perspectiva isométrica o la perspectiva cónica. Otras aprovechan la capacidad de cálculo de los ordenadores, para permitir cualquier punto de vista o cualquier posición de los ejes.

Vamos a empezar por la perspectiva caballera. En ella, el eje X (anchura) y el Z (altura) se muestran "en verdadera magnitud" (a tamaño real), y el eje Y (profundidad) forma 135 grados con ellos:



Las distancias en el eje Y se suelen representar "reducidas", en un factor de 1/2, o bien de 2/3 o de 3/4.

Representar un punto 3D en pantalla usando este método es fácil:
  • La coordenada X se representará tal cual, correspondiendo con la coordenada X de nuestra pantalla, salvo quizás un desplazamiento a un lado o a otro, para colocar el origen de coordenadas en un punto que no sea la esquina de la pantalla, y salvo quizá un factor de escala para poder ver las cosas con mayor o menor detalle.
  • La coordenada Z del espacio (vertical) corresponderá con la coordenada Y de nuestra pantalla, salvo el desplazamiento del origen, quizá un factor de escala, y con una diferencia importante: es habitual que la Y de una pantalla crezca hacia abajo, y la Z del sistema de coordenadas suele crecer hacia arriba, así que habrá que cambiar de signo.
  • Cada avance en el eje Y del espacio (profundidad), se reflejará en un ligero avance hacia abajo en la pantalla (y*0.5) y un ligero retroceso en horizontal (-y*0.5)

Por tanto, las coordenadas X e Y en pantalla de un punto de coordenadas reales (X,Y,Z), si no hacemos ningún cambio de escala, serían:

xPantalla = x + desplazX
yPantalla = -z + (y * 0.5) - (x * 0.5) + desplazY

Así un fuente de Basic256 que dibujara un "muelle" (un círculo "estirado hacia arriba", para que sus puntos tengan coordenadas X, Y, Z, distintas de cero en su mayoría, y se note el efecto de la profundidad y el de la altura) podría ser:

REM Datos del circulo
xCentroCirculo = 200
yCentroCirculo = 180
radioCirculo = 50

REM Desplazamiento del origen
desplazX = 0
desplazY = 200

clg
for i = 1 to 360
REM Calculo la x,y,z reales
x = xCentroCirculo + radioCirculo * cos(radians(i))
y = yCentroCirculo + radioCirculo * sin(radians(i))
z = i

REM Y las coordenadas de pantalla equivalentes
xPantalla = x + desplazX
yPantalla = -z + (y * 0.5) - (x * 0.5) + desplazY
plot xPantalla, yPantalla
pause (0.01)
next i

(Puedes leer más sobre perspectiva caballera, por ejemplo aquí:)

10 junio 2011

Tiro parabólico


Dibujar una parábola es fácil: basta aplicar la ecuación y = a* x2 + bx + c, de modo que se obtiene un valor de y para cada valor de x.

Es un poco más complejo cuando se trata de un tiro parabólico, que sale desde unas ciertas coordenadas, con una cierta velocidad inicial y un cierto ángulo. La ecuación es de la forma:

y = x · tan a - ( g · x2 / 2 · v2 · cos2 a)

(siendo a el ángulo de salida, v la velocidad inicial, g la constante de la gravedad 9.8)

Para un programa de ordenador, posiblemente habría que cambiar el signo de Y, si las coordenadas de pantalla empiezan en la esquina superior izquierda de la pantalla y crecen hacia abajo. También puede ser necesario ajustar la escala, para no salirnos muy rápido de las coordenadas de la pantalla (por ejemplo, dividiendo entre 10).

Un fuente en Basic256 para dibujar este movimiento podría ser así:

REM Datos del punto de origen
x1 = 10
y1 = 90

REM Datos del punto final
x2 = 200

REM gravedad, angulo de salidad y velocidad inicial
g = 9.8
a = 70
v = 40


REM Borrar pantalla gráfica
clg

REM Parte repetitiva
for x = x1 to x2 step 2
REM Ecuacion de Y a partir de X en tiro parabolico
REM y = x · tan a - ( g · x2 / 2 · v2 · cos2 a)
y = x * tan(radians(a)) - (( g* x* x) / (2 * v * v * cos(radians(a))* cos(radians(a)) ))
REM Cambio la escala a 1/10 para que se vea mejor
y = y1 - (y / 10)
plot x, y
pause 0.1
next x

(Más detalles sobre estas ecuaciones, por ejemplo en estos sitios:)


(la imagen animada está tomada de este último artículo)

08 junio 2011

Movimiento circular


Hemos visto cómo dibujar una recta entre dos puntos (o como hacer que algo se mueva en línea recta). Vamos a ver ahora cómo dibujar un círculo punto a punto, que se podría aplicar para imitar un movimiento circular.

Las ecuaciones de cada punto X e Y de una circunferencia, a partir de su radio (r) y del ángulo (t) que forma ese punto con la horizontal son:

x = r · cos t
y = r · sin t
Si el centro de la circunferencia no es (0,0) sino otro punto de coordenadas (xCentro, yCentro), quedaría:

x = xCentro + r · cos t
y = yCentro + r · sin t

Si aplicamos esto a un fuente con Basic256, podría quedar así:

xCentro = 200
yCentro = 150
radio = 50
clg
for i = 1 to 360
plot xCentro + radio * cos(radians(i)), yCentro + radio * sin(radians(i))
pause (0.01)
next i

Sólo dos consideraciones:
  • Normalmente "a un humano" le resulta cómodo medir en grados, pero las funciones "seno" (sin) y "coseno" (cos) suelen esperar que se indique el ángulo en radianes, por lo que habrá que convertir, usando órdenes como "DEG" (trabajar en grados), funciones como "radians" (convertir a grados, como en el ejemplo anterior) o bien convertir "a mano" de radianes a grados, multiplicando por 180 y dividiendo entre PI.
  • Si los dos radios (horizontal y vertical) no son iguales, obtendremos una elipse en lugar de una circunferencia.



07 junio 2011

Movimiento entre dos puntos en línea recta

Hacer que algo se mueva en línea recta entre dos puntos de la pantalla no es difícil. Basta aplicar la ecuación "punto-pendiente" de la recta:

y - y1 = m (x-x1)

donde (x1,y1) son las coordenadas del punto por el que pasa la recta, y "m" es la pendiente de la recta.

Si lo queremos expresar en función de las coordenadas de dos puntos (x1,y1) y (x2,y2) en vez de usar la pendiente, los cambios no son muy grandes:

y = y1 + ((y2-y1)/(x2-x1)) * (x-x1)

De modo que conociendo x1, x2, y1, y2, podemos obtener la coordenada "y" que corresponde a cada punto "x" de la recta.

Un fuente que lo hiciera con algún intérprete de Basic sencillo y que permita dibujar, como Basic256, podría ser:

REM Datos del primer punto
x1 = 10
y1 = 20

REM Datos del segundo punto
x2 = 100
y2 = 205

REM Borrar pantalla gráfica
clg

REM Parte repetitiva
for x = x1 to x2 step 2
REM Recta con punto y pendiente : y - y1 = m (x - x1)
y = y1 + ((y2-y1) / (x2-x1)) * (x-x1)
plot x, y
pause 0.1
next x



Cuando se trata de un juego, el movimiento se suele repetir como parte del "bucle de juego", así que no existiría ese "for", porque de lo contrario, todo el juego se paralizaría mientras se mueve este elemento.

En este caso, se iría incrementando paso a paso, con algo que podría ser como (siguiendo la sintaxis del lenguaje C y sus derivados):

if (moviendo)
{
x += incrX;
y = y1 + ((y2-y1)/(x2-x1)) * (x-x1)
}
dibujar(x,y);

06 junio 2011

Turbo Pascal en Windows 7 64 bits

Hay quien sigue usando, como herramienta de aprendizaje, antiguos compiladores de MsDos, como Turbo Pascal, Borland Pascal, Turbo C, Borland C++...

El problema es que con las versiones 64 bits de Windows no se pueden utilizar ya estos compiladores. En el caso de Turbo Pascal, el problema no es demasiado grave, porque hay al menos un par de soluciones sencillas:

  • Usar FreePascal, que es muy compatible con Turbo Pascal 7, pero no da problemas con Windows 7 64 bits. De hecho, hay versiones incluso para Windows, Linux y Mac OS. Además, se puede elegir entre el editor en modo texto (al estilo del Turbo Pascal original) o un entorno más moderno "al estilo Windows", si se descarga todo el paquete llamado Lazarus.
  • Usar el propio Turbo Pascal (o Turbo C, o el programa del que se trate) desde algún "emulador de MsDos. El más senciillo de usar es DosBox, pero tiene un problema: no viene "totalmente instalado y listo para usar", porque no crea un "disco C" en el que instalar nuestro compilador. Una alternativa más cómoda es usar "D-Fend Reloaded", que es un entorno mejorado que incluye DosBox, pero lo instala junto con varias utilidades, y deja un "disco C" listo para usar. Además, es fácil copiar cosas desde Windows en ese "falso disco C", porque es una carpeta llamada "VirtualHD" que se encuentra en nuestra carpeta de usuario: C:\Documents and Settings\Usuario\D-Fend Reloaded\VirtualHD Todo lo que copiemos en esa carpeta (por ejemplo, los ficheros de instalación de nuestro compilador) estará visible cuando entremos a D-Fend Reloaded. Una última consideración: en Turbo Pascal, Borland C++ y toda esa familia de productos se pulsaba Ctrl+F5 para lanzar nuestro programa, pero esa tecla está reservada en DosBox para hacer una captura de pantalla, por lo que deberemos ejecutar nuestros programas entrando al menú "Run".

30 abril 2011

Bad image format exception en 64 bits (juegos con SDL que dejan de funcionar)


Los períodos de transición en el software suelen tener un problema: a veces las cosas dejan de funcionar por culpa de los cambios.

Hace poco me he encontrado con uno de esos casos: juegos creados en C# usando SDL, que funcionan correctamente en Windows XP, en Windows 7 de 32 bits... y que en cambio no funcionan en Windows 7 de 64 bits. En concreto, el mensaje de error que devuelven es "Bad image format exception" (excepción provocada por un formato incorrecto de imagen).

Si se tiene acceso al código fuente, es fácil de solucionar: hay que cambiar la plataforma de destino de nuestro proyecto, para que en vez de ser "Any CPU" (cualquier procesador), sea "X86". En Visual Studio 2008 se hacia apenas en un par de clics, pero en Visual Studio 2010 está un poco más escondido.

Los pasos que habría que dar en Visual C# 2010 Express serían:

1) Abrir nuestro proyecto.


2) En la propia barra de herramientas deberíamos poder escoger el "destino" de nuestro proyecto: tanto si es una versión de depuración (Debug) o una definitiva (Release), como el tipo de procesador de destino, que en principio debería ser "Any CPU" (cualquier procesador).


3) Es posible que sólo tengamos la opción "Any CPU" y no se nos permita escoger "X86", que es el nombre genérico para un procesador de 32 bits, y que es la opción que deberemos elegir, porque es lo que esperan las librerías auxiliares que emplea nuestro juego. En ese caso, entramos al menú Generar, y a la opción "Administrador de configuración":



4) Si en la opción de "plataforma", desplegamos la lista correspondiente e "Any CPU", veremos que no tenemos más opciones disponible, pero que podemos añadirlas (con la opción "Nueva..."):



5) Entonces nos mostrarán las demás plataformas que podemos escoger, y que deberían ser Itanium, X64, y (como pretendíamos) X86:


6) Ahora el "Administrador de configuración" debería parecerse más a lo que buscamos:


7) Y la propia barra de herramientas nos debería permitir escoger que nuestro proyecto sea para la plataforma X86:


8) Finalmente, no está de más comprobar que en nuestro proyecto ha quedado también "X86" como destino. Lo haríamos desde el menú "Proyecto", en la opción "Propiedades de...", y dentro de la pestaña "Generar":



Con eso, nuestro juego ya debería funcionar correctamente bajo Windows 64 bits.

¡ Suerte !

20 febrero 2011

Un juego modular con XNA (Convertir el juego de SDL a XNA - 2)


El esqueleto que hemos creado con XNA sirve para juegos sencillos, pero un único fuente de gran tamaño no es lo más sencillo de mantener. En cuanto la lógica del juego se complica, convendrá desglosar en clases que encapsulen los detalles del comportamiento de nuestro personaje, de nuestro(s) enemigo(s), etc.

Así, podríamos comenzar por crear una clase "Elemento gráfico", similar a la que ya usábamos con SDL (aunque todavía mucho más simple), de la que heredáramos para crear nuestro Personaje, los enemigos, los objetos del fondo, etc. Esta clase podría ser así de momento:


class ElemGrafico
{
protected Texture2D miImagen;
protected int x, y;
protected int incrX, incrY;

public ElemGrafico(string nombreFichero)
{
x = 0; y = 0;
incrX = 0; incrY = 0;
CargarImagen(nombreFichero);
}

/// Mueve el elemento grafico a otra posicion
public void MoverA(int nuevaX, int nuevaY)
{
x = (short)nuevaX;
y = (short)nuevaY;
}

/// Carga la imagen que representara a este elemento grafico
public void CargarImagen(string nombre)
{
miImagen = Texture2D.FromStream(miPartida.GetGraphics(),
new FileStream ( nombre ,FileMode.Open ) );

contieneImagen = true;
contieneSecuencia = false;
}


/// Dibuja en pantalla oculta, como parte de un "SpriteBatch"
public void DibujarOculta(SpriteBatch listaSprites)
{
listaSprites.Draw(miImagen, new Vector2(x, y), Color.White);
}
}
Y a partir de ella, podríamos crear clase como la que representa a nuestro enemigo, así:

class Enemigo : ElemGrafico
{
// Constructor
public Enemigo(Partida p)
: base("imagenes/enemigo.png")
{
x = 400; // Valores iniciales
y = 400;
incrX = 2;
}

// Métodos de movimiento
public void Mover()
{
x += incrX;

if ((x <> 700))
incrX = (short)(-incrX);
}
}


De forma que el cuerpo del programa tendría cosas como:


protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);

miPersonaje = new Personaje();
miPersonaje.MoverA(400, 300);

miEnemigo = new Enemigo();
}


protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}


protected override void Update(GameTime gameTime)
{
MoverElementos();
ComprobarTeclas();

base.Update(gameTime);
}


protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

spriteBatch.Begin();
miPersonaje.DibujarOculta(spriteBatch);
miEnemigo.DibujarOculta(spriteBatch);
spriteBatch.End();

base.Draw(gameTime);
}


public void MoverElementos()
{
miEnemigo.Mover();
}


public void ComprobarTeclas()
{
if (Keyboard.GetState().IsKeyDown(Keys.Left))
miPersonaje.MoverIzquierda();
if (Keyboard.GetState().IsKeyDown(Keys.Right))
miPersonaje.MoverDerecha();
...
}


Es decir, creará el personaje, el enemigo, los moverá y dibujará, y podemos incluso crear funciones auxiliares MoverElementos, ComprobarTeclas, ComprobarColisiones, etc., que acerquen la rígida estructura que propone XNA a la estructura "clásica" que habíamos creado por encima de SDL.

Si quieres ver el fuente completo, puedes descargar el mini-proyecto desde su página en Google Code, con el nombre miner002XNA:

http://code.google.com/p/manic-miner/downloads/list

07 febrero 2011

Convertir el juego de SDL a XNA (1)


En este primer acercamiento, veremos las pautas básicas de cómo hacer que un personaje se mueva por la pantalla al pulsar las flechas del teclado. Posteriormente lo ampliaremos para se que parezca un poco más (dentro de lo posible) a las clases que estábamos usando para ocultar SDL.

El primer paso es descargar las herramientas necesarias para crear juegos con XNA: Visual Studio como entorno de desarrollo y XNA Game Studio como biblioteca de juegos.

En primer lugar, descargamos e instalamos Visual Studio. Si queremos ir a la última versión (2010), en su variante “Express” (de evaluación), la podemos encontrar aquí:

http://www.microsoft.com/express/Downloads/#2010-Visual-CS

Podemos optar entre descargar sólo Visual C# en castellano (se descarga un instalador de pequeño tamaño, que luego va descargando progresivamente todo lo que necesita) o bien una imagen ISO que contiene todo Visual Studio, para instalar posteriormente en local.

Después descargamos e instalamos XNA. Su última versión en estos momentos es la 4, sólo en inglés, que podemos encontrar aquí:

http://www.microsoft.com/downloads/en/details.aspx?FamilyID=9ac86eca-206f-4274-97f2-ef6c8b1f478f


Si ahora entramos a Visual Studio e indicamos que queremos crear un Nuevo proyecto. Entre las plantillas que se nos proponen, debería aparecer una llamada “Windows Game (4.0)”.

Damos un nombre al proyecto y aparecerá un fuente llamado “Game1.cs”, que contiene algo similar a (eliminando los comentarios):

public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}

protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
}

protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}

protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
base.Draw(gameTime);
}
}

Las ideas básicas son:
  • “Initialize” es la función de inicialización. En principio, debería crear la infraestructura que fuera necesaria, a excepción de los gráficos.
  • “LoadContent” se encarga de cargar el “contenido” que fueramos a emplear. Ahí preparemos nuestras imágenes.
  • “UnloadContent” se encargará del paso contrario, si fuera necesario (en nuestros ejemplos simples no lo será).
  • “Update” (actualizar) se debe encargar de toda la lógica de juego: actualizar el mundo, comprobar colisiones, comprobar entrada del usuario -teclas, joystick, ratón-, reproducir sonido, etc.
  • “Draw” dibuja los elementos en pantalla.


Si queremos acercarlo un poco a nuestro estilo, la imagen del personaje sería un “Texture2D” (una textura en dos dimensiones), que declararíamos en la parte de atributos:

Texture2D imagenPersonaje;

Para cargar la imagen, podemos “esquivar” los “contenidos” de XNA y hacerlo a nuestra manera, cargando un fichero PNG desde el programa. Esto, en XNA 3.1 se puede conseguir con Texture2D.FromFile:

imagenPersonaje = Texture2D.FromFile(graphics, "imagenes\\personaje.png");

pero esta sintaxis no es válida en XNA 4, sino que tenemos que usar “FromStream”, un poco más incómodo, porque deberemos leer la imagen desde un “FileStream”, así por ejemplo:

imagenPersonaje = Texture2D.FromStream(graphics.GraphicsDevice,
new FileStream ("imagenes\\personaje.png",FileMode.Open));

(como usamos un FileStream, deberemos añadir “using System.IO;” al principio de nuestro fuente).

La alternativa fácil es dejar las imágenes prefijadas, como parte del “contenido” (content) del proyecto, pero no estamos buscando la solución más fácil, sino una que recuerde a nuestra forma anterior de trabajar, cuando usábamos Tao.SDL..

Para la posición, podemos usar dos números enteros, x e y, o podemos emplear un tipo de datos existente en XNA, el “Vector2” (un vector de 2 componentes): lo declararíamos como atributo:

Vector2 posicion;

al que daremos valor desde LoadContent:

posicion = new Vector2(400, 300);

Desde “Update” podemos comprobar si se pulsa ESC para salir o alguna de las flechas de dirección para cambiar la posición del personaje:

if (Keyboard.GetState().IsKeyDown(Keys.Escape))
this.Exit();

if (Keyboard.GetState().IsKeyDown(Keys.Left))
posicion.X -= 5;
if (Keyboard.GetState().IsKeyDown(Keys.Right))
posicion.X += 5;
if (Keyboard.GetState().IsKeyDown(Keys.Up))
posicion.Y -= 5;
if (Keyboard.GetState().IsKeyDown(Keys.Down))
posicion.Y += 5;

Y para dibujar el personaje, y los demás “sprites” del juego, lo haremos desde “Draw”, como parte del “lote de sprites” (spriteBatch), así:

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

spriteBatch.Begin();
spriteBatch.Draw(imagenPersonaje, posicion, Color.White);
spriteBatch.End();

base.Draw(gameTime);
}

Si todos los cambios han ido bien, nuestro proyecto debería compilar correctamente y quizá hasta funcionar, con dos consideraciones:
  • Deberemos crear una carpeta “imagenes” dentro de la carpeta del ejecutable de nuestro proyecto (bin/debug), y copiar en ella la imagen llamada “personaje.png”.
  • Es posible que al lanzar el proyecto, se queje de que nuestra tarjeta gráfica no es lo suficientemente potente y “no soporta un perfil HiDef”. No es problema: vamos al menú Proyecto, escogemos la opción “Propiedades de... (el nombre del proyecto)” y en la primera pestaña, en el apartado “Game profile”, dejamos activada la opción “Use Reach” en vez de “Use HiDef”.


Puedes descargar el mini-proyecto desde su página en Google Code, con el nombre miner001XNA:

http://code.google.com/p/manic-miner/downloads/list


05 febrero 2011

Descubrir tiles en un juego

Si estamos pensando hacer un remake, o al menos tomar ideas de un juego clásico, nos puede interesar tratar de descubrir que casillas repetitivas (tiles) formaban la pantalla de ese juego.

GIMP tiene una opción que puede ser de gran ayuda para conseguirlo. Se trata del filtro de "rejilla", que superpone una cuadrícula a nuestra imagen.

Lo podemos encontrar en el menú Filtros, dentro de la opción Filtros -> Renderizado -> Patrón -> Rejilla.


Nos preguntará:
  • El espaciado horizontal y vertical, que normalmente será 8 o 16 píxeles en una imagen de un juego de 8 bits que no se haya redimensionado, o bien 16 o 32 píxeles si la imagen está a doble tamaño, como ocurre con algunas que exporta CPCE y algún otro emulador.
  • El desplazamiento inicial, tanto en horizontal como en vertical, que suele ser 0.
  • La anchura de las líneas, que debería bastar con 1 píxel, para que no nos tape mucho de la imagen original.
  • El color de las líneas, que depende de cómo sea el juego: en los que tienen una pantalla predominantemente negra, podríamos dibujar líneas blancas, amarillas o incluso rojas.

El resultado debería ser algo parecido a:

En el que se ve claramente las casillas repetitivas que forman el fondo.

Si ahora queremos extraer esas casillas para usarlas nosotros en nuestro remake, tenemos dos opciones (ambas partiendo de la imagen original, no de la recuadrada):
  • Extraer los fragmentos que nos interesan, con un poco de paciencia y la herramienta de recorte, como ya vimos.
  • Crear un programita que extraiga todos los tiles por nosotros, y luego eliminamos los repetidos.
Ese tipo de programas puede ser muy fácil de hacer si nos apoyamos en herramientas como "nconvert" (www.xnview.com), que sean capaces de convertir una imagen a PNG y de cambiarle el tamaño o recortarla, todo ello a partir de los parámetros que le indiquemos. Así, nuestro programa simplemente tendría que llamar a "nconvert" (o la herramienta similar que escojamos) de forma repetitiva, indicando qué fragmento queremos extraer cada vez.

Un programa básico en C# que nos ayudara en esta tarea podría ser así:


// Recortador de imágenes en tiles
// (se apoya en nconvert)

using System; // WriteLine
using System.IO; // Path
using System.Diagnostics; // Process

public class RecortadorDeFicheros
{
public static void Main( string[] args )
{
if (args.Length < 1)
{
Console.WriteLine("Indica el nombre de fichero a recortar");
return;
}

int anchoImagen = 768;
int altoImagen = 576;

int anchoTile = 32;
int altoTile = 32;

int cantidadColumnas = anchoImagen / anchoTile;
int cantidadFilas = altoImagen / altoTile;

string nombre = args[0];
string nombreSalida = Path.GetFileNameWithoutExtension(nombre);
string extension = Path.GetExtension(nombre);


for (int fila = 0; fila < cantidadFilas; fila++)
for (int columna = 0; columna < cantidadColumnas; columna++)
{
Console.WriteLine("Fragmento: "+fila+","+columna);
string filaTexto = fila.ToString("00");
string columnaTexto = columna.ToString("00");
string nombreFinal = nombreSalida + filaTexto +
columnaTexto + "." + extension;
File.Copy(nombre, nombreFinal);
Process.Start("nconvert.exe",
"-out png -D -crop "
+columna*anchoTile + " " // x
+fila*altoTile + " " // y
+anchoTile + " " // w
+altoTile + " " // h
+nombreFinal
);
}
}
}