Fuentes con Bitmaps y estilo display LED – XNA


Con XNA podemos escribir texto si creamos un objeto tipo SpriteFont, pero también podemos cargar una imagen o bitmap que contenga los caracteres y luego dibujarlos.

La forma que voy a mostrar es muy fácil, para empezar, cargamos en nuestro proyecto una imagen que contenga los caracteres que vamos a mostrar, en este caso voy a usar una imagen con los números y la coma:

Ahora que tenemos cargada la imagen, procedemos a crear un GameComponent, que tendrá una textura, un espaciado, un interlineado, ancho y alto, además del texto que se va a mostrar.

Antes de mostrar el código, voy a explicar la lógica:

Cuando vamos a dibujar por ejemplo el número 100, debemos primero saber que porción de la imagen pertenece al número 1 y al número 0, para eso vamos a usar un texto que sea igual a la imagen, con el texto podemos saber en que posición esta el número y luego con algunos cálculos cortamos la imagen original y se muestra solo el número.

Ejemplo:

La cadena de caracteres que se parece a la imagen es “1234567890.”, si buscamos el número 1, lo encontraremos en la primera posición, con la posición que obtuvimos del número, podemos calcular un rectángulo para cortar el número en la imagen.

El código del componente es el siguiente:

public class Fuente : Microsoft.Xna.Framework.DrawableGameComponent
   {
      ContentManager Content;
      SpriteBatch spriteBatch;

      public string Filename;
      public string Characters;
      public int StartOffset;
      public int CharacterSpacing;
      public int CharacterWidth;
      public int CharacterHeight;

      public Texture2D Textura { get; set; }
      public Vector2 Posicion { get; set; }
      public String Digitos { get; set; }

      public Fuente(Game game, string fileName, string characters, int startOffset,
                        int characterSpacing, int characterWidth, int characterHeight)
         : base(game)
      {
         Content = (ContentManager)Game.Services.GetService(typeof(ContentManager));
         spriteBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
         Filename = fileName;
         Characters = characters;
         StartOffset = startOffset;
         CharacterSpacing = characterSpacing;
         CharacterWidth = characterWidth;
         CharacterHeight = characterHeight;

         LoadContent();
      }

      protected override void LoadContent()
      {
         if (!String.IsNullOrEmpty(Filename))
         {
            Textura = Content.Load(Filename);
         }
         base.LoadContent();
      }

      ///
<summary> /// Allows the game component to perform any initialization it needs to before starting /// to run. This is where it can query for any required services and load content. /// </summary>
      public override void Initialize()
      {
         // TODO: Add your initialization code here

         base.Initialize();
      }

      ///
<summary> /// Allows the game component to update itself. /// </summary>
      ///Provides a snapshot of timing values.
      public override void Update(GameTime gameTime)
      {
         base.Update(gameTime);
      }

      public void DibujarTexto(String digitos, Vector2 posicion)
      {
         Digitos = digitos;
         Posicion = posicion;
      }

      public override void Draw(GameTime gameTime)
      {
         float xPosition = Posicion.X;
         for (int i = 0; i < Digitos.Length; i++)
         {
            if (Digitos[i] != ' ')
            {
               int character = this.Characters.IndexOf(Digitos[i]);
               //Dibuja el caracter correcto en la posición correcta
               spriteBatch.Draw(Textura, new Vector2(xPosition, Posicion.Y), new Rectangle(character * this.CharacterSpacing +
                                 this.StartOffset, 0, this.CharacterWidth, this.CharacterHeight), Color.White);
            }

            //Mueve la posición al próximo caracter.
            xPosition += this.CharacterWidth;
         }
         base.Draw(gameTime);
      }

Para usarlo debemos inicializarlo, enviado como parametros el nombre de la imagen, el espaciado, el ancho, alto y adicionarlo a los componentes:


font = new Fuente(this, "numbers_large", "1234567890,", 20, 64, 30, 64);

Components.Add(font);

Y para mostrar un texto, se invoca el método DibujarTexto, enviando el texto y la posición donde se va a dibujar:


font.DibujarTexto(gameTime.TotalGameTime.ToString(),new Vector2(100,20));

Al ejecutar el ejemplo, podemos ver el tiempo que transcurre:

Texto al estilo reloj digital o display de LEDs

Existen varios tipos de LEDs, los de 7 segmentos, que son los que tienen los relojes digitales y solo permiten dibujar los números, algunos símbolos y algunas letras.

Como se ve en la imagen anterior, cada segmente tiene un número, y para mostrar un número debemos prender o apagar los segmentos, por ejemplo, para mostrar el 2, se prenden los segmentos 1,2,7,5 y 4.

También existe un LED de 14 segmentos y uno de 16 segmentos:

En XNA, para crear el efecto de dibujar números y símbolos, debemos tener una imagen donde este dibujado cada segmento, ejemplo:

En la imagen anterior, aunque casi no se ve, hay una imagen con todos los 16 segmentos apagados, luego hay varias imágenes, cada una con un segmento diferente.

La manera de convertir un carácter en el código a una imagen LED, es enumerar cada segmento y usar una matriz de 1 y 0 de 16 dígitos, cada uno representa un segmento, y el 1 representa un LED encendido. Ejemplo:

La matriz que representa el número 0 es: 1111111100000000, donde el orden de los segmentos es: 1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16.

Ya pasando la teoría voy a mostrar el código del componente LED, el cual puede ser de 7 y 16 segmento, se tiene una textura, una posición, un total de dígitos o LEDS a mostrar, un caracter de relleno para cuando el texto es menor a la cantidad de dígitos, un Color y un mensaje.

public class LCD : Microsoft.Xna.Framework.DrawableGameComponent
   {
      ContentManager Content;
      SpriteBatch spriteBatch;

      public enum TipoLED
      {
         Siete,
         Dieciseis
      }

      public Texture2D Textura { get; set; }
      public Vector2 Posicion { get; set; }
      public Int32 Width { get; set; }
      public Int32 Height { get; set; }
      public Color Color { get; set; }
      public Int32 TotalDigitos { get; set; }
      public String Mensaje { get; set; }
      public Char Relleno { get; set; }
      public String NombreTextura { get; set; }
      public TipoLED Tipo { get; set; }

      private string matriz;

      public LCD(Game game, TipoLED tipo)
         : base(game)
      {
         Content = (ContentManager)Game.Services.GetService(typeof(ContentManager));
         spriteBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
         Color = Color.White;
         Posicion = Vector2.Zero;
         Tipo = tipo;
         TotalDigitos = 4;
         Mensaje = "";
         if (Tipo == TipoLED.Siete)
         {
            matriz = "0000000";
            NombreTextura = "led7";
         }
         else
         {
            matriz = "0000000000000000";
            NombreTextura = "led16";
         }
         Relleno = ' ';

         LoadContent();
      }

      public override void Initialize()
      {
         base.Initialize();
      }

      protected override void LoadContent()
      {
         if (!String.IsNullOrEmpty(NombreTextura))
         {
            Textura = Content.Load(NombreTextura);
            Height = Textura.Height;
            // se divide en la cantidad de combinaciones de las texturas
            if (Tipo == TipoLED.Siete)
            {
               Width = Textura.Width / 8;
            }
            else if(Tipo == TipoLED.Dieciseis)
            {
               Width = Textura.Width / 17;
            }
         }
         base.LoadContent();
      }

      public override void Update(GameTime gameTime)
      {
         // se alinean a la derecha y se rellenan
         Mensaje = Mensaje.PadLeft(TotalDigitos, Relleno);
         base.Update(gameTime);
      }

      public override void Draw(GameTime gameTime)
      {
         Int32 cantCombinaciones = 0;
         if (Tipo == TipoLED.Siete)
         {
            cantCombinaciones = 7;
         }
         else
         {
            cantCombinaciones = 16;
         }
         //recorremos todos los displays
         for(int i = 1; i  -1)
         {
          //Dibuja un LED vacio
          spriteBatch.Draw(Textura, new Vector2(Posicion.X + ((i + 1) * Width), Posicion.Y), new Rectangle(0, 0, Width, Height), Color);

          if (Tipo == TipoLED.Siete)
          {
           ConvertirSeg7(Mensaje[i - 1].ToString());
          }
          else
          {
           ConvertirSeg16(Mensaje[i - 1].ToString());
          }

          if (matriz.IndexOf("1") > -1)
          {
           for (int j = 1; j <= cantCombinaciones; j++)
           {
            if (matriz[j - 1] == '1')
            {
             spriteBatch.Draw(Textura, new Vector2(Posicion.X + ((i + 1) * Width), Posicion.Y), new Rectangle(Width * j, 0, Width, Height), Color);
            }
          }
        }
      }
      base.Draw(gameTime);
   }

private void ConvertirSeg16(String valor)
 {
valor = valor.ToUpper();
switch (valor)
{
 case "0":
 matriz = "1111111101010000";
 break;
 case "1":
 matriz = "0011000000000000";
 break;
 case "2":
 matriz = "1110111000001100";
 break;
 case "3":
 matriz = "1111110000001100";
 break;
 case "4":
 matriz = "0011000100001100";
 break;
 case "5":
 matriz = "1101110100001100";
 break;
 case "6":
 matriz = "1101111100001100";
 break;
 case "7":
 matriz = "1111000000000000";
 break;
 case "8":
 matriz = "1111111100001100";
 break;
 case "9":
 matriz = "1111110100001100";
 break;
 case "-":
 case ":":
 matriz = "0000000000001100";
 break;
 case "_":
 matriz = "0000110000000000";
 break;
 case "=":
 matriz = "0000110000001100";
 break;
 /*Abecedario*/
 case "A":
 matriz = "1111001100001100";
 break;
 case "B":
 matriz = "1111110000000111";
 break;
 case "C":
 matriz = "1100111100000000";
 break;
 case "D":
 matriz = "1111110000000011";
 break;
 case "E":
 matriz = "1100111100001100";
 break;
 case "F":
 matriz = "1100001100001000";
 break;
 case "G":
 matriz = "1101111100000100";
 break;
 case "H":
 matriz = "0011001100001100";
 break;
 case "I":
 matriz = "0000000000000011";
 break;
 case "J":
 matriz = "0011111000000000";
 break;
 case "K":
 matriz = "0000001101101000";
 break;
 case "L":
 matriz = "0000111100000000";
 break;
 case "M":
 matriz = "0011001111000000";
 break;
 case "N":
 matriz = "0011001110100000";
 break;
 case "O":
 matriz = "1111111100000000";
 break;
 case "P":
 matriz = "1110001100001100";
 break;
 case "Q":
 matriz = "1111111100100000";
 break;
 case "R":
 matriz = "1110001100101100";
 break;
 case "S":
 matriz = "1101110100001100";
 break;
 case "T":
 matriz = "1100000000000011";
 break;
 case "U":
 matriz = "0011111100000000";
 break;
 case "V":
 matriz = "0000001101010000";
 break;
 case "W":
 matriz = "0011001100110000";
 break;
 case "X":
 matriz = "0000000011110000";
 break;
 case "Y":
 matriz = "0000000011000001";
 break;
 case "Z":
 matriz = "1100110001010000";
 break;
 default:
 matriz = "0000000000000000";
 break;
 }
 }
 private void ConvertirSeg7(String valor)
 {
valor = valor.ToUpper();
 switch (valor)
 {
 case "0":
 matriz = "1111110";
 break;
 case "1":
 matriz = "0110000";
 break;
 case "2":
 matriz = "1101101";
 break;
 case "3":
 matriz = "1111001";
 break;
 case "4":
 matriz = "0110011";
 break;
 case "5":
 matriz = "1011011";
 break;
 case "6":
 matriz = "1011111";
 break;
 case "7":
 matriz = "1110010";
 break;
 case "8":
 matriz = "1111111";
 break;
 case "9":
 matriz = "1111011";
 break;
 case "-" :case ":":
 matriz = "0000001";
 break;
 case "_":
 matriz = "0001000";
 break;
 case "=":
 matriz = "0001001";
 break;
 case ",":
 matriz = "0000100";
 break;
 default:
 matriz = "0000000";
 break;
 }
 }
 }

En el método Draw, primero se dibuja la imagen que tiene todos los segmentos apagados, luego se recorre la matriz, se verifica que se tenga en la matriz un 1, si tiene un 1 significa que el segmento va prendido y con la misma técnica que usamos en el primer ejemplo, obtenemos la imagen del segmento prendido, y así se sigue hasta que se acaba la matriz.

Los métodos ConvertSeg16 y ConvertSeg7, convierten el carácter a la matriz de segmentos, lo único malo es que debemos predefinir cada carácter.

Las diferentes combinaciones con un LED de 16 segmentos son:

La imagen de como quedan todos los ejemplos es:

Descargar el código fuente.

Referencias:

http://xnacommunity.codeplex.com/

Anuncios

Deseas comentar o sugerir algo?

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s