Share via


Este artículo proviene de un motor de traducción automática.

Las fronteras de las UI

Un pergamino de color para XNA

Charles Petzold

Descargar el ejemplo de código

Charles PetzoldUno de los primeros programas de Windows que escribí para la publicación se ha llamado COLORSCR (“ color desplazamiento ”) y se puso de manifiesto en el número de mayo de 1987 de Microsoft Systems Journal , la tarea predecesora a esta revista.

En los años, he frecuentemente encontrado instructivo volver a escribir este programa para otras API y los marcos de trabajo. Aunque el programa es simple: manipula tres barras de desplazamiento o los controles deslizantes de los valores de rojos, verdes y azules crear un color personalizado, que implica las tareas fundamentales como, por ejemplo, diseño y el control de eventos. En la funcionalidad, así, el programa no es estrictamente una demostración sencilla, sin sentido. Si alguna vez se encuentra usted mismo la creación de un cuadro de diálogo de selección de color, este programa es un buen lugar para comenzar.

En este artículo, deseo se muestra cómo escribir un programa de color de desplazamiento mediante XNA Framework, tal como está implementado en el teléfono de Windows 7. El resultado se muestra en de figura 1.

image: The Xnacolorscroll Program

Figura 1 del Programa Xnacolorscroll

XNA es el código de Microsoft basadas en .NET Framework administrado para la programación de juegos, pero si se inicia la búsqueda a través de los espacios de nombres Microsoft.Xna.Framework, no encontrará las barras de desplazamiento o los controles deslizantes ni botones incluso entre las clases. XNA no es ese tipo de marco de trabajo. Si desea que un control deslizante en el programa, debe crearse específicamente para ese trabajo.

Que se muestra cómo escribir un programa de color de desplazamiento XNA no es simplemente un ejercicio académico, sin embargo. Mi intención es en realidad mucho más amplia.

Como sabe, 7 de teléfono de Windows admite Silverlight y XNA y, por lo general para cualquier aplicación determinada es obvia la elección entre los dos. Sin embargo, es posible que las peticiones gráficas de la aplicación son simplemente demasiado como para que Silverlight, pero al mismo tiempo, es posible que dudas acerca de si acerca de cómo usar XNA en su lugar, faltan elementos habituales, como botones y controles deslizantes.

Mi respuesta es: No tenga miedo. La escritura de controles sencillos de XNA es más fácil de lo que probablemente piensa.

En este artículo también puede considerarse como una introducción a XNA programación para Windows Presentation Foundation (WPF) y Silverlight, los programadores. Me di cuenta que la aplicación de algunos conceptos de WPF y Silverlight para este trabajo, por lo que el sistema de color de desplazamiento XNA es una celebración de sinergia también.

Todo el código descargable de la fuente está en una solución de Visual Studio denominada XnaColorScroll. Esta solución incluye un proyecto de XnaColorScroll de teléfono de Windows (7) y una biblioteca llamada Petzold.Xna.Controls que contiene los cuatro XNA “ controles ” que creé para este programa. (Obviamente, la carga de la solución en Visual Studio requiere que tenga instaladas las herramientas de desarrollo de teléfono de Windows.) Puede hacerse con estos controles y el suplemento de ellos con sus propios de su propia biblioteca de controles XNA.

Juegos y componentes

Igual que la clase principal de un programa WPF, Silverlight o formularios Windows Forms es una clase que deriva de la ventana, página o formulario, la clase principal de un programa XNA se deriva de juego. Un programa de XNA utiliza este juego derivado para dibujar mapas de bits, texto y gráficos en 3D en la pantalla.

Para ayudarle a organizar su programa en entidades funcionales discretas, XNA admite el concepto de un componentes , que es quizás el más cercano que XNA trata de un control. Los componentes que se presentan con dos variedades: GameComponent derivados y DrawableGameComponent derivados. DrawableGameComponent propiamente dicho se deriva de GameComponent y los nombres sugieren la diferencia. Utilizaré dos clases de base en este ejercicio.

La clase de juego define una propiedad con el nombre de los componentes de tipo GameComponentCollection. Todos los componentes que se crea un juego se deben agregar a esta colección. De este modo, se garantiza que el componente obtendrá las mismas llamadas a varios métodos reemplazados que obtiene el juego en sí mismo. Un DrawableGameComponent dibuja sobre lo que se dibuja el juego.

Let’s comenzar el análisis del programa XnaColorScroll, no los derivados de la partida, pero con las derivadas de GameComponent. La figura 2 muestra el código fuente de un componente, llamo a TextBlock y sirve de mucho a la misma manera como WPF o TextBlock de Silverlight.

La figura 2 de los componentes de juegos de TextBlock

public class TextBlock : DrawableGameComponent
{
  SpriteBatch spriteBatch;
  Vector2 textOrigin;

  public TextBlock(Game game) : base (game)
  {
    // Initialize properties
    this.Color = Color.Black;
  }

  public string Text { set; get; }
  public SpriteFont Font { set; get; }
  public Color Color { set; get; }
  public Vector2 Position { set; get; }
  public HorizontalAlignment HorizontalAlignment { set; get; }
  public VerticalAlignment VerticalAlignment { set; get; }

  public Vector2 Size
  {
    get
    {
      if (String.IsNullOrEmpty(this.Text) || this.Font == null)
          return Vector2.Zero;

      return this.Font.MeasureString(this.Text);
    }
  }

  protected override void LoadContent()
  {
    spriteBatch = new SpriteBatch(this.GraphicsDevice);
    base.LoadContent();
  }

  public override void Update(GameTime gameTime)
  {
    if (!String.IsNullOrEmpty(Text) && Font != null)
    {
      Vector2 textSize = this.Size;
      float xOrigin = (int)this.HorizontalAlignment * textSize.X / 2;
      float yOrigin = (int)this.VerticalAlignment * textSize.Y / 2;
      textOrigin = new Vector2((int)xOrigin, (int)yOrigin);
    }
    base.Update(gameTime);
  }

  public override void Draw(GameTime gameTime)
  {
    if (!String.IsNullOrEmpty(Text) && Font != null)
    {
      spriteBatch.Begin();
      spriteBatch.DrawString(this.Font, this.Text, this.Position, this.Color,
                            0, textOrigin, 1, SpriteEffects.None, 0);
      spriteBatch.End();
    }
    base.Draw(gameTime);
  }
}

Tenga en cuenta las propiedades públicas que sigue al constructor en de figura 2. Estos errores indican el texto y la fuente, color y la posición del texto con respecto a la pantalla. Dos propiedades adicionales, HorizontalAlignment y VerticalAlignment, permitir que esa posición al hacer referencia a la parte o el centro de la cadena de texto. En XnaColorScroll, estas propiedades son útiles para centrar el texto con respecto a los controles deslizantes.

La clase también incluye una propiedad de tamaño de get-only, que devuelve el tamaño del texto representado con esa fuente determinada. (La estructura de XNA Vector2 suele utilizarse para representar los tamaños y posiciones de dos dimensiones, así como vectores).

El componente de TextBlock reemplaza los métodos de la actualización y el dibujo, que es normal que un componente con estas características y normal para el juegos derivados también. Estos dos métodos constituyen del programa “ bucle juego ” y se llaman a la misma velocidad, como la resolución de pantalla: 30 veces por segundo de teléfono de Windows 7. Se puede considerar el método de dibujo, como un mensaje WM_PAINT o Reemplazar OnPaint, excepto en que se le llama para cada actualización de la pantalla. La actualización que se llama al método antes de llamar a cada dibujo y está prevista su utilización para llevar a cabo todos los cálculos necesarios y preparación para el método de dibujo.

TextBlock que escriben tiene mucho espacio para las mejoras de rendimiento. Por ejemplo, se puede almacenar en caché el valor de la propiedad de tamaño o sólo volver a calcular el campo textOrigin siempre que cambie cualquier propiedad que contribuye a la misma. Estas mejoras pueden tener prioridad si alguna vez se descubre que el programa ya es lento, ya que todas las llamadas de dibujo y de actualización en el programa se produce más de 0,03 segundos en ejecutarse.

Los componentes y componentes secundarios

Como puede ver en de figura 1, XnaColorScroll tiene seis elementos TextBlock, pero también tres reguladores, que probablemente se van a ser un poco más complicada.

Muy muchos años, es posible que ha probado el control deslizante completo en una sola clase de codificación. Con mi experiencia en las plantillas de control WPF y Silverlight, sin embargo, me doy cuenta que el control deslizante del control que mejor se construye mediante el montaje de un control Thumb y RepeatButton controles. Parece razonable intentar crear un componente de XNA Slider de componentes secundarios similar.

Una clase de juego agrega componentes secundarios a sí mismo si las agrega a su propiedad Components del tipo GameComponentCollection. La pregunta ahora se convierte en: ¿GameComponent sí tiene una propiedad de los componentes de sus propios componentes secundarios?

Por desgracia, la respuesta es no. Sin embargo, un componente tiene acceso a su clase primaria de juego, y si el componente es necesario crear instancias de sus propios componentes secundarios, puede agregar los componentes adicionales a la misma colección de componentes de la clase de juego. Hacer esto en el orden correcto y aún no es necesario trabajar con la propiedad DrawOrder, definido por DrawableGameComponent, que puede ayudar en el orden en que los componentes que se muestren. De forma predeterminada, dibujar en la clase de juego se llama al método en primer lugar, a continuación, todos los métodos de dibujo de los componentes tal y como aparecen en la colección de componentes.

Por lo tanto, let’s comenzar. Al igual que TextBlock, la clase del control Thumb se deriva de DrawableGameComponent. De figura 3 muestra toda la clase de botón, excepto para el procesamiento de contacto. Thumb define tres eventos de los mismos, como los controles WPF y Silverlight Thumb.

La figura 3 de máximo rendimiento de la clase de botón

public class Thumb : DrawableGameComponent
{
  SpriteBatch spriteBatch;
  Texture2D tinyTexture;
  int?
touchId;

  public event EventHandler<DragEventArgs> DragStarted;
  public event EventHandler<DragEventArgs> DragDelta;
  public event EventHandler<DragEventArgs> DragCompleted;

  public Thumb(Game game) : base(game)
  {
    // Initialize properties
    this.Color = Color.White;
  }

  public Rectangle Position { set; get; }
  public Color Color { set; get; }

  protected override void LoadContent()
  {
    spriteBatch = new SpriteBatch(this.GraphicsDevice);
    tinyTexture = new Texture2D(this.GraphicsDevice, 1, 1);
    tinyTexture.SetData<Color>(new Color[] { Color.White });

    base.LoadContent();
  }

  ...
public override void Draw(GameTime gameTime)
  {
    spriteBatch.Begin();
    spriteBatch.Draw(tinyTexture, this.Position, this.Color);
    spriteBatch.End();

    base.Draw(gameTime);
  }
}

En el componente de TextBlock en de figura 2, la propiedad Position es un punto de tipo Vector2;en el botón, la propiedad Position es un objeto Rectangle y define no sólo la ubicación de Thumb, sino también su tamaño rectangular. Los objetos visuales de Thumb consta sólo de un mapa de bits de píxeles en blanco 1 x 1 que se muestra en la ubicación y el tamaño indicado por la propiedad Position. (Al utilizar mapas de bits de píxeles simple expandida a un tamaño determinado es uno de Mi favorito XNA técnicas de programación.)

El control Thumb procesa la entrada táctil, pero no mueve por sí mismo. El componente de Slider es responsable de controlar los eventos de DragDelta desde el control Thumb y establecer una nueva propiedad de posición.

Además de Thumb, el control deslizante también contiene dos componentes de RepeatButton, uno a cada lado de Thumb. Al igual que los controles WPF y Silverlight RepeatButton, estos componentes generan una serie de eventos de Click si mantiene el dedo en ellos.

En el control deslizante de XNA, los componentes de RepeatButton ocupen un área concreta de la pantalla, pero realmente no están visibles. (La pista vertical fina por debajo del centro se dibuja mediante el control deslizante de sí mismo). Esto significa que se puede derivar RepeatButton de GameComponent en lugar de DrawableGameComponent. La figura 4 muestra todo el control RepeatButton excepto para el procesamiento de contacto.

La figura 4 de máximo rendimiento de la clase de RepeatButton

public class RepeatButton : GameComponent
{
  static readonly TimeSpan REPEAT_DELAY = TimeSpan.FromMilliseconds(500);
  static readonly TimeSpan REPEAT_TIME = TimeSpan.FromMilliseconds(100);

  int?
touchId;
  bool delayExceeded;
  TimeSpan clickTime; 

  public event EventHandler Click;

  public RepeatButton(Game game) : base(game)
  {
  }

  public Rectangle Position { set; get; }

  ...
}

En este punto, estamos listos para crear el componente de Slider. Para no razonablemente sencillas, yo mismo restringido a una orientación vertical. Control deslizante tiene las propiedades públicas habituales: mínimo, máximo y un valor, entre otros y también define un evento ValueChanged. En su reemplazo Initialize, Slider crea los componentes de tres secundarias (y los guarda como campos), los controladores de eventos se establece y se agregan a la colección de componentes de su clase de juegos de principal a través de su propiedad juego:

public override void Initialize()
{
  thumb = new Thumb(this.Game);
  thumb.DragDelta += OnThumbDragDelta;
  this.Game.Components.Add(thumb);

  btnDecrease = new RepeatButton(this.Game);
  btnDecrease.Click += OnRepeatButtonClick;
  this.Game.Components.Add(btnDecrease);

  btnIncrease = new RepeatButton(this.Game);
  btnIncrease.Click += OnRepeatButtonClick;
  this.Game.Components.Add(btnIncrease);

  base.Initialize();
}

XNA Game derivados y GameComponent derivados tienen tres veces para realizar la inicialización, el constructor de clase, reemplace el método Initialize y reemplace el método LoadContent, y es posible que ser un poco confuso cuándo se debe utilizar lo que. Como su nombre indica, LoadContent es muy útil para cargar el contenido (por ejemplo, las fuentes o mapas de bits) y se debe utilizar para todos los demás inicialización que depende de la existencia del objeto GraphicsDevice.

Yo prefiero reemplazan la creación de componentes y su adición a la colección de componentes durante la inicialización, como se muestra en el fragmento de código anterior. En XnaColorScroll, la clase derivada de juego también crea seis elementos TextBlock y reemplazan tres componentes del control deslizante en su inicialización. Cuando se llama al método Initialize de la clase base al final, a continuación, se llaman a todos los métodos Initialize para los componentes secundarios. El método Initialize en cada control, a continuación, crea el control Thumb y dos componentes de RepeatButton. Este esquema, se garantiza que todos estos componentes se agregan a la colección de componentes en un orden adecuado para la representación.

Procesar la entrada táctil

El medio principal de la entrada del usuario en la mayoría de los programas de Windows teléfono 7 es multi-touch y XNA no es una excepción. (Entrada de teclado también está disponible para programas XNA y programas pueden utilizar también situado del teléfono móvil como un dispositivo de entrada).

Un programa de XNA obtiene la entrada táctil durante el reemplazo de actualización. No hay ningún evento de contacto. Contacto de todas las de entrada se sondea y es accesible a través de la clase TouchPanel. El método estático de TouchPanel.GetState proporciona entrada táctil de bajo nivel que se compone de objetos de TouchLocation que indique actividad de Pressed, Moved y lanzada con los números de ID. de enteros y de información de posición para distinguir entre varios de los dedos. El método TouchPanel.ReadGesture (que no utilizados en este programa) proporciona un mayor nivel de soporte técnico de movimiento.

Cuando empecé a trabajar con componentes de juegos, pensé que cada componente se puede obtener la entrada táctil por sí mismo, realizar lo que necesita y omitir el resto. Esto no parece funcionar muy bien;era como si la clase de juego y los componentes se todos compitiendo para entradas táctiles en vez de compartirla como niños educados.

Se ha decidido idear otro sistema para procesar la entrada táctil. Sólo el juego de la clase llama TouchPanel.GetState. A continuación, cada objeto se pasa a componentes secundarios a través de un método de TouchLocation llamo ProcessTouch. Esto permite a los componentes obtener dibs primeros en la entrada. Un componente que hace que se utilice de un objeto que devuelve true desde el método ProcessTouch y procesamiento adicional de una que termina de objeto concreto de TouchLocation de TouchLocation. (Devolvió true desde ProcessTouch es como se establece la propiedad Handled de RoutedEventArgs true en WPF o Silverlight).

En XnaColorScroll, el método Update en las llamadas de la clase principal de juego TouchPanel.GetState para obtener todo el toque de entrada y, a continuación, se llama al método ProcessTouch en cada componente de Slider tal como se muestra aquí:

TouchCollection touches = TouchPanel.GetState();

foreach (TouchLocation touch in touches)
{
  for (int primary = 0; primary < 3; primary++)
    if (sliders[primary].ProcessTouch(gameTime, touch))
      break;
}

Tenga en cuenta la transformación de cada objeto TouchLocation se detiene cada vez que ProcessTouch devuelve true.

El método ProcessTouch en cada control Slider, a continuación, llama a los métodos ProcessTouch en los dos componentes de RepeatButton y Thumb componente:

public bool ProcessTouch(GameTime gameTime, TouchLocation touch)
{
  if (btnIncrease.ProcessTouch(gameTime, touch))
    return true;

  if (btnDecrease.ProcessTouch(gameTime, touch))
    return true;

  if (thumb.ProcessTouch(gameTime, touch))
    return true;

  return false;
}

Si cualquiera de estos componentes (o la propia clase de juego) realizar tareas realizar sus propios procesos de contacto, llamaría a ProcessTouch en cada elemento secundario. Cualquier objeto TouchLocation no procesado por los elementos secundarios que, a continuación, se puede examinarse para el uso de la clase. De hecho, este esquema permite que los elementos secundarios de forma visual de nivel superiores para que el primer acceso a datos, lo que a menudo exactamente lo que desea. Es muy similar al tratamiento del evento enrutado en WPF y Silverlight.

Tanto el RepeatButton y Thumb están interesados en la entrada táctil si primero se toca un dedo en el área indicado por el rectángulo de posición del componente. El componente, a continuación, “ captura ” ese finger, guarde el ID de contacto. Todos los demás con el que la entrada táctil ID pertenece a ese componente concreto hasta que se publique el dedo.

Propagación de eventos

El procesamiento de contacto de los componentes de Thumb y RepeatButton es en realidad la crux del control Slider, pero eso es algo que pueda estudiar por sí mismo si está interesado. Cuando el componente de Thumb detecta un movimiento del dedo en su superficie, genera eventos DragDelta;Cuando el componente de RepeatButton detecta los grifos o presiones de sostenido, se activa haga clic en eventos.

Estos eventos se controlan mediante el componente de control principal, que ajusta la propiedad Value en consecuencia, tanto éste activa un evento ValueChanged del control deslizante. El control deslizante también es responsable de establecer las propiedades de posición de Thumb y dos componentes de RepeatButton basados en el nuevo valor.

La clase de juego controla los eventos de ValueChanged desde los tres componentes de control en un único controlador de eventos, por lo que el método simplemente establece nuevos valores de los tres componentes de TextBlock y un color nuevo:

void OnSliderValueChanged(object sender, EventArgs args)
{
  txtblkValues[0].Text = sliders[0].Value.ToString("X2");
  txtblkValues[1].Text = sliders[1].Value.ToString("X2");
  txtblkValues[2].Text = sliders[2].Value.ToString("X2");

  selectedColor = new Color(sliders[0].Value, 
                            sliders[1].Value, 
                            sliders[2].Value);
}

Deja el reemplazo de dibujo en la clase de juego con sólo dos tareas: Dibujar el rectángulo negro que actúa como fondo de detrás de los componentes y dibujar el rectángulo con un nuevo selectedColor a la derecha. Ambas tareas implican un mapa de bits de 1 x 1 píxeles estirada para ocupar la mitad de la pantalla.

¿Mejor que Silverlight?

Recientemente también escribí una versión de Silverlight del programa de color de desplazamiento para el teléfono de Windows 7 y descubrí que el programa sólo me ha permitido manipular un control a la vez. Sin embargo, si distribuye el programa XnaColorScroll a un teléfono, se pueden manipular todos los controles Slider tres por separado. Me parece muy interesante esa diferencia. Muestra las interfaces una vez más la forma de alto nivel como, por ejemplo, Silverlight pueden realizar nuestras vidas mucho más sencillo, pero a menudo algo debe sacrificarse. Creo que el concepto se denomina TANSTAAFL: No ain't dicho nada como una comida de libre.

XNA es tan bajo nivel en algunos aspectos que pueden recordar nos de la West Wild. Pero con un poco sensato utilizar de componentes (incluidos los componentes que se generan a partir de otros componentes) y emulando el control de eventos enrutados, incluso XNA puede ser tamed y a parece más a Silverlight, tal vez aún mejor.

Charles Petzold es editor colaborador de larga para MSDN Magazine*.*Su libro nuevo, “ programación Windows teléfono 7 ” (Microsoft Press, 2010), está disponible como descarga gratuita en bit.ly/cpebookpdf de .

Gracias al siguiente experto técnico para este artículo: Shawn Hargreaves